リブートキャンプ by Swift 目次

 

 ご無沙汰。

 1ヶ月空いちゃったけど大丈夫!生きてます。

 

 iPhoneやiPadの画面サイズバリエーションがこれだけ増えた今、前回紹介したInterface BuilderでのAuto Layoutの指定技法は、iOSアプリ開発者必須教養と言えるもんなんですが説明始めるとキリないんですよ。

 なので、リブートキャンプではあんまり細かい解説はしません。

 キャンプ後始める予定のカメラでのキャプチャなんかで、その都度説明しようかと考え中。

 待ちきれんわー、な皆さんは、WWDCのビデオ見て学習しましょう。ビデオなので操作自体は、よくわかると思う(ただし説明は英語なりよ)。

 

WWDCのビデオ:

Auto Layout Techniques in Interface Builder

Best Practices for Mastering Auto Layout

Auto Layout by Example

Mysteries of Auto Layout, Part 1

Mysteries of Auto Layout, Part 2

 

 私の「親切すぎるiPhoneアプリ開発の本」を持ってる人は、ビデオを見る前に545ページのAuto Layoutの基礎や、756ページあたりから始まるInterface Builderでの設定を読んでおくとモアベターよ。Xcodeの進化でインターフェースは微妙に変わってるけど、基本となる情報は今でも通用するっす。

 

 

 コードで書くと、かなりな手間のAuto Layout(NSLayoutConstraint)の指定が、画面で効果を確認しながら図形アプリみたいに記述できる点がストーリーボードのいいところですな。

 ちなみに、Interface BuilderによるAuto Layoutの指定自体は.storyboardファイルに限らず、.xibファイルでもできるようになってます。

 

.xibファイル

 

 .xibファイルってのは、前回やったようなUIViewControllerのself.viewに対する「地図」ボタンの作成や配置とかをUIViewレベルでやるためのファイルっす。

 歴史的にはこの.xibファイルでUIViewを1つだけ用意して、それをUIViewControllerのself.viewに適用するってやり方があって、そこから発展してファイル自体にUIViewControllerのself.viewという概念を取り込んだ.storyboardファイルが生まれたわけですな。

 

 

 「UIViewとUIViewController」でMapViewController作るときに"Also create XIB file"てチェックがあったの覚えてるかな〜。

 

 

 あれをチェックすると、MapViewController.swiftファイルと一緒に、self.viewに対応するUIViewが一つだけ用意されてるMapViewController.xibファイルってのが作られるようになっているんです。

 昔は、こうやってUIViewControllerの派生クラスごとに対応する.xibファイルを作ってself.viewのレイアウトをInterface Builderで設定してた。

 なんだけど、今はストーリーボードが用意されて、1つのファイルに複数のビューコントローラを定義できるようになったんですな。

 つまりViewControllerに加えて、MapViewControllerもストーリーボードに追加できるわけです。

 

 

 MapViewController.swiftファイル作成時に、"Also create XIB file"をチェックしなくていいと書いた理由がこれ。

 

 でもってストーリーボードでは、ViewControllerからMapViewControllerへの遷移まで定義できるようになってる。つまりViewController.swiftに用意したshowAsViewメソッド

    @IBAction func showAsView() {
        let mapViewController = MapViewController()
        mapViewController.delegate = altDelegate
        self.present(mapViewController, //  mapViewControllerに切り替え
            animated: true)    //  切り替えはアニメーションさせる
    }

 て部分の大部分が、ストーリーボードでも定義できるわけですよ。

 てなわけで、ストーリーボードが無かった昔は、ビューコントローラごとに.xibファイルを用意してself.viewのレイアウトをやってたんですが、今はほとんどやりません。

注意)といっても、カスタムUIViewをプログラム中で作る時に、UINibを使って.xibファイルから生成なんてことに使えるので.xibファイルの存在意義は今でも十分あるんじゃないかと思われ。UINibについて興味ある人は自分で調べてみましょう。「親切すぎるiPhoneアプリ開発の本」を持ってる人は747ページあたりからの話ね。ちなみに.xibファイルは複数のUIViewのレイアウトを定義したりもできます。

 

 .xibファイルの説明が終わったところで、先に挙げたストーリーボードで複数のビューコントローラの定義、およびビューコントローラ間の遷移の定義ってのをやってみましょう。

 前回の最後のサンプルのMain.storyboardにMapViewController生成と呼び出しを加えてみるっす。

 

