0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Closureを使ってスマートで再利用可能なモーダルを作ろう計画

Last updated at Posted at 2020-05-08

こんにちは。つまようじ職人という名前でやらせていただいております。この記事が初投稿です。


今回ですが、SwiftにおけるClosureを用いることによって、様々な場面で再利用できるモーダルのViewController
(以下, VC)を作成していこうという内容です。

文で書いてもわかりにくいと思いますので、少しずつ画像などを使いながらやっていきたいと思います。

また、この例のサンプルのgithubはこちらになります。
https://github.com/tsumayoji1102/practice_closure

やろうと思ったきっかけ


いろいろなアプリを見たりしていましたが、このようなデザイン性の実装がしてみたいと感じました。

これはYahoo乗り換え案内アプリの時間を設定する際に登場する画面です。


特徴として、

・小さくモーダル表示される
・背景が少し暗くなる
・選択した値(画像の中では時間)をもとの画面に返却している

といった画面を作成したいと思ったということです。

このような画面を実装していくに当たって得られた収穫は、

・TransitioningViewControllerDelegateの実装
・VCのモーダル表示(modalpresentationstyle = .custom

ですが、調べても出てこなかった(というよりもちゃんと実装された例がなかった)のが、

「クロージャー処理」の実装でした。

選択した値を疎結合に返却する方法が分からずに、いろいろ調べた結果クロージャがあることを知りました。

しかし調べていってもこのような実装をしている例が見つかりませんでした。

こんな画面を実装したいと思っている人はあまり多くないのかな? という疑問を持ったのですが、
自分自身どうしても実装したくてずっと調べていました。

最終的には試行錯誤でたどり着いてしまいましたが(笑)

もし同じようなことを考えてらっしゃる方がいらっしゃいましたら、参考になれば幸いです。

クロージャー処理の実装


今回は、クロージャに焦点をあてて書きますので、transition(背景が暗くなるアニメーション)など気になる方はコードを参照いただければ幸いです。

以下のような画面(4つのUIButton)を設置し、それぞれで違う値が返せるようにしました。

ボタンを押下した際に、モーダルが表示され、トランジションによって背景が暗くなります。
(トランジションが存在する理由は、そのまま遷移するとViewController特有のアニメーションが動作されてしまうため。)

そして、それぞれで値の選択ができるようになっています。


選択してOKボタンをタップすると、モーダルが閉じ、値を返却して元のボタンに反映されるようになっています。


GIFだとこんなイメージになります。

仕組み


今回、ボタンが設置されているViewをTableViewController、モーダルのViewをModalViewControllerと名付けました。
(Transitionの実装は、PresentationControllerとしています。)

まずModalViewControllerでは、クロージャーを設定しておきます。
また、ピッカーに使うリストを保持しておくための配列も用意します。

ModalViewController.swift
import UIKit

class ModalViewController: UIViewController {

    @IBOutlet weak var modalView: UITableView!
    
    // ここにリストを収める
    var list: Array<String>!
    
    // クロージャー
    var closure: ((Int) -> Void)!
    
    var pickerView: UIPickerView! // ピッカー
    var OKButton:   UIButton!     // OKボタン

    override func viewDidLoad() {
      super.viewDidLoad()
       

クロージャーの意味としては、Int型の値を引数として返却するので、それをVoid型の関数の中で使用してくれたらよいですよ、という意味です。

正直ここがわかりにくいポイントですが、Void型の関数というのはすなわちメソッドです。

これはモーダルのひらき元のViewControllerで自由に設定することができるということです。

その際にInt型の返却値を利用してくれれば幸いだ、というスタンスであるということです。

そして、ModalViewControllerでOKボタンを押した際の処理では、クロージャに値を返却するようにすれば大丈夫です。

ModalViewController.swift
  // OKボタンタップ後
    @objc func tapButton(_ sender: UIButton){
        
        // クロージャで選択したもののインデックス番号を返却
        closure(pickerView.selectedRow(inComponent: 0))
        self.dismiss(animated: true, completion: nil)
    }

こうすることで、クロージャーに値をいれてモーダルを閉じることができます。

そして、もとの画面でそのクロージャのメソッドを設定します。

少しわかりにくいかもしれませんが、遷移元の画面でボタンを押した際に動作するメソッド内に記述していきます。

それぞれのボタンでタグの値を設定し、それによって分岐するように作成しております。

TableViewController
// ボタンタップ時(遷移元)
    @objc func tapButton(_ sender: UIButton){
        
        // モーダル作成
        let ModalVC = self.storyboard?.instantiateViewController(identifier: "ModalViewController") as? ModalViewController
        // トランジションの実装
        ModalVC?.transitioningDelegate = self
        ModalVC?.modalPresentationStyle = .custom
        
        // タグで分岐
        switch sender.tag {
        case ButtonTag.name.rawValue:
            // リスト、クロージャーを設定
            let list = ["田中", "鈴木", "山本", "片山", "剛力"]
            ModalVC?.list = list
            ModalVC?.closure = { index in
                let name = list[index]
                sender.setTitle("名前は\(name)", for: .normal)
            }
            break
        case ButtonTag.sex.rawValue:
            // リスト、クロージャーを設定
            let list = ["男性", "女性"]
            ModalVC?.list = list
            ModalVC?.closure = { index in
                let sex = list[index]
                sender.setTitle("性別は\(sex)", for: .normal)
            }
            break
        case ButtonTag.hobby.rawValue:
            // リスト、クロージャーを設定
            let list = ["釣り", "釣り以外"]
            ModalVC?.list = list
            ModalVC?.closure = { index in
                let sex = list[index]
                sender.setTitle("趣味は\(sex)", for: .normal)
            }
            break
        case ButtonTag.cake.rawValue:
            // リスト、クロージャーを設定
            let list = ["ショートケーキ", "チョコケーキ", "モンブラン"]
            ModalVC?.list = list
            ModalVC?.closure = { index in
                let cake = list[index]
                sender.setTitle("好きなケーキは\(cake)", for: .normal)
            }
            break
        default:
            break
        }
        
        self.present(ModalVC!, animated: true, completion: nil)
        
    }

それぞれのケースで、モーダルのlistに選択リストを代入し、クロージャの返却値をインデックスとして取得し、
選択した文字列をボタンに代入しています。

index in と記載されているところがポイントで、indexは返ってくる引数であり、名前の付け方は自由です。
indexでなくても構いません。

そして、inの後ろにVoidのメソッドを設定していきます。とはいっても、普通にやりたい処理を書いていくだけで良いです。

クロージャは順番が難しい

今回はモーダルでの実装から逆算するように説明しましたが、コードの流れ方としては

①TableViewControllerより、モーダルのインスタンスを作成
②モーダルのクロージャのVoidを、引数を利用しながら設定(TableViewController内でどう使いたいかをイメージ)
③モーダルに画面遷移、そして閉じる時に値が返却される
④TableViewControllerに値が返却され、そしてクロージャに設定したVoidが動く

となっております。設定したクロージャのメソッドはモーダルが閉じたときに動作する、という認識を持って作成すると整理しやすいかと思います。

この辺りを理解するのに自分自身も多くの時間がかかりました。

クロージャーを使うメリット

僕が特に良いなと感じていることは、クロージャのVoidの設定が自由であることから、モーダル自体はかなり疎結合になることです

iOSアプリの実装においてしんどいなと思うポイントは、ViewController同士で値の受け渡しをさせすぎると複雑になり、保守や新規実装もめんどくさければテストも難しくなります。

しかし、このサンプルをとってもクロージャを設定することで、代入するリストはその都度自由に設定できますし、返却する値も統一して実装することができます。

ピッカーでなくともこのようにして値の返却ができるようになれば、かなり保守性の高いコードをかけること間違いなしです。

まとめ、感想

私自身クロージャーを理解する努力を怠れば、4つのボタンに対して4つのモーダルを作成し設定してしまっていたのではないかと感じています。

少しでも僕自身の苦しみが他の誰かへの貢献につながれば幸いです。最後まで読んでくださりありがとうございました。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?