※2016/04/02 更新:最新バージョンに対応
セレクターの部分をSwift2.2の書き方に直しました.
最近は,アラート関連のライブラリも多く見られるようになりましたね.
綺麗なデザインの物から多機能の物,探すのが大変なくらいです.
しかし!あなたは感じたことがありませんか?
「アプリにマッチしてない...」「惜しいんだけど,なんか違う...」
感じたことがなくても,個性のあるアプリを作りたい時はライブラリには依存したくないですよね.
そこで今回は,5分で出来るオリジナルアラートビューのレシピを紹介します.
※新人プログラマ応援
タグをつけさせていただいてます.
正直微妙なラインですが,順序よく説明していくのでSwiftコードを書いていく方法がわかれば,という思いです.
どちらかというと初級者->中級者への移行応援
かもしれない.(私自身もまだもがいている段階ですが.笑)
環境
今回は,以下の環境で作っていきます.
OSX El Capitan 10.11.3
Swift 2.1.1 2.2
Xcode 7.2.1 7.3
Step.0 今回の完成物
先に,全コードを載せます.分かる方はこれとStep.1
で大体分かるかと思います.
これからダラダラと説明が続くので,適宜読み飛ばしてください.
Step.1 UIの構築
まずは,アラートのUIを作っていきます.
File -> New -> File
から,新しいStoryboardを追加します.
今回は,MyAlertController.storyboard
とします.
Object Library
からUIViewControlelr
をD&Dして,準備は完了です.
それでは,自由にデザインしていきましょう!
アラートの背景
以下の二つを設定します.
-
Attributes inspector
のPresentation
をOver Current Context
に設定する - 一番上の階層のViewの背景色は,後ろのVCが見えるように透過させる.(
UIColor(white: 0, alpha: 0.3~0.5)
くらい)
アラートビュー
メインとなるビュー(コンテンツビュー)です.
まずは,ちゃっとパーツを置いてみましょう
AutoLayoutに関してですが,アラートビューは基本的にサイズは固定(メッセージによるが)なので,
今回はコンテンツビューのサイズ,位置(中央)は固定にしました.
中のパーツも適切に指定してあげます.
メッセージ表示用のUILabelに関しては,Attributes inspector
で,lines
を0にすると,メッセージの量によって自動で調整してくれるので,高さは指定しないようにしましょう.
ボタンについて
それっぽいものはできましたが,これでは汎用性がなさすぎます.
そこで今回は,ボタン1つの場合と,ボタン2つの場合を考慮していきます.
(この辺は,作成するアプリの使用目的に合わせて拡張していけばいいかと思います.)
コードでframeいじったりしてもいんですが,できるだけシンプルにしたいんで,ExtraViewsを使用します.
ExtraViewsについては以下を参考にしてください.
・iOS Extra Viewsを使ってみる
下の図のように,ViewController上部のアイコンが3つ並んでる部分に,UIViewをD&Dします.
ViewControllerの上の方に,Viewが浮かぶかと思います.
これがExtraViews,インスタンスは生成されるけどAddSubView()
されてないビューです.
このExtraViewsで,ボタンのレイアウトを行いましょう.
Viewのサイズは280x30
としています.
Step.2 アラートの表示
さて,ようやくレイアウトが終わりました.ここからはコードで各処理を行っていきます.
MyAlertController
クラスの作成
まず,MyAlertController.swift
ファイルを追加します.
File -> New -> File
から,UIViewControllerのサブクラスを作成してください.
各パーツを接続します.合わせてボタンのアクションも指定してあげます.
class MyAlertController: UIViewController {
@IBOutlet weak var contentView: UIView! // コンテンツビュー
@IBOutlet weak var titleLabel: UILabel! // タイトルラベル
@IBOutlet weak var messageLabel: UILabel! // メッセージラベル
@IBOutlet var singleButtonView: UIView! // ExtraViewsのボタン1つの方
@IBOutlet var doubleButtonView: UIView! // ExtraViewsのボタン2つの方
@IBOutlet weak var singleViewAcceptButton: UIButton! // ExtraViewsのボタン1つの方のOKボタン
@IBOutlet weak var doubleViewCancelButton: UIButton! // ExtraViewsのボタン2つの方のCancelボタン
@IBOutlet weak var doubleViewAcceptButton: UIButton! // ExtraViewsのボタン2つの方のOKボタン
override func viewDidLoad() {
super.viewDidLoad()
singleViewAcceptButton.addTarget(self, action: #selector(acceptButtonTapped), forControlEvents: .TouchUpInside)
doubleViewAcceptButton.addTarget(self, action: #selector(acceptButtonTapped), forControlEvents: .TouchUpInside)
doubleViewCancelButton.addTarget(self, action: #selector(cancelButtonTapped), forControlEvents: .TouchUpInside)
}
}
/**
* Button actions ---------------------
*/
extension MyAlertController {
func acceptButtonTapped() {
}
func cancelButtonTapped() {
}
}
アラート表示用のメソッド
何はともあれ,いったん表示してみましょう.
表示用のメソッドを追加して,表示させる側のViewControllerから実行してみます.
MyAlertController.storyboard
でisInitialViewController
にチェックが入っていることを確認してください!
/**
* Show ---------------------
*/
extension MyAlertController {
class func show(presentintViewController vc: UIViewController) {
guard let alert = UIStoryboard(name: "MyAlertController", bundle: nil).instantiateInitialViewController() as? MyAlertController else { return }
vc.presentViewController(alert, animated: false, completion: nil)
}
}
class ViewController: UIViewController {
....
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
MyAlertController.show(presentintViewController: self)
}
....
}
以下のようになっていれば,とりあえずOKです.
確認ができたら,次はラベルのテキストを変更できるようにしましょう.
show()
メソッドにパラメータを増やす+αだけでいけそうです.
class MyAlertController: UIViewController {
....
var titleText: String = ""
var messageText: String = ""
override func viewDidLoad() {
....
titleLabel.text = titleText
messageLabel.text = messageText
}
....
}
/**
* Show ---------------------
*/
extension MyAlertController {
class func show(presentintViewController vc: UIViewController, title: String, message: String) {
guard let alert = UIStoryboard(name: "MyAlertController", bundle: nil).instantiateInitialViewController() as? MyAlertController else { return }
alert.titleText = title
alert.messageText = message
vc.presentViewController(alert, animated: false, completion: nil)
}
}
class ViewController: UIViewController {
....
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
MyAlertController.show(presentintViewController: self, title: "タイトル", message: "メッセージを\n表示しています")
}
....
}
いい感じですね.
Step.3 ボタンの表示
段落の分け方が怪しくなってきましたが,気にせず進めていきましょう.笑
さて,メッセージの表示までできました.次はユーザのアクションを求めるボタンを表示させていきます.
これまでに,二つのボタンパターンを作りましたね.よって,それぞれ追加する用の関数をつくります.
class MyAlertController: UIViewController {
....
private func addSingleButton(title title: String) {
singleButtonView.frame.origin = CGPoint(x: 0, y: contentView.frame.height-singleButtonView.frame.height) // 1
contentView.addSubview(singleButtonView) // 2
singleViewAcceptButton.setTitle(title, forState: .Normal) // 3
}
private func addDoubleButton(cancelTitle cancelTitle: String, acceptTitle: String) {
doubleButtonView.frame.origin = CGPoint(x: 0, y: contentView.frame.height-doubleButtonView.frame.height) // 1
contentView.addSubview(doubleButtonView) // 2
doubleViewCancelButton.setTitle(cancelTitle, forState: .Normal) // 3
doubleViewAcceptButton.setTitle(acceptTitle, forState: .Normal) // 3
}
}
説明は以下の通りです.
- 追加する位置を指定
- コンテンツビューに追加
- ボタンの対鶴を設定
さて,再びshow()
をアップデートしましょう.
今回は,enumでボタンのタイプを指定して,viewDidLoad()
にて追加メソッドを実行するようにしています.
(バグを見つけて急遽こういう形にしたので,もっといい方法があるかもしれないですが,,,)
// ボタンのタイプ
enum MyAlertControllerType {
case SingleButton
case DoubleButton
}
class MyAlertController: UIViewController {
....
var buttonType = MyAlertControllerType.SingleButton("")
override func viewDidLoad() {
....
switch buttonType {
case let .SingleButton(title) :
addSingleButton(title: title)
case let .DoubleButton(cancel, accept) :
addDoubleButton(cancelTitle: cancel, acceptTitle: accept)
}
}
}
/**
* Show ---------------------
*/
extension MyAlertController {
class func show(presentintViewController vc: UIViewController, title: String, message: String, buttonTitle: String) {
guard let alert = UIStoryboard(name: "MyAlertController", bundle: nil).instantiateInitialViewController()
as? MyAlertController else { return }
alert.titleText = title
alert.messageText = message
alert.buttonType = .SingleButton(buttonTitle)
vc.presentViewController(alert, animated: false, completion: nil)
}
class func show(presentintViewController vc: UIViewController, title: String, message: String, cancelTitle: String, acceptTitle: String) {
guard let alert = UIStoryboard(name: "MyAlertController", bundle: nil).instantiateInitialViewController()
as? MyAlertController else { return }
alert.titleText = title
alert.messageText = message
alert.buttonType = .DoubleButton(cancelTitle, acceptTitle)
vc.presentViewController(alert, animated: false, completion: nil)
}
}
これで,無事ボタンも表示できました.
Step.4 ボタンのイベント
さて,ようやく最後のステップです.
Step.2でサラッと追加したacceptButtonTapped()
とcancelButtonTapped()
に処理を書いていきます.
ボタンを押した時に呼び出し側で処理をかけるように,AlertAction
というハンドラを定義します.
ボタンを押した時にこのハンドラを実行することで,呼び出し側のコードを実行できます.
また,どのボタンを押したのか判別できるように,enumでMyAlertControllerAction
を定義します.
enum MyAlertControllerAction {
case Cancel
case Accept
}
class MyAlertController: UIViewController {
....
typealias AlertAction = (action: MyAlertControllerAction)->()
var handler: AlertAction?
....
}
/**
* Button actions ---------------------
*/
extension MyAlertController {
func acceptButtonTapped() {
handler?(action: .Accept)
dismissViewControllerAnimated(false, completion: nil)
}
func cancelButtonTapped() {
handler?(action: .Cancel)
dismissViewControllerAnimated(false, completion: nil)
}
}
最後に,show()
メソッドに,変更を加えます.
/**
* Show ---------------------
*/
extension MyAlertController {
class func show(presentintViewController vc: UIViewController, title: String, message: String, buttonTitle: String, handler: AlertAction?) {
guard let alert = UIStoryboard(name: "MyAlertController", bundle: nil).instantiateInitialViewController()
as? MyAlertController else { return }
alert.titleText = title
alert.messageText = message
alert.buttonType = .SingleButton(buttonTitle)
alert.handler = handler
vc.presentViewController(alert, animated: false, completion: nil)
}
class func show(presentintViewController vc: UIViewController, title: String, message: String, cancelTitle: String, acceptTitle: String, handler: AlertAction?) {
guard let alert = UIStoryboard(name: "MyAlertController", bundle: nil).instantiateInitialViewController()
as? MyAlertController else { return }
alert.titleText = title
alert.messageText = message
alert.buttonType = .DoubleButton(cancelTitle, acceptTitle)
alert.handler = handler
vc.presentViewController(alert, animated: false, completion: nil)
}
}
class ViewController: UIViewController {
....
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
MyAlertController.show(presentintViewController: self, title: "タイトル", message: "メッセージを\n表示しています", cancelTitle: "キャンセル", acceptTitle: "おっけー") { action in
switch action {
case .Accept : print("accept!")
case .Cancel : print("cancel!")
}
}
}
....
}
動作確認できたでしょうか?
スマートとは言い難いかもしれないですが,これで個性的なアラートビューをつくることができるかと思います.
今回のMyAlertController.swift
ファイルをベースとして,UIをいじるだけの爆速開発に貢献できることを期待します.
Step.5 (おまけ)アニメーションをつける
今のままでは表示が少し味気ないですよね.
なので,フェードしながら現れるように,アニメーションをつけてみます.
コンテンツビューを出したり消したりする用のメソッドを作り,viewDidAppear()
, ボタン押下時(acceptButtonTapped()
, cancelButtonTapped()
)に呼び出します.
class MyAlertController: UIViewController {
....
override func viewDidLoad() {
....
animationContentView(true)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
animationContentView(false)
}
private func animationContentView(hidden: Bool, completion: (()->())? = nil) {
let alpha: CGFloat
if hidden { alpha = 0 }
else { alpha = 1 }
UIView.animateWithDuration(0.2, animations: {
self.view.alpha = alpha
}) { _ in
completion?()
}
}
....
}
/**
* Button actions ---------------------
*/
extension MyAlertController {
func acceptButtonTapped() {
animationContentView(true) {
self.handler?(action: .Accept)
self.dismissViewControllerAnimated(false, completion: nil)
}
}
func cancelButtonTapped() {
animationContentView(true) {
self.handler?(action: .Cancel)
self.dismissViewControllerAnimated(false, completion: nil)
}
}
}