前回の最後のサンプル:

 http://tetera.jp/xcc/book-sample/storyboard-4.zip

 

 まずは「地図」ボタンを貼り付けた要領で、ライブラリからView Controllerをcanvasビューの何もないところにドラッグ&ドロップっす。今回の場合はoutlineビューにドロップしてもいいです。

 

 

 新しく貼り付けたView Controllerを選んで、IdentifyインスペクタのClassを見てもらうと、薄い文字で"UIViewController"ってなってます。

 

 

 こいつを"MapViewController"に書き換えてください。

 

 

 MapViewControllerはUIViewControllerからの派生なので、こういった切り替えができる。

 次に、outlineビュー側のViewController項目の中にある「地図」ボタン項目をcontrolキーを押しながらクリックしてドラッグすると、@IBActionメソッドを設定した時みたいに線が伸びるので、この線をMap View Controller項目(classをMapViewControllerに変更したので項目名も変わった)まで持っていって、そこでボタンを放しましょう。

 

 

注意)canvasビュー上でやってもいいし、canvas、outlineビューをまたがってもいい。ここではcanvasビューを縮小表示しているために、canvasビュー上での線を引き出す操作がやりにくいのでoutlineビューを使ってる。

 

 ↓こんな感じでoutlineビューからcanvasビューに線を伸ばしてもいい。

  canvasビューではMap View Controllerのアイコンじゃなくてself.viewが反応する点に注意。

 

 

 そうすっとAuto Layoutの指定の時みたいにメニューがポップするんで、Present modaly項目をクリックしましょう。

 

 


 これで「地図」ボタンがタップされるとMapViewControllerに遷移する以下のコードを記述したのと同等となる。

    
        let mapViewController = MapViewController()
        self.present(mapViewController, //  mapViewControllerに切り替え
            animated: true)    //  切り替えはアニメーションさせる

 

 

 ちなみに、この記述はcanvasビューでは連結線として表現され、outlineビューでは項目として表示されます。

 

 

 これはSegue(セグエ)と呼ばれ、UIViewController間の遷移をオブジェクトとして表現するものです。

 

Segue

 セグエ: (ある曲・話題・状態などから)円滑に[穏やかに]移行する

 

 これで「地図」ボタン押されるとMapViewControllerに遷移することになるんだけど、前回設定した「地図」ボタンタップによるViewControllerのshowAsViewメソッド呼び出す仕組みも残ったままなんすよ。

 このままだと「地図」ボタン押されると、2系統でMapViewControllerに遷移することになるんで変な感じなります。

 Runして「地図」ボタンタップして確認してみてちょ。

 下のDebugエリアのコンソールにワーニングが出るはず。

 

サンプル:

 http://tetera.jp/xcc/book-sample/storyboard-5.zip

 

 

 これは、最初のMapViewController作って切り替えて画面表示の最中に、もう一度別のMapViewController作って、そっちの画面に切り替えようとしたのを怒られてる状態。

 「今、画面切り替えてる最中でしょ」って怒られてるわけです。

 これを防ぐためには、セグエじゃない方、前回の「地図」ボタンタップ側のターゲットアクション実行を解除してやればいい。

 

 ここで、ちょっと面白い実験。

 本来、Main.storyboard側で「地図」ボタンタップ側のターゲットアクションを解除し、使わなくなったViewController.swift側のshowAsViewメソッドを削除するのが正当なんですが、そうせずにViewController.swift側のshowAsViewメソッドだけを削除しちゃうとどうなるか。

 ストーリーボード側の「地図」ボタンタップ側のターゲットアクションには、まだshowAsViewメソッドが設定されたままなんですが、この状態でRunさせて「地図」ボタンをタップするとどうなるか〜。

 

  ・・・
  class ViewController: UIViewController,
  ・・・
    @IBAction func showAsView() {
        let mapViewController = MapViewController()
        mapViewController.delegate = altDelegate
        self.present(mapViewController, //  mapViewControllerに切り替え
            animated: true)    //  切り替えはアニメーションさせる
    }

 

 Runして「地図」ボタンタップして試してください。

 はい死んだ。

 

