はじめに
Swift 4よりprivateで宣言されたプロパティも、extensionで分けたクラスから参照できることになったため、今回紹介するプロパティの機能はSwift 4からの機能として説明します。
本連載はアプリ開発の最初に知っておくべき基本的な事柄の説明をメインテーマとしています。内容によっては、Swift 4以前のバージョンに触れることや、Swift自体の言語仕様などには説明が及ばないこともご了承ください。本連載以上の各種情報は末尾の参考文献等を参照してください。
今回説明する具体的な内容は次の通りです。
- プロパティの値にクロージャを指定して画面をまたいだ処理を行う
- プロパティのwillSet/didSet関数を利用してUIを制御する
対象読者
本記事は、次の方を対象にしています。
- Swiftでの基本的なプログラムができる方
- Xcodeを使える方
クロージャを利用して処理を行う【Swift 4】
デリゲートは、自由に作成することができる汎用性の高い仕組みです。しかし、定義する部分と実行する部分が離れているため、いくつものデリゲートを使う場合にソースコードを追うのが手間になる場合もあります。また、利用する目的がひとつしかない場合などでは、デリゲートよりもシンプルに処理を実装したい場合もあります。このケースでは、デリゲートの代わりにクロージャを利用します。
Swift 3以降では、クロージャに関する強化された機能として次の2点が挙げられます。
- クロージャの引数にオプショナル型の変数が渡せるようになった
- ビューコントローラーのプロパティとしてクロージャの実装がサポートされた
サンプルを通して具体的な実装の例を解説します。
クロージャをプロパティとして定義する
前回のサンプルをデリゲートではなくクロージャを使って実装してみます。
前回作成した、デリゲートを利用するサンプルと同様の処理をクロージャを使って実装する場合、まずクロージャをプロパティで実装します。今回のサンプルでは、選択した項目をString型の変数で渡せるクロージャを、プロパティでアクセスできるように定義しています。
class MenuTableViewController: UITableViewController { var completeSelect: ((String?) -> Void)? ...後略...
リスト1の通り引数と処理を表す「Void」を括弧で囲ってクロージャを定義します。クロージャを利用しない場合も考えられるため、後ろに「?」をつけてnullを考慮します。
さらに、クロージャの引数もオプショナル型で定義します。引数をオプショナル型にしておくことで、空の変数が渡された際にアプリがクラッシュしてしまうことを回避できます。
クロージャの処理内容を記述する
クロージャの処理内容は、インスタンスを生成する際に記述しています。サンプルでは、MenuTableViewControllerクラスのインスタンスを生成する際にクロージャの処理内容を記述しています。
@IBAction func onPressMenu(_ sender: Any) { // Main.storyboardを取得 let storyBoard = UIStoryboard(name: "Main", bundle: nil) if let vc = storyBoard.instantiateViewController(withIdentifier:"MenuTableViewController") as? MenuTableViewController { vc.completeSelect = { [weak self] (item) in guard let word = item else { return } self.label.text = "\(word)をクロージャで選択しました!" } self.present(vc, animated: true, completion: nil) } }
具体的には、ボタンを押してモーダルでテーブルを表示する際にクロージャの内容を記述しています。サンプルのようにクロージャの中で自分自身であるselfを参照すると、自分自身で指定したクロージャの中から自分自身を参照してしまう循環参照が発生します。それを防ぐために、クロージャの先頭でselfの前にweakをつけて弱参照で自分自身を参照するようにしておきます。その後、guard文でクロージャに渡された値が空だった場合、処理を抜けるようにします。最後にクロージャに渡された値を画面に表示します。
クロージャを実行する
ここまでの作業でクロージャを利用するための準備はできました。最後にセルの選択が行われた場合に、クロージャを実行する処理を記述します。
// テーブルのセル選択時の処理 override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // IndexPathからセルを取得 let cell = tableView.cellForRow(at: indexPath) if let text = cell?.textLabel?.text { self.completeSelect?(text) // ------(1) } // モーダルを閉じる self.dismiss(animated: true, completion: nil) }
デリゲートでの処理と同様に、選択されたセルの項目名を取得します。取得した項目名を引数にしてクロージャを実行します(1)。
項目を選択した際に、選択した項目が画面に表示されることが確認できます。