Swiftで遊ぼう! on Hatena

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

Swiftで遊ぼう! - 366 - Developing iOS Apps (Swift) Connect the UI to Code

2016年12月25日:Swift 3に対応*1

チュートリアル索引に戻る→

developer.apple.com

学ぶべきオブジェクトがリストアップされてます。

  • トーリーボード上のSceneとそのベースにあるView Controllerの関連性の説明
  • トーリーボードのUIエレメントとソースコードを繋ぐoutletとactionの作成
  • テキストフィールドに入力された結果をUIに表示する仕組み
  • クラスをプロトコールに準拠させる
  • デリゲーションパターンの理解
  • アプリケーションのデザインをするときにターゲットアクションパターンに従う

このリストは、私が1年かけてなんとか理解した内容なんです。ちょっと時間がかかり過ぎてますね。でも気にしないで頑張ります。ターゲット・アクション・パターンというのが何のことか良くわからないので、今回はここを考えながら復習します。

プロジェクト・ナビゲータからViewController.swiftを選択するか、Main.storyboardを選んでアシスタント・エディタを開くとViewControllerのコードが出てきます。

「class ViewController: UIViewController {」の下に次の文字列を加えます。

// MARK: Properties

これはコメントを意味します、「// MARK:」はカテゴリーを意味する特別なコメントになります。この説明は後で出てきます。

まず、UIエレメントとソースコードを繋ぐ説明はこのブログで何度もしていまが、「Ctrl + ドラッグ」です。

Viewに設置した上2つのオブジェクト(Label, Text Field)をViewConrollerの中、「// MARK: Properties」の直下に「Ctrl + ドラッグ」します。

f:id:yataiblue:20161225234004j:plain

Text Fieldの名前を「nameTextField」とします。

f:id:yataiblue:20161225234037j:plain

Labelは、「mealNameLabel」と名前をつけて@IBOutletとして繋げます。

// MARK: Properties
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var mealNameLabel: UILabel!

ViewControllerクラス宣言の最後の「}」の前に次のカテゴリー・コメントを加えます。

// MARK: Actions

Viewに設置しているもう一つのオブジェクトはButtonですが、ターゲット・アクション・パターンとして振る舞います。このパターンは、特別なイベントが発生すると、1つのオブジェクトから他のオブジェクトにメッセージを送りアクションを駆動させます。Buttonは、「コントロール」オブジェクトで、ユーザーからの「タップ」というイベントを受け「アクション・メソッド」をViewControllerに送信します。

Buttonから「Ctrl + ドラッグ」して// MARK: Actionsの直下に繋げますが、@IBActionにして、名前を「setDefaultLabelText」にします。この名前が「アクション・メッセージ」になり、senderを「AnyObject*2」から「UIButton」に変更します。

f:id:yataiblue:20161225234925j:plain

// MARK: Actions
@IBAction func setDefaultLabelText(_ sender: UIButton) {
}

ここにメッセージしかできていないので、アクションを記述します。簡単なアクションです。Labeに「Default Name」を加えるだけです。

// MARK: Actions
@IBAction func setDefaultLabelText(_ sender: UIButton) {
    mealNameLabel.text = "Default Text"
}

これで、ボタンを「タップ」するというイベントで「setDefaultLabelText」というアクション・メソッドを起動させて、アクションとして、ターゲットのmealNameLabelのプロパティ「text」に「Default Name」を入力するというターゲット・アクションが完成しました。

f:id:yataiblue:20161225235632j:plain

それぞれのプロパティとメソッドの頭に「黒丸」がついています。これはView上のプロトタイプと繋がっていることを示しています。ストーリーボードを使ってクラスの実体化(インスタンス化)できるということです。

コードでプロパティを加える場合、「var someProperty: String!」というように、「!」を付けて宣言した場合、必ず初期化ステップでインスタンス化する必要があります。初期化ステップでインスタンス化しないと実行時エラーになりますが、ストーリーボードから「Ctrl + ドラッグ」して「黒丸」の付いている「@IBOutlet」プロパティは初期化ステップが無くても問題ありません。