サンプル:

 http://tetera.jp/xcc/book-sample/storyboard-6.zip

 

 確認したら、Stopボタン押してアプリを強制停止しちゃってください。

 

 

 ま〜、ちゅうわけで@IBActionメソッドを削除する場合は、ストーリーボード側のターゲットアクション接続も確実に切りましょう。

 接続を切るにはMain.storyboardで「地図」ボタンを選んでConnectionsインスペクタでSent EventsグループのTouch Up Insideのターゲットアクション接続の×印をクリックするだけっす。

 

 

 ストーリーボードいじってて、Runするとアプリが死ぬようになったら、真っ先にここら辺を疑うといいです。まあ、そういう時もDebugエリアのコンソールに何かメッセージが出てないかをちゃんと確認するのが一番先だけどね。

 

 ちなみに、ストーリーボードで接続切って、swiftソース側の@IBActionメソッドを残しておくのは問題ない。単に呼び出されないだけっす。

 Runして「地図」ボタンをタップ。

 

サンプル:

 http://tetera.jp/xcc/book-sample/storyboard-7.zip

 

 

 セグエ側の接続が残ってるので、ちゃんと地図画面が表示されます。

 ただし、現時点では問題が残ってる。

 地図画面タップして気づいた人もいると思うけど、「そして委譲へ」で実装したDebugエリアのコンソールにタップ位置の出力が実行されないんすよ。

 

 

 ていうのもセグエでは、showAsViewメソッドでやってたようなMapViewControllerのdelegateプロパティに、ViewControllerのaltDelegateオブジェクトが設定されてないため。

        let mapViewController = MapViewController()
        mapViewController.delegate = altDelegate ←これね
        self.present(mapViewController, //  mapViewControllerに切り替え
            animated: true)    //  切り替えはアニメーションさせる

 

 セグエはUIViewController間の遷移はやってくれるんですけど、ViewControllerとMapViewController間というようなプログラマが勝手に拡張したやり取りまでは面倒みてくれないんすよ。

 なのでMapViewControllerのdelegateプロパティへのaltDelegate設定はプログラムで責任持って実装しないといけません。でもセグエ、こっちの知らないところで勝手に遷移しちゃうじゃん。どーする?

 そういう時のために、UIViewControllerにはセグエでの遷移直前に呼び出されるメソッドが用意されてます。


    func prepare(for segue: UIStoryboardSegue, sender: Any?)

てメソッドなんですが、こいつを今回の遷移元であるViewControllerでオーバーライドすることで、セグエ時のaltDelegateの設定が可能になります。

 セグエの情報はUIStoryboardSegueのインスタンスとして表現されてて、このオブジェクトのdestinationプロパティから遷移先のビューコントローラのインスタンスが手に入るんですな。

 なので、ViewControllerクラスで次のようにprepareメソッドをオーバーライドで追加してaltDelegateを設定します。

 オーバーライドがわからん人は「こんにちは世界」を読みましょう。

  ・・・
  class ViewController: UIViewController,
  ・・・
    //  セグエの遷移直前
    //  segue.destinationがMapViewControllerなので、型変換して
    //  altDelegateを設定する
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        //  今はセグエの遷移先はMapViewControllerのみだが
        //  この先、いろいろなビューコントローラに遷移する可能性を
        //  考えて、if letによるチェックを入れる
        if let mapviewController = segue.destination as? MapViewController {
            mapviewController.delegate = altDelegate
        }
    }
  ・・・ 

