Swiftで遊ぼう! on Hatena

あしたさぬきblogでやってた初心者オヤジのiOSプログラミング奮闘記がHatenaに来ました

Swiftで遊ぼう! - 192 - アプリを作ろう UIAlertControllerとUIAlertAction

2016年12月6日:Swift 3に向けて改訂*1

毎日少しずつ勉強をしているSwiftによるiOSアプリ開発。しかし、50オヤジの衰えた頭にはかなり堪えている...(^_^;)「継続は力なり」という魔法が私にもかかるのだろうか?

2015年1月20日

先の見えない勉強を続けていた2年前... 今読み直してみると継続していることで少しずつ理解が深まっていることは実感できます。趣味のレベルでプログラミングをしているので、これくらいゆっくりでも何か得るものもあるんじゃないかと信じながら続けています。

ここの記事は、Hatenaでまとめた最初の記事になります。元ネタは、Beginning iPhone Development with Swift: Exploring the iOS SDKでしたが、2年が経過して、参照した本もBeginning iPhone Development with Swift 3: Exploring the iOS SDKになりました。

UIAlertControllerを実装するデモに取り組んでいましたが、プロジェクトの説明が中途半端になっているので、改めてミニプロジェクトにして説明をします。

AlertControllerDemo」というSingle View Applicationプロジェクトを作ります*2。Deviceは簡単に実装できるように「iPhone」だけにします。真っ白なMain.storyboardの上部に「UITextField」、真ん中に「UIButton」を設置してコンストレイントを設定*3させてRunさせると次のようにシンプルな画面になります。

f:id:yataiblue:20161206184150j:plain

この2つのオブジェクトをViewControllerクラスの中でコード化*4して次の@IBOutletプロパティと@IBActionメソッドを作成します。

@IBOutlet weak var nameField: UITextField!

@IBAction func buttonPressed(_ sender: UIButton) { }

デモの目的はボタンを押すことで、AlertControllerに表示をコントロールさせる仕組みなので、この@IBActionメソッドに次のようなコードを加えます。

@IBAction func buttonPressed(_ sender: UIButton) {
        
    let controller = 
        UIAlertController(title: "いいんですか?", 
                        message: nil, 
                 preferredStyle: .actionSheet)
        
        
    let yesAction = 
        UIAlertAction(title: "はい!いいですよ", 
                      style: .destructive,  
                    handler: { action in
        let msg = self.nameField.text!.isEmpty ? 
                  "気楽にいこう 大丈夫だよ" : 
                  "気楽にいこう\(self.nameField.text!)," + 
                                                  "大丈夫だよ"
        let controller2 = 
            UIAlertController(title: "何かをやり遂げたよ", 
                            message: msg, 
                     preferredStyle: .alert)
        let cancelAction = 
            UIAlertAction(title: "わおー", 
                          style: .cancel, 
                        handler: nil)
        controller2.addAction(cancelAction)
        self.present(controller2, 
                     animated: true, 
                   completion: nil)
    })
        
    let noAction = 
        UIAlertAction(title: "駄目だ!", 
                      style: .cancel, 
                    handler: nil)
        
    controller.addAction(yesAction)
    controller.addAction(noAction)

    /* iPadのためのコードなのでこのデモには必要ありません。   
    if let ppc = controller.popoverPresentationController {
        ppc.sourceView = sender as? UIView
        ppc.sourceRect = sender.bounds
    }
    */
        
    present(controller, animated: true, completion: nil)
}

iOSには2種類のアラート表示機能が用意されています。アクション・シート(ActionSheet)とアラート(Alert)の使い分けを学びます。iPhoneの画面の下からせり出し選択肢を強要するのがアクション・シート。ポップアップして注意喚起するウインドウがアラートです*5

これらのスタイルはUIAlertControllerクラスで制御されています、しかし、コンテンツになるボタン情報はUIAlertActionクラスです(はてな記法でSwift言語を扱ってみる - Swiftで遊ぼう! on Hatena)。

全く知ら無いクラスな*6ので、使用するためにクラス情報(初期化ステップ、プロパティにメソッド)を調べます。

どうやって調べる?

Xcodeのメニューにある「Help」から「Documentation and API Reference」を選んで、検索ボックスにUIAlertControllerと入力するだけです。

