Open4

SwiftUI: iOS 17でTextFieldのクリアができなくなっている問題

kabeyakabeya

iOS 16でうまく動く以下のコードが、iOS 17になってから機能しなくなっています。
(iOS 17.1.2の実機、iOS 17.2のシミュレータで確認。iOS 16は16.7.2の実機で確認)

struct TextFieldWithClearButton: View {
    var title: LocalizedStringKey
    @Binding var text: String
    var prompt: LocalizedStringKey
    
    var body: some View {
        HStack {
            TextField(title, text: $text, prompt: Text(prompt))
                .textFieldStyle(.roundedBorder)
                .padding(.horizontal, 10)
            
            Button(action: {
                text = ""
            }, label: {
                Image(systemName: "xmark.circle.fill")
            })
            .buttonStyle(.borderless)
            .foregroundColor(.gray)
            .padding(.trailing, 10)
        }
    }
}

やっていることは、

  • TextFieldの横に×ボタンをつける
  • ×ボタンがタップされたら、TextFieldtext:の変数をクリアする

というだけなんですが、iOS 17だと、日本語入力後に×ボタンをタップしてクリアしても、TextFieldの描画が更新されません。変数自体はクリアされているので、変数の内容を元に別のビューの更新がかかる場合、そっちは更新されます。

また、入力された日本語じゃない場合(アルファベットや数字、記号)はクリア(表示更新)されます。
もうちょっと言うと、IMが起動されてしまうと半角数字に変換して入力したとしても表示が更新されなくなります。
IM起動後でも、一回バックスペースを打つと、クリアできるようになります。

×ボタンはクリアする機能なんですが、それ以外のボタンなどで外からTextFieldtext:変数を上書きするような機能は全面的にダメな(=IM起動直後では表示が更新されなくなる)ように見えます。

困りました。

kabeyakabeya

Buttonaction:を以下のようにすると、ひとまずはうまく行くように見えます。

            Button(action: {
                if !text.isEmpty {
                    let _ = text.removeLast()
                    Task { @MainActor in
                        self.text = ""
                    }
                }
            }, label: {
                Image(systemName: "xmark.circle.fill")
            })

1文字削ってから(バックスペースを打つののシミュレーション)、Tasktext:変数を更新します。

  • 上記は1文字削ってますが、半角スペースを足すとかでも良さそうです。ただ、何かしらtext:変数を操作してからでないとうまく機能しないようです。
  • Taskを使って非同期実行していますが、Taskを使わずに直接クリアしてもダメなようです。
  • 1文字削る処理もTaskに入れると、それはそれでダメなようです。1文字削るのと、クリアするのは別にする必要があるようです。

問題は、一回変数操作を挟んでいるので、この変数に依存して描画が更新されるビューは2回再描画が走るということですね。
影響がどれぐらいあるのか次第でしょうか。

kabeyakabeya

IMを使う場合にだけ影響が出るように見えますので、英語圏とかからはAppleにバグ報告されない気もしますね。


追記)フィードバックアシスタントで報告しました。

kabeyakabeya

上記は1文字削ってますが、半角スペースを足すとかでも良さそうです

削る方はダメですね。1文字だけ入力されているケースでは、削ったのが空文字列になってしまって(=Taskでの更新結果と同じになってしまって)うまく動作しません。
足す方がよさそう。


追記)
自分で参照する際に、間違ってダメな方をコピペしそうになったので、改めてよさげな方を載せておきます。

            Button(action: {
                if !self.text.isEmpty {
                    self.text = self.text + " "
                    Task { @MainActor in
                        self.text = ""
                    }
                }
            }, label: {
                Image(systemName: "xmark.circle.fill")
            })