サンプル:

 http://tetera.jp/xcc/book-sample/storyboard-8.zip

 

 これでRunして地図画面でタップすると、以前のようにタップ位置がコンソールに表示されるようになる。

 ViewControllerの派生元であるUIViewControllerのprepareメソッドは空(クイックヘルプに書いてる)なので、super側のprepareメソッドを呼び出す必要はないです。呼ぶだけ無駄。

 以上、モーダル遷移をストーリーボードで定義する方法でした。

 

 こうなると、ストーリーボードを使い、ViewControllerのself.viewに「地図」ボタンを貼り付けたように、MapViewControllerのself.viewにMapViewを貼ったりしてMapViewControllerのviewDidLoadメソッドでの作業を軽減させたくなるのは人情なわけですが、ここで気をつけないといけないのは、使ってるMapViewクラスがUIViewをカスタマイズしていて作成時に子供ビューを自身に貼り付けてる点。

 ライブラリに用意されてるのはUIViewなので、これを「地図」ボタンのようにself.viewに配置してクラスをMapViewに書き換えるのは基本なんだけど、それだけだと予想外の結果になります。

 ま、興味のある人は自分なりの解釈で実際にやってRunしてみてください。

 

 ここら辺をどう解決するかは次回。

 

 

リブートキャンプ by Swift 目次

 

 予告通り地図画面起動ボタン付けます。

 

 まずはボタンの画面への貼り付けから。

 この場合、ライブラリからボタンを探さないといけないんだけど、前回のTap Gesture Recognizerを探した要領で、検索ボックスに「but」あたりを入れれば一発で出てきます。

 

 この見つけた項目をCanvasビューにドラッグ&ドロップするのはTap Gesture Recognizerの時と同じ。

 

 

 ちょっと違う点は、ボタンの場合、ドロップした位置にボタンが表示されるってこと。ボタンはTap Gesture Recognizerと違って画面の一部になるんで、ビューコントローラが画面を表示した際に、どう見えるのか確認できるようになってるんですよ。

 

 

 画面に貼り付けたら、あとはボタンのタップに合わせた反応を設定するだけっす。

 で、例のターゲットアクションデザインパターンなわけですよ。

 ボタンもTap Gesture Recognizer同様、他オブジェクトとの連携にこのパターン使ってます。ターゲットアクションデザインパターンがわからん人は「タップしてドン」を読みましょう。

 なので、ボタンの反応もTap Gesture Recognizerと同じで、ターゲットとアクションを設定するってことになる。

 異なる点はTap Gesture Recognizerだと、アクションに対する引き金はタップだけだったんだけど、ボタンにはいろいろな引き金があるってとこ。

 

 

 どの引き金に対して、どうアクションするかはプログラマ次第ってわけで、それぞれにターゲットとアクションを指定できます。

 で、今回なら、Touch Up Inside(タッチした指先がボタン上で放された)時に地図を表示が適切なんで、こいつにターゲットとアクションを指定します。

 前回、Tap Gesture RecognizerでやったみたいにTouch Up InsideからView Controllerに向けて接続線を伸ばせば、ドロップ時に現れるメニューからshowAsViewメソッドをアクションに指定することができます。

 

 

 ちゃんと設定できたら、ボタンのConnectionsインスペクタはこんな感じになる↓

 

 

 これで

 

  画面にボタンを置く

  ボタンがタップされたらshowAsViewメソッドを呼び出す

 

という処理がストーリーボードで記述されたことになるわけっすな。

 前回のサンプル:http://tetera.jp/xcc/book-sample/storyboard-2.zipを元に、上の手順に従ってボタンを加え、Runしちゃってみてください。

 画面タップの他にボタンをタップでも地図画面が出るようになります。

 

 

 てなわけで、ボタン用意できたなら画面全体のタップ反応は不要っすね。ボタン以外の画面タップは反応しなくしちゃいましょう。

 

 今現在は、ViewControllerのshowAsViewメソッドが、ボタンおよびTap Gesture Recognizerの両方のターゲットアクションとして登録されてる状態っす。確認したい人はView Controller選んで、Connectionsインスペクタの画面を見ましょう。   

 Referencing Outlut Collectionsグループで、showAsView項目に2つのオブジェクトが接続されてるのがわかると思う。

 


 なので、このうちのTap Gesture Recognizerとの接続を切ればボタン以外の画面タップは反応しなくなります。

 接続を切るのは、接続項目の横にあるxマークをクリックするだけです。

 

 

 ま、これで全然問題ないんですが、そもそもTap Gesture Recognizer自体が不要なわけですよ。なのでTap Gesture Recognizer自体を削除しましょう。

 これはoutlineビューでTap Gesture Recognizer項目を選んで、deleteキーでOK。

 

 Runするとボタン以外反応しなくなったはず。

 

