69499


SwiftでUITextFieldの値が変化したときに処理を行いたいです。  
以下のように書いたところ、TextFieldに入力を行うとアプリがクラッシュしてしまいす。

[code]

    @IBOutlet weak var weightTextField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        weightTextField.delegate = self
        weightTextField.addTarget(self, action: Selector(("weightTextFieldDidChange:")),for: UIControl.Event.editingChanged)
    }
    
    func weightTextFieldDidChange(textFiled: UITextField) {
        let text = weightTextField.text ?? "test"
        print(text)
    }
    
[/code]

エラー内容は、以下のようになっています。

[code]

    2020-08-12 17:14:52.985860+0900 TestApp[10347:1326518] -[TestApp.MotionWeightViewController weightTextFieldDidChange:]: unrecognized selector sent to instance 0x13d31ad30
    2020-08-12 17:14:52.988600+0900 TestApp[10347:1326518] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TestApp.MotionWeightViewController weightTextFieldDidChange:]: unrecognized selector sent to instance 0x13d31ad30'
    *** First throw call stack:
    (0x186408300 0x18611cc1c 0x186306a90 0x18a580dfc 0x18640ca60 0x18640ed60 0x18a5526c0 0x189f3ab30 0x189f3ae98 0x18a8483bc 0x18a82d2d4 0x18a859910 0x18a85c0d4 0x18a85d9cc 0x18a831208 0x18a84e8d4 0x18a369678 0x18a362f1c 0x18a361c88 0x18a393b44 0x18a3924ac 0x186800d6c 0x186383af4 0x186383a48 0x186383198 0x18637df38 0x18637d8f4 0x190794604 0x18a551358 0x104dc6f00 0x1861f92dc)
    libc++abi.dylib: terminating with uncaught exception of type NSException
    (lldb) 
    
[/code]

エラーの原因は何でしょうか？  
Doneを実行せずにUITextFieldの変化内容を取得することはできますでしょうか？

XCodeのバージョンは、Version 11.5を使用しています。  
SwiftのバージョンはSwift5です


CC BY-SA 4.0
2020-08-12T08:43:00.430
69480
2020-08-12T18:38:33.707
2020-08-12T12:59:42.540
36446
36446
question
swift
SwiftでUITextFieldの値が変化したときに処理を行いたい


UIKitにおいて、オブジェクト間でイベントを伝送するメカニズムには、いくつかの種類があります。代表的なものをふたつ挙げると、ひとつは **Target-
Action** で、もうひとつは **Delegate** です（注）。

Target-ActionとDelegateは、別個のメカニズムであって、関連はありません。ご提示のコードの

[code]

    weightTextField.delegate = self
    
[/code]

と、

[code]

    weightTextField.addTarget(self, action: Selector(("weightTextFieldDidChange:")),for: UIControl.Event.editingChanged)
    
[/code]

の行は、なんの連携もしていません。  
Target-Actionを利用するか、Delegateを利用するか、どちらか一方にすべきですが、Delegateを選ぶのが主流ではないかと思います。

[code]

    import UIKit
    
    class ViewController: UIViewController, UITextFieldDelegate {
    // プロトコルUITextFieldDelegateを付け加えることを、お忘れなく。
    
        @IBOutlet weak var textField: UITextField!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            textField.delegate = self //Delegate移譲先を指定。
        }
    
        func textFieldDidChangeSelection(_ textField: UITextField) {
            print("Changed")
        }
        // Delegateメソッドは、他にも多数ありますので、適宜ご選択ください。
    }
    
[/code]

Delegate実装の最小限のコードだけ書きました。  
Delegateメソッドの種類はこちらをご覧ください。