ここでランします。ボタンを押せば、ラベルは「Default Name」に変わります。

次はデリゲーションの説明ですが私は苦労して理解しました。UIViewクラスのオブジェクトは、表示機能、表示という抽象概念のみ実装しています。具体的な表示内容はデリゲーション(委任)させるのがMVCモデルです。

UITextFieldも同様です。テキストフィールドをタップしてキーボードを出現させて入力を受け入れ、Returnキーをタップすることでラベルの内容をアップデイトさせるという機能はデリゲーション・メソッドになるため、プロトコールが用意されています。

UITextFieldで用意されているデリゲーション・プロトコールは「UITextFieldDelegate」です。これをViewControllerに準拠させなけらばならないので、次のようにクラス宣言をします。

class ViewController: UIViewController, UITextFieldDelegate {

次にすべきことは、UITextFieldクラスにViewControllerのプロパティを持たせるステップです。UITextFieldDelegate型のdelegateというプロパティが用意されているので、このdelegateプロパティにUIViewController自身を設定しなければなりません(ここのステップ5です)。こうすると、UITextFieldDelegateで用意されているメソッドをViewControllerで実装して使うことができるようになります。この説明はややこしいですが、慣れるしかないでしょう。

ViewControllerの中のviewDidLoad()メソッド内で設定するのが一般的です。

override func viewDidLoad() {
    super.viewDidLoad()
        
    // Handle the text field’s user input through delegate callbacks.
    nameTextField.delegate = self
}

これでプロトコールを使ってViewControllerとUITextFieldの繋がりができました。普通なら実装しなければならない「必須(Required)メソッド」があったりしますが、UITextFieldはOptionalメソッドにはないので、ラン(Cmd + R)できます。

ランしてテキストフィールドをクリックすればキーボードが出現します。次々にキーも入力できるのですが、それだけです。キーボードを消せないし、Doneキーも働きません。

この機能は「オプション(Optional)メソッドを使って実装します。

ViewControllerクラス内の最後の「}」の前に次のカテゴリー・コメントを加えます。

// MARK: UITextFieldDelegate

ここで「//MARK: -」のカテゴリー・コメントの使い方を説明します。エディター上部にあるファイルの右橋のアイテムをクリックすると次のようなメニューが出てきます。

f:id:yataiblue:20151018143328j:plain

「//MARK: -」がついたコメントはインデックス扱いなるためコードが長くなった時に目的のコードを探しやすくなります。

閑話休題、ユーザーからイベントが発せられると、アプリはそれをfirst responderとして捉えます。UITextFieldの場合、テキストフィールドをタップしたらイベントになり、そこから続くイベント(キーを叩いて入力を続ける)の流れが続きます。これを中断させるために、first responderを消去(dismmiss)させる必要があるんです。

func textFieldShouldReturn(_ textField: UITextField) -> Bool
func textFieldDidEndEditing(_ textField: UITextField)

first responderを切るためのメソッドです。

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    return true
}

これで編集作業が終わったという流れができます。first responderが消えたときに呼ばれるメソッドを使って、ラベルの文字列を変更させます。

func textFieldDidEndEditing(_ textField: UITextField) {
    mealNameLabel.text = textField.text
}

この2つのメソッドの流れは重要です。直ぐ忘れるので何度も繰り返し覚える必要がありますね。まだまだキーボード周りの実装ができません。

次に進む→Swiftで遊ぼう! - 367 - Developing iOS Apps (Swift) Work with View Controllers - Swiftで遊ぼう! on Hatena

*1:2015年10月16日改訂:環境環境:Xcode7.1Swift2.1

*2:Objective-CからSwiftへ開発環境が移行する過程の中で生まれました。SwiftプログラマーでもObjective-Cの理解は欠かせないため理解しなければなりません。私は悩みました...