サンプル:

 http://tetera.jp/xcc/book-sample/storyboard-3.zip

 

 ちなみにサンプルではボタンのタイトルを「Button」から「地図」に変えてるけど、これはcancasビュー上のボタンをダブルクリックすれば変更できます。

 

 ボタンを選んだ状態でAttributesインスペクタで変更でも可。どっちでも好きな方でやってください。

 

 

 ViewController.swiftでソースコードとして書くなら、こんな感じっす。ボタンの矩形の値は適当。

    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton(type: .system)
        button.frame = CGRect(x: 150, y: 250, width: 50, height: 44)
        button.addTarget(self, action:#selector(showAsView),
                for: .touchUpInside)
        button.setTitle("地図", for: .normal)
        self.view.addSubview(button)
    }

 

 これがストーリーボード側でInterface Builderを使って記述されたことになるわけですな。

 

 で、いよいよストーリーボードを使うメリットのド本命。Auto Layoutをストーリーボードに記述する方法っす。

ストーリーボードでのAuto Layout記述

 「ダイナミックなレイアウト」で紹介したNSLayoutConstraintの定義をストーリーボードに記述しようって話っす。

 例えば、ボタンを画面の真ん中にしたい時、ソースコードでAuto Layoutを使う場合は、作成したボタン(button)に対して次のような記述が必要になるわけですが…

 こいつをストーリーボードで記述してみます。

                ・・・
        button.setTitle("地図", for: .normal)
        self.view.addSubview(button)
        //  Auto Layout機能追加
        button.translatesAutoresizingMaskIntoConstraints = false
        var constraints = [NSLayoutConstraint]()
        //  横の制約を作成しconstraintsに追加する
        let h_constraint = NSLayoutConstraint(
            item:button, attribute:.centerX,
            relatedBy:.equal,
            toItem:self.view, attribute:.centerX,
            multiplier:1, constant:0)
        constraints.append(h_constraint)
        //  縦の制約を作成しconstraintsに追加する
        let v_constraint = NSLayoutConstraint(
            item:button, attribute:.centerY,
            relatedBy:.equal,
            toItem:self.view, attribute:.centerY,
            multiplier:1, constant:0)
        constraints.append(v_constraint)
        //  制約群を有効にする
        NSLayoutConstraint.activate(constraints)

 

注意)こんな風にダイレクトに記述する方法の他にVisual Format Languageて記述法を使う方法もあります。こっちの方が記述が簡略化できるけど、さらなる文法覚える必要があります。興味がある人はAuto Layout Guideを読んでみましょう。Advanced Auto LayoutのProgrammatically Creating ConstraintsやAppendexで紹介されてます。

 

 てなわけで、さっそく実行。

 まず、canvas画面のボタンが小さすぎて操作しにくい場合は、適度なサイズにcanvasビューをズームしときましょう。

 

 

 でもって、controlキーを押しながらcanvasビュー上のボタンをクリックし、そのままドラッグして線を引っ張り出します。

 

 

 で、この線をドラッグでボタンが置かれてる親ビュー(つまりボタンの枠外)まで伸ばしたところでドロップ。

 これでボタン(ドラッグ元)と親ビュー(ドロップ先)との位置関係制約(NSLayoutConstraint)を作りたいんだってことになり、んじゃ、どういう拘束なんだよとポップアップメニューが出るんでCenter Horizontally in Safe Area項目(親ビュー側の水平線中央とボタンの水平線中央を合わせる)を選びます。

 ちなみにSafe Areaってのは最近導入された領域で、オブジェクトが占める画面領域のうち、ユーザーの操作に支障が出そうにない領域を指します。最近のiOSは画面端からのフリップでいろいろシステム側が動作する(コントロールパネル出したりとか)ので、そういった画面端なんかを含まない領域を指します。

 

 

 とにかくこれで横位置の設定が完了。

 で、これだけだと横位置しか設定されてないから、拘束しきれないよ〜と注意されるので、再びボタン上でcontrolキー+クリックして線引っ張り出して、親ビューの上で放してメニューからCenter Vertically in Safe Area項目を選んでください。

 

 