[Protocol
UITextFieldDelegate](https://developer.apple.com/documentation/uikit/uitextfielddelegate)

* * *

注：あくまでもUIKitにおける……であり、Swiftのメカニズムではありません。Swiftは、言語レベルで、Target-
Actionも、Delegateもサポートしていません。ではSwiftは、どうやってオブジェクト間の伝送をするのかというのは、SwiftUIを詳細にお調べくださると、概要がわかるでしょう。


CC BY-SA 4.0
2020-08-12T14:53:26.690
69499
2020-08-12T15:07:35.207
2020-08-12T15:07:35.207
18540
18540
69480
answer


まず最初に、参考にされた記事が古すぎます。2016年と言うとSwift言語が登場してまだ間もない頃で、言語仕様そのものも、UIKitなどのObjective-
C製APIとの連携方法もころころ変わっていた頃です。ネット上でSwiftの記事を参考にされる場合には、記事の日付も気にしないと、iOSでのプログラムの書き方よりも、Swift言語そのものの歴史を学ぶ方に時間を取られることになります。

また、そんな古いコードを「XCodeの誘導に従って」書き換えてもうまく動くことにはならない場合の方が多いです。

* * *

次に、 **UITextFieldの値が変化したときに処理を行いたい** についてですが、iOSでUIイベントを受け取る方法については、

  * Target-actionパターンで特定のactionイベントを拾う
  * delegateパターンを使う
  * 通知を使う

などと言ったやり方があります。イベントの種類によっては、複数の方法が使える場合もありますが、
**今回の「UITextFieldの値が変化したときに処理を行いたい」については、「Target-
actionパターンで`UIControl.Event.editingChanged`のactionイベントを拾う」と言うやり方で問題ありません**。

* * *

では、なぜ実行時エラーが出てしまうのかと言うと、`Selector(("weightTextFieldDidChange:"))`という書き方がまずいからです。

エラー内容の`'-[TestApp.MotionWeightViewController weightTextFieldDidChange:]:
unrecognized selector sent to instance
0x13d31ad30'`と言うのは、「`MotionWeightViewController`というクラスには、`weightTextFieldDidChange:`なんてメソッドは存在しない」ということを表しています。

`weightTextFieldDidChange:`と言うメソッド名も、`unrecognized selector sent to
instance`なんて言い回しもObjective-Cで使われるものです。Apple製のフレームワークでは、内部的にObjective-
Cの言語機能を利用している部分が多く、エラーメッセージなんかを読み解くにはObjective-C言語の知識が要求されます。

で、そんな面倒なことになるのも **`Selector(...)`なんて、現在は使うべきではないとされている書き方を使っているせい** です。

# `addTarget`などセレクタが必要となる場面では必ず`#selector`構文を使ってください

今回の例で言うと、上記の`Selector(...)`を全部削除して、代わりにその位置に`#selecto(self.`と入力してみて下さい。

(`self.`と打つのは、target(最初の引数)に`self`を指定したから。)

[code]

            weightTextField.addTarget(self, action: #selector(self.), for: .editingChanged)
    
[/code]

`self.`の`.`を入力した時点でXcodeが候補表示を行ってくれるはずですので、その中から自分の呼びたいメソッド名を選択してやります。

[code]

            weightTextField.addTarget(self, action: #selector(self.weightTextFieldDidChange(textFiled:)), for: .editingChanged)
    
[/code]

で、この状態になると、Xcodeが修正候補付きのエラーメッセージ(中白点付きの赤丸)を表示するはずです。

[code]

    Argument of '#selector' refers to instance method 'weightTextFieldDidChange(textFiled:)' that is not exposed to Objective-C
    
    Add '@objc' to expose this instance method to Objective-C [Fix]
    
[/code]

「`'weightTextFieldDidChange(textFiled:)'`がObjective-
C用のメソッドにならない」と言うことを言っています。それは困るのでここではFixを選択すると、メソッド名の前に`@objc`が付加されて以下のような状態になります。

[code]

        @IBOutlet weak var weightTextField: UITextField!
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            weightTextField.delegate = self
            weightTextField.addTarget(self, action: #selector(self.weightTextFieldDidChange(textFiled:)), for: .editingChanged)
        }
    
        @objc func weightTextFieldDidChange(textFiled: UITextField) {
            let text = weightTextField.text ?? "test"
            print(text)
        }
    
[/code]

ここまででコンパイルエラーもなくなるはずなので、実行してみて下さい。`weightTextField`の中身を変更するたびに`print(text)`が実行されているのを確認することができるはずです。

* * *

  * `#selector(self.weightTextFieldDidChange(textFiled:))`の、`self.`は省略することができます。

  * 同じクラス内に`weightTextFieldDidChange(`で始まるメソッドがなければ、`(textFiled:)`の部分も省略することができます。

つまり、両方合わせて、`#selector(weightTextFieldDidChange)`と書いても動作します。このような省略記法を使っているネット記事もよくあるので、同じことを表していると言うのがわかるようにしておいて下さい。

* * *

実際には`UITextFieldDelegate`のメソッドを使用していないのなら、`weightTextField.delegate =
self`の行も不要です。

(今は使用していなくても、いずれ必要になりそうなら残しておいても良いですが。)

なお、 **`UITextFieldDelegate`のメソッド`textFieldDidChangeSelection(_:)`はtext
fieldの「選択範囲(Selection)」の変化を拾うもので、編集中のテキストの変化を拾うものではありません**。iOSでは幅が0の「選択範囲」がカーソル位置を表すので、1文字ずつ入力している場合は同じことのように見えるかもしれませんが、カーソル位置を移動させたり、範囲選択などを行うと動作が異なることが分かります。


CC BY-SA 4.0
2020-08-12T18:38:33.707
69502
2020-08-12T18:38:33.707
13972
69480
answer