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

 

 てなわけで、UIViewを派生させたMapViewクラスを定義しておいて、画面タップされたら、ViewControllerのself.viewの上に表示させてるのが前々回のサンプルってわけなんですが

 

 ↓こいつね

サンプル:

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

 

 これだとMapViewオブジェクト作るとき、必ず"map"って名前の画像がいるよね。

override init(frame: CGRect) {
        super.init(frame:frame)
        let subview = UIImageView(image:UIImage(named:"map"))
         ・・・

 画像を使う使わないや、どの画像を使うかは、使う側で好きに変更できる方が汎用性があっていいよね〜、て話になるわけですよ。

 そうしておくとMapViewに別のアプリで使いまわせるかもしれない。

 それ以前に、使う画像の用意の仕方がわからん人は「スクールカーストとかヒエラルキとか」を読みなさい。

 

 考え方自体は正しいし、技術的にも難しいことじゃなくて、subviewを外から触れるようにプロパティにしちゃうだけで解決しちゃう問題なんだけど…

    let subview = UIImageView() ←これで解決
    override init(frame: CGRect) {
        super.init(frame:frame)
        let subview = UIImageView(image:UIImage(named:"map"))
        subview.frame = self.bounds ← ま、それと画像を設定しないので、矩形を
                   明示的に設定する必要もあるわけだが
        subview.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    ↑ 親UIViewのリサイズに合わせて自分もリサイズする指定もしておく
     この2点は元々の処理でもつけておくべきだった
         ・・・

autoresizingMaskプロパティの意味がわからん人は「重箱の隅をつつくようにネチネチと進めてみる」を読みましょう。

 

 いやしかし、そうするとViewController側でsubviewのimageに画像設定する必要あるよねって話になるわけで…

    @objc func showAsView() {
        let mapView = MapView(frame:self.view.bounds)
        mapView.subview.image = UIImage(named:"map") ←追加作業
        mapView.frame.origin.y = mapView.frame.maxY

 いや、いいんすけどね。でもViewController側で作業増やしたくないってことでMapViewクラス用意したんじゃん、ブツブツ…

 

 という葛藤が発生するわけですな。

 実際の凝ったゲームだとUIImageを1個設定するくらいじゃ済まなんだろうし…

class MapView: UIView {
    override init(frame: CGRect) {
        super.init(frame:frame)
                ・  ← MapView側でやらせようとしてたいろいろな作業が…
                ・
                ・
 
class ViewController: UIViewController {
   ・・・
    @objc func showAsView() {
        let mapView = MapView(frame:self.view.bounds)
    ↓ ViewController側でやることになっちゃう…、って、それじゃ元の木阿弥
        mapView.subview.image = UIImage(named:"map")
                ・  
                ・
                ・

 MapViewに汎用性持たせつつ、ViewController側での作業は増やしたくないとか、どんだけわがまま…

 

 これに対しAppleが提案してるのが「UIViewControllerとUIViewのセットもう一個用意して、UIViewControllerごと切り替えちゃえば良くってよ」ていうもんです。

 

 

 

 例えばMapViewを設定する側のUIViewControllerを、MapViewControllerとして用意して、そこのviewDidLoadで、MapViewは設定(subviewのimageとか)しちゃう。

class MapViewController: UIViewController {
 
    override func viewDidLoad() {
        super.viewDidLoad()
        let mapView = MapView(frame:self.view.bounds)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.view.addSubview(mapView)
        ・・・

 こうすると、MapViewControllerクラスを余分に定義することになるけど、そのviewDidLoadでやってることは、結局MapViewクラス内でやる予定の、今回のアプリに依存する部分を分離してるだけなんで、作業的にはそう変わるもんじゃない。

 

 

 で、アプリに依存する部分を分離した結果、MapViewは汎用性を持つことになって、別のアプリでも使える可能性が出てくる。別のアプリではやっぱり同じようにそのアプリ専用のMapViewControllerクラスを作ることにはなるんだけど、MapView部分は使いまわせる。

 

 

 こういった使い回しができることは、MapViewがスクロール可能になったり、ズーム可能になったりと多機能になっていくほど、ありがたみが出てくるわけです。

 

 とまあ、そういった理由でAppleはUIViewControllerごとの切り替えを推奨してて、そのための便利な機能が、UIViewControllerにいろいろ提供されてもいるわけです。

 その1つがUIViewControllerをまるごろ切り替えるためのメソッド

 

 func present(_ viewControllerToPresent: UIViewController, 

  animated flag: Bool, completion: (() -> Swift.Void)? = nil)

 

  viewControllerToPresent:切り替え先のUIViewControllerオブジェクトを指定

  animated:切り替え時にアニメーションするならtrue

  completion:切り替えアニメーション完了直後に何かしたければクロージャを設定

  クロージャがわからん人は「タップしてドン」を読みなさい。

 

 completion部分は何も指定しないならnilを指定なんだけど、その場合、省略もできて、例えばViewControllerでMapViewController作って切り替えるならこんな感じ

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

 でもって、切り替えられたUIViewControllerから、切り替え元に戻るためのメソッド

 

 func dismiss(animated flag: Bool, 

  completion: (() -> Swift.Void)? = nil)

 

  animated:切り替え時にアニメーションするならtrue

  completion:切り替えアニメーション完了直後に何かしたければクロージャを設定

 

なわけです。

 

で、MapViewController側ではUISwipeGestureRecognizerをMapViewからこっちに持ってきてスワイプされたらdismissメッセージを自分に送る。

class MapViewController: UIViewController {
   ・・・
    @objc func swipe() {
        self.dismiss(animated: true, completion: nil)
    }

 こんな感じでcompletion:を省略せずにnilを指定でもいい。

 ま、詳細はサンプルソース見てください。

 Git使ってMapView単独版からの変化を比較できるようにしてます。

 なんスカそれ、Gitって美味しいの?な人は「キャッチして」を読みましょう。

 

サンプル:

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

 

 ちなみに、自分でもUIViewControllerを派生させてMapViewControllerクラス定義してみようと思う人。

 MapViewController.swftをプロジェクトに登録するのにはMapView.swiftを登録したやり方を使うんだけど、UIView派生の時と違いUIViewControllerの派生ではXIBファイル作るか聞かれます。

 

 Also create XIB file

 

 てチェックボックスなんだけど、今回は作らないのでチェック外しでよろしく。XIBについてはいずれ。

 

 

ではでは。

 

AD