注意)shiftキー押しながらクリックで、複数の項目の選択を追加したり解除したりできます。この場合、メニューを閉じるときはenterキーを使う。そのまま閉じちゃうとキャンセル扱いになる。
 

 これで中央位置への拘束の設定は完了。

 だけど、元々のボタンの位置が画面中央でなければワーニングマーク(注意だけど致命的じゃない)が出ます。

 

 

 これは「中央位置への拘束が指定されたけど、いまのボタンの位置って中央じゃないっすよ」って注意。この場合、選びうる選択肢としては

 

 1、画面パーツの位置を、指定した制約の位置になるよう修正

 2、制約側のパラメータを、今の画面パーツの位置になるよう修正

 3、制約破棄

 

があります。今回の場合は中央配置させたいので選ぶのは1ですね。

 さっきの注意マークをクリックし、表示された問題点リストの項目横にあるワーニングマークをクリックしましょう。

 修正方法の候補リストが表示されます。

 

 一番上のUpdate framesが「画面パーツの位置を指定した制約の位置になるよう修正」っす。これで注意は消え「地図」ボタンは、どのiPhone、iPadでも画面の中央に配置されるようになる。

 Swiftで記述する場合のめんどくささが嘘のよう。

 しかも、横置きとか縦置きとか、実行するiPhoneやiPadの種類も選べて確認できるようになってもいます。ビバ、ストーリーボード!

 

 

 ちなみに拘束(NSLayoutConstraint)はoutlineビューでは項目、canvasビューでは拘束線として表示されます。Tap Gesture Recognizerの時と同じように選択、deleteキーで削除したりもできる。選択してAttributesインスペクタで拘束パラメータを変更ってなこともできますよ。

 

 

 いろいろ試してみてちょ。

 

サンプル:

 http://tetera.jp/xcc/book-sample/storyboard-4.zip

 

 シミュレータで実行でもよろしあるよ。

リブートキャンプ by Swift 目次

 

 

 

 

 まさかの癌宣告で前回から1か月以上あいちゃったけど、予告通りストーリーボードの話。

 

 

 ちなみに抗がん剤治療の副作用で、冷たいものに触ると指先がビリビリします。なので部屋を暖かくしてからMacBook Proのトラックパッドを触るようにしてる。

 

 

 

 

 

ストーリーボード

 

 

 ストーリーボードってなんだというと、「スクリプトエンジンっぽい」で作った辞書を使った脚本オブジェクトみたいなもんです。

 

 

 あの時は、アプリで表示させるビューやその動作を辞書に脚本として記述して、それを読み出して実行するプログラムを作ったわけですが、ストーリーボードも同じように、画面上にどんなビューを出したり、タップを通知したりということを記述できたりします。

 

 

 で、こいつはSwiftのプログラムコードのように、テキストエディタを使って記述するのではなく、Interface BuilderというXcodeに組み込まれた専用のエディタを使って記述するようになってます。

 

 

 でもって、その記述したものをファイルとして保存して任意のタイミングで読み込んで利用する。

 

 

 

 

 

.storyboardファイル

 

 

 それが.storyboardって拡張子のファイルなんだけど、実はXcodeのiPhoneアプリ用プロジェクトのテンプレートは全て、この.storyboardファイルを利用するようになってます。

 

 

 それがこのMain.storyboardってファイル。これまで作ったプロジェクトにも入ってて、ナビゲーションエリアのMain.storyboardを選ぶと表示されます。

 

 

 

 

 

 

 

 

 

 

サンプル:

 

 