ほら、情報が出てきました。宣言法の説明もあります。

    init(title: String?, 
       message: String?, 
preferredStyle: UIAlertControllerStyle)
  • title:警告メッセージがなせ必要になったのか理由をユーザーに伝えるための情報表示。オプショナルですが値を持たせるのが一般的です。
  • message:もう少し詳細な情報が必要な場合に加えます。titleの下に少し小さなフォントで表示されます。
  • preferredStyle:アクションシート(ActionSheet)にするかアラート(Alert)ウインドウにするか選択します。これはイーナム型なので、パラメーターを渡す時に「.actionSheet」か「.alert」にします。

ボタンのコードを最初からみていきます。

let controller = UIAlertController(title: "いいんですか?", 
                                 message: nil, 
                          preferredStyle: .actionSheet)

まずボタンが押されると、UIAlertControllerクラスのインスタンス化が生じます。定数controllerを生成するために3つのパラメーターが必要です。titleはString型なので、そのまま文字列の"いいんですか?"を渡しています*7

次の「message」ですが、今のところ付帯情報はいらないので、「nil」を与えます。オプショナル型なので可能です。

そして、最後のpreferredStyleはDocumentation and API Referenceの情報をみて確認します。UIAlertControllerクラスのプロパティの1つでイーナム(enum)型です。その項目にActionSheetとAlertがあります。ということで、ここでは「.actionSheet」を渡してインスタンスが生成されました。

ここで実験をします。下記のコードだけでRunします。

@IBAction func buttonPressed(_ sender: UIButton) {
        
    let controller = 
        UIAlertController(title: "いいんですか?", 
                        message: nil,
                 preferredStyle: .actionSheet)
        
    present(controller, animated: true, completion: nil)
}

「Button」を押すと次のようになります。

f:id:yataiblue:20161206184128j:plain

出現したアクションシートは消せませんし、無反応で使い物になりません。AlertControllerにAlertActionを加えてインタラクティビティを持たせていきます。

UIAlertActionクラスのコードは少し複雑になります。まずアクションシートの挙動をみていきます。2つのボタンをアクションシートに設置させて、次のように「はい! いいですよ」ボタンと「駄目だ!」ボタンをcontrollerに持たせます。

f:id:yataiblue:20161206184810j:plain

この2つのボタンはUIAlertActionクラスのインスタンスです。

let yesAction = UIAlertAction(title: "はい!いいですよ", 
                              style: .destructive, 
                            handler: { action in
            // コードは省略
        })
        
let noAction = UIAlertAction(title: "駄目だ!", 
                             style: .cancel, 
                           handler: nil)

UIAlertActionクラスをインスタンス化するための初期化(イニシャライザ)に関する情報も「Dodumentation and API Reference」で調べます。

init(title: String?, 
     style: UIAlertActionStyle, 
   handler: ((UIAlertAction) -> Void)? = nil)
  • title:ボタンのタイトルなのにnilを取れるようになりました。これはSwift2.0からオプショナル型に変更されています。
  • style:これは警告がどういうタイプなので、イーナム型のUIAlertActionStyleをチェックしないといけません*8
  • handler:これは典型的なクロージャです。戻り値の無い関数型です。

yesActionのhandlerは後で考察するとして、全体のコードの流れをまず理解します。

2つのボタンをインスタンス化(UIAlertAction)ができたので、これをcontrollerに加えます。

これはUIAlertControllerが持っているメソッド.addAction()を使います。

controller.addAction(yesAction)
controller.addAction(noAction)

2015年1月のオリジナル記事で扱っていたデモはiPadでも動くようにしていたので、この説明が残っています。

これでiPhoneのアクションシートの設定は問題ありません。しかし、iPadの場合はアクションシートもポップアップウインドウになるため位置決めをする必要があります。

if let ppc = controller.popoverPresentationController {
    ppc.sourceView = sender as? UIView
    ppc.sourceRect = sender.bounds
}

このコードブロックは、iPadのボタンが押された場合、popoverPresentationControllerに値が入ってppcが作られます。しかし、iPhoneだったらnilになりif条件文がfalseになって飛ばされます。

最後のコードでアクションシートが画面に表示されます。

present(controller, animated: true, completion: nil)

アクションシートの表示の仕組みの流れを説明しました。しかし、アクションシートの「はい!いいですよ」ボタンを押した時のhandlerの説明が残っています。

クロージャ(Closure)

ここのコードがクロージャ(Closure)です。

