2023年3月26日日曜日

[SwiftUI]TextFieldで文字列ではなく、数値やnilを扱いたい(挙動の違いも検討)

はじめに

 以前SwiftUI学習のために作成したアプリ(リンク)において、使用者様から、「TextFieldに金額を入力して、その金額のお皿を追加するButtonをタップした際にTextFiledの入力値をクリアできたら良いのでは」というご指摘をいただきました!(ありがとうございます!)

 そこで対応していた際に、うん?簡単そうでうまくいかないぞ?っていう点があり、調べても調べても解決できず苦労した点をここに記します!多分不具合か仕様かと思っています!

詳細わかる方いましたらコメントください!一旦個人的な見解を述べてます!


現在の開発環境

・PC:MacBook Air M1
・Xcode:Version 14.2
・Swift :Version 5.7.2

文字列ではなく、数値を入力値として扱うには?

 TextFieldで扱う値を数値だけにするには、引数textではなく、value及びformat(formatter)を用います。
 textはString型であり、そのままでは数値やnilを許容するオプショナル型を扱えません。format: .numberを指定することで数値にフォーマットすることができます。
 入力するキーボードを指定するには.keyboardType(.numberPad)モディファイアを付与します。

ここで問題が・・・

 valueとformatterを用いていたので、Buttonタップ時にvalueにバインディングしている変数に空文字列""を代入すれば解決・・・とはいきません。
変数には数値を扱うためにInt型にしています。しかし"0"を代入すると"0"が残るので、オプショナル型を用いる必要がありますね。"0"でも良いのですが、指でTextFieldをタップして数値を入力する際に、微妙に邪魔してくれるので、消しましょう。
Buttonタップ時の処理に、@Stateを付与してTextFieldにバインディングしている変数にnilを代入する処理を追記します。
ここまでは何ら問題ありません。

 ここからが問題でした。シミュレータにて、挙動を確認したら、あら不思議。
何ということでしょう・・・TextFieldの値が、消えないではありませんか。
 しかしながら、printで出力して値を確認すると、当該変数の値は変更されています。・・・であれば、Viewが更新されていない可能性があると考え、調べても調べても情報はありません。@StateはViewとリンクしている、これはSwiftUIにおける最大のメリットと考えていましたが・・・こうも裏切られるとは・・・

解決のために

 プロジェクト上では他のコードが影響あるのでは?という懸念を振り払うため、新規プロジェクトを作成し、余計なコードは書きません。
 TextFieldとButtonだけ配置し、さあ実験。結果は・・・やはり変わりません。
そこで、TextFieldをもう一つ増やし、そちらは引数textで挙動を確認しました。
すると驚くことが判明しました。
 textにバインディングしている変数の値に””を代入するパターンはしっかりViewに反映されるのです。そう、valueだけが、nilも数値も、変数に代入した値がViewに反映されていない、ということがわかりました。もちろん変数の値は変更されています。(printで確認ずみ)
 しかし、ある条件下ではvalueの方もしっかりとViewが更新されたのです。これはさまざまな実験をする中での偶然の気付きでした。

原因は・・・?

 その条件とは、value側のTextFieldに値を入力した後、text側のTextFieldを選択して、Buttonをタップすることです。要は選択(フォーカス)状態が解除されている場合のみ、TextFieldの値が変更されたのです!text側は選択(フォーカス)状態関係なくViewが更新されるのに・・・。

 詳細な原因は判明しておりませんが、仕様としてこのような挙動がある、ということがわかりました。なので、同じように沼にハマってしまった方にも情報を共有できたらと思い、記事にしました。私自身、まだモヤモヤは残りますが、一旦挙動の違いということがわかり、悩み続けることがなくなり一安心です。
 共感や解決策等ありましたら、お気軽にコメントお願い致します!

以上!

追記

 ちなみに、引数formatではなく、formatterを用いた場合、TextFieldに値を入力しても、リターンキーを押されないと、変数に値が反映されず選択(フォーカス)状態が解除された時点で入力した値が消えます。numberPadにはエンターキーがないため、強制的に解決するしかなくなります。こちらの記事にこの挙動については記載がありました。これにもかなり悩まされました・・・

 あと、流行りのchatGPTに聞いたら、オプショナル型を扱うとViewが更新されないと教えられましたが、大嘘でした笑。

flutterについて、学んだことなど

Swiftについて学んできたため、つい比較してしまいがちです。 SwiftUIと比較して、書いてみました。 似ているようで似ていない flutterには、SwiftUIにおける「View」という概念が、「Widget」と呼ばれるものになる SwiftUIのようにWidgetの中に...