http://tetera.jp/xcc/book-sample/storyboard.zip

 

 

 

 

 

 エディタエリアに表示されてる画面はInterface Builderのもので、左の領域をoutlineビュー、右の領域をcanvasビューと呼びます。

 

 

 outlineでストーリーボードに記述されているオブジェクトの一覧を階層構造で見せ、canvasで、それらのオブジェクトが実際のiPhoneの画面をどう表示するかを見せてるわけですよ。

 

 

 でもって、どっちのビューでもオブジェクト単位で選択ができるようになってて、互いに連動してハイライトされるようになってもいます。

 

 

 

 

 

 

 

 

 

 

 

 

 

 で、そのまた右のユーティリティエリアでは、選択されたオブジェクトの情報が表示されるという具合。

 

 

 

 

 

 

 



 んじゃ、そのMain.storyboard様には何が記述されてるかというと、ViewControllerオブジェクトの作成なんですな。

 そういうわけで、今回のプロジェクトのMain.storyboardを選んだエディタエリアには、ViewControllerオブジェクトと、そいつが持つUIView(self.view)が表示されていることになります。

 

 

 本当にそうなのか疑う人はoutlineでView Controller項目を選んで、ユーティリティエリアでIdentityインスペクタ画面を表示させてみましょう。

 

 

 classのところがViewControllerってなってることが確認できるでしょう。

 

 

 

 

 

 

 

 

 

 

 以前「こんにちは世界」で触れ、これまで説明していなかった

 

 

 

 

 

 このアプリは、起動するとViewControllerオブジェクトが1つ用意されるようになってるんですよ

 

 

 

 

 

 に対して当然湧き上がるであろう疑問、「じゃあ、なんで、アプリは起動するとViewControllerオブジェクトを用意するのか?」の仕組みがこれです。

 

 

 

 

 

 答え:Main.storyboardでViewControllerを作れって指定されてるから。

 

 

 

 

 

 でもって、プロジェクトでアプリ起動時にMain.storyboard読み込んで使えって指定してるから。

 

 

 ちなみに、どこでMain.storyboardが利用されるように指定されてるかというと

 

 

 ここ↓

 

 

 

 

 

 

 

 

 

 

 ここに指定された名前と拡張子.storyboardを組み合わせたファイルが、アプリ起動時に読み込まれて利用されるようになっている。

 

 

 今回ならMain.storyboardファイルがアプリ起動時に読み込まれて利用されるわけですよ。

 

 

 なので、例えば、このMain.storyboardでViewControllerじゃなく地図画面用であるMapViewControllerを作るように記述すれば、アプリ起動時に地図画面を表示するようにだってできます。

 

 

 そこらへんは追い追いやるとして、とりあえずは手馴しとしてViewController.swiftのviewDidLoadメソッドで記述した、タップの見張りと通知なんかをMain.storyboardで記述してみることにしましょう。

 

 

 ↓こいつね。

 

 

 