let yesAction = 
    UIAlertAction(title: "はい!いいですよ", 
                  style: .destructive,  
                handler: { action in
    let msg = self.nameField.text!.isEmpty ? 
              "気楽にいこう 大丈夫だよ" : 
              "気楽にいこう\(self.nameField.text!)," + 
                                              "大丈夫だよ"
    let controller2 = 
        UIAlertController(title: "何かをやり遂げたよ", 
                        message: msg, 
                 preferredStyle: .alert)
    let cancelAction = 
        UIAlertAction(title: "わおー", 
                      style: .cancel, 
                    handler: nil)
    controller2.addAction(cancelAction)
    self.present(controller2, 
                 animated: true, 
               completion: nil)
})

まず、この「はい!いいですよ」ボタンが押されると、handlerとしてUIAlertActionクラスインスタンスから戻り値の無い関数が実行されます。これはクロージャ(Closure)ーが実行されるということになります。

もう一度Documentation and API Reference*9で確認します。

// Swift1.2
init(title: String?, 
     style: UIAlertActionStyle, 
   handler: ((UIAlertAction) -> Void)? = nil)

UIAlertActionクラスのインスタンス化(初期化)に必要なパラメータのhandlerは「handler: ((UIAlertAction) -> Void)? = nil)」で、戻り値の無いクロージャが必要になります。

じゃあクロージャに注目します。

{ action in
    let msg = self.nameField.text!.isEmpty ? 
              "気楽にいこう 大丈夫だよ" : 
              "気楽にいこう\(self.nameField.text!)," + 
                                              "大丈夫だよ"
    let controller2 = 
        UIAlertController(title: "何かをやり遂げたよ", 
                        message: msg, 
                 preferredStyle: .alert)
    let cancelAction = 
        UIAlertAction(title: "わおー", 
                      style: .cancel, 
                    handler: nil)
    controller2.addAction(cancelAction)
    self.present(controller2, 
                 animated: true, 
               completion: nil)
}

〜 in 〜」はクロージャの構文で、最初の引数のネーミングは何でもいいんです。ここでactionにしてますが、something、anything、papipupepでもかまいません。これが「UIAlertActionクラスのインスタン -> Void」という型になり、「in」以下が実行されます。

UIAlertControllerのインスタンスcontroller2を生成して、UIAlertActionのインスタンスだけで画面に表示できないからです。

UIAlertControllerのパラメーターは3つです。最初のタイトルは「何かやり遂げたよ」を指定して、タイトルの下にmessageも表示させます。

定数msgが引数として読まれます。

let msg = self.nameField.text!.isEmpty ? 
          "気楽にいこう 大丈夫だよ" : 
          "気楽にいこう\(self.nameField.text!)," + 
                                          "大丈夫だよ"

「A ? B : C」という構文を覚えているかな?

nameFieldは、名前を入力するテキストフィールドです。 このフィールドに何も入ってなければ、msg = "気楽にいこう 大丈夫だよ"で、「Yuji Tai」と入力されていたら、msg = " "気楽にいこう Yuji Tai 大丈夫だよ"になるんです。

そして、preferredStyleをAlertウインドウにしています。ボタンは一つだけキャンセルボタンを用意するためにcancelActionインスタンスを作って、controller2に組み込んでいます。ViewControllerのメソッド、presentViewController()を使って表示します。

こうすれば次のようにAlertウインドーが出現します。

f:id:yataiblue:20161206190759j:plain

これで簡単なデモは終わります。クロージャの理解は重要です。

*1:2016年2月20日改訂:古いチュートリアルの一部をUIAlertControllerのミニチュートリアルに変更します。内容もSwift2.1向けに書き換えました。新しい内容を引用文として説明していきます。

*2:新しいプロジェクトの作成法は→BASIC 新規プロジェクト作成手順

*3:コンストレイントの話が分からなければ→BASIC オートレイアウトの調整

*4:Main.storyboard上のUIオブジェクトのコード化→BASIC UI部品のコード化

*5:アクションシート(ActionSHeet)はiPadでポップアップするので扱いは異なります。ああー面倒くさいですね

*6:2016年加筆時は既に知っています。

*7:イニシャライザの場合、外部パラメーター名は必要ありません。メソッドのパラメーターと扱いが異なるので初心者は注意!

*8:初心者には大変な作業です。1つ調べるとまた1つ分からない項目が出てきて、また次を調べなければならない無間地獄...誰にでも経験あることかな...

*9:Xcode7 > Menu > Help > Documentation and API Referenceをチェック!