class ViewController: UIViewController,

    ・・・



    override func viewDidLoad() {

        super.viewDidLoad()

        self.view.addGestureRecognizer(UITapGestureRecognizer(

           target: self, action: #selector(showAsView)))

    }



 

 

 

 UITapGestureRecognizerの作成とself.viewへの登録、そしてtargetとactionの設定をストーリーボードで記述するわけです。

 

 

 

 

 まずはUITapGestureRecognizerの作成とself.viewへの登録から。

 

 

 

 

 どうやるかというと、ユーティリティエリア下にあるライブラリエリアに表示されてるTap Gesture Recognizerという項目を、エディタエリアのcanvasビューのViewにドラッグ&ドロップしてやります。

 

 

 

 

 

 

 

 

 

 1、ライブラリエリアでObjectライブラリを選択してオブジェクト群を表示させる。

 

 

 

 

 2、表示された項目からTap Gesture Recognizer項目を探し、これをcanvasビュー上のViewにドラッグ&ドロップする。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 Viewはoutlineの階層を見ると察しがつくと思うけど、ViewControllerの持ってるUIView、つまりself.viewを意味してます。これで

 

 

 

 

 

 

 

 

 

  self.view.addGestureRecognizer(UITapGestureRecognizer()))

 

 

 

 

 

 

 

 

 

と同じことを記述したことになるんですな。

 

 

 

 

 ちなみに、objectライブラリのTap Gesture Recognizer項をダブルクリックすると、どういうクラスのオブジェクトかわかるようになってます。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 で、これでUITapGestureRecognizer作ってself.viewに登録するまではできたわけですが、これだけだとtarget: self, action: #selector(showAsView)部分が記述できてないわけですよ。

 

 

 

 

 これをどうするか?

 

 

 

 

 ↓これね。

 

 

 

 

 

 

 

 

 

 

        self.view.addGestureRecognizer(UITapGestureRecognizer(

           target: self, action: #selector(showAsView)))

 

 

 

 

 まず必要なのは、ViewController.swiftにテキストエディタを使って記述したshowAsViewメソッドを、ストーリーボードを記述するInterface Builderに認識させること。

 

 

 

 

 

 

 

 

 

@IBAction

 

 

 

 

 そのために一度、ナビゲーションエリアでViewController.swiftを選び、テキストエディタでViewController.swiftのshowAsViewメソッドの前についてる@objc@IBActionに書き換えます。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

class ViewController: UIViewController,

   ・・・

    @objc func showAsView() {

 

 

 

 

 

 ↓
 

class ViewController: UIViewController,

   ・・・

    @IBAction func showAsView() {

 

 

 

 

 

 

 

 

 

 この@IBActionこそが、Interface Builderに対して、このメソッドはターゲットアクションデザインパターンのアクションとして指定可能なメソッドだよって知らせるキーワードなわけですな。ちなみに@IBActionのついたメソッドは@objc指定であることも保証します。

 

 

 

 

 ターゲットアクションデザインパターンや@objcが何かわからん人は「タップしてドン」と前回の「そして委譲へ」を読みましょう。

 

 

 

 

 書き直したら再びナビゲーションエリアのMain.storyboardを選び、outlineビューのView Controller項目を選択し、ユーティリティエリアのConnectionsインスペクタを表示させてみる。

 

 

 

 

 そうすると下の方のRecieved ActionsのグループにshowAsViewの項目が出現するんですな。これがInterface BuilderがshowAsViewメソッドをターゲットアクションデザインパターンのアクションとして指定可能なメソッドとして認識した証拠。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 で、これが出ると、このshowAsViewの項目の右にある◎からドラッグで線を引き出して、outlineビューのTap Gesture Recognizer項目に繋ぐことができるようになります。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 でもって、上の図のようになったところでマウスのボタンを放すと、これでUITapGestureRecognizerオブジェクトのtargetとしてViewControllerが、actionとしてshowAsViewメソッドが設定されることになるわけです。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 つまり、これで

 

 

 

 

 

 

 

 

 

 

        self.view.addGestureRecognizer(UITapGestureRecognizer(

          target: self, action: #selector(showAsView)))

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 と同じことが、ストーリーボードに記述できたことになるんですな。

 

 

 

 

 なので、ViewController.swiftのviewDidLoadメソッドで記述したUITapGestureRecognizerオブジェクトの作成とself.viewへの登録は不要になりました。そのままだとストーリーボード、viewDidLoadメソッドの両方でUITapGestureRecognizerオブジェクトを作っちゃってself.viewへ多重登録することになるんで、viewDidLoadメソッド側は消しちゃってください。

 

 

 

 

 ↓こいつね。

 

 

 

 

 

class ViewController: UIViewController,

    ・・・



    override func viewDidLoad() {

        super.viewDidLoad()

        self.view.addGestureRecognizer(UITapGestureRecognizer(

           target: self, action: #selector(showAsView)))

    }



 

 

 

 

サンプル:

 

 

 

 

http://tetera.jp/xcc/book-sample/storyboard-2.zip

 

 

 

 

 

 

 

 

 

 消したらRun!

 

 

 

 

 viewDidLoadメソッドの処理が無いのに、ちゃんと画面タップで地図画面が表示されるはず。

 

 

 

 

 グレート。

 

 

 

 

 

 

 

 

 

 というか、ぶっちゃけ、これくらいならviewDidLoadメソッドにテキストエディタでプログラムコード直接記述する方がよっぽど早いんすけどね。

 

 

 

 

 

 

 

 

 

 違うから!

 

 

 

 

 本来、ストーリーボードを使うメリットは、View上にボタンといったサブビューをAuto Layout使って配置する時に出てくるんだから。

 

 

 

 

 

 

 

 

 

 というわけで、次回は、ストーリーボード上でボタンを配置してタップしたら地図画面が表示されるようにしてみる。