Xcode 11.7 + iOS 13.7の環境でTextFieldを使っていると、日本語入力時に挙動がおかしくなることに気がついた。開発中はハードウェアキーボードで英数字のみ入力していたので気がつかなかった。
実行環境
- Xcode 11.7
- iOS 13.7
問題の挙動
SwiftUIのTextFieldで日本語(フリック入力?)すると挙動がおかしくなってしまう不具合が発生した。たとえば「わだ」をフリック入力する場合には以下の現象が発生する。
- 「わ」を入力後
- 「た」を入力
- 「わ」が消えて「た」のみ残る
これではまともなに動く画面を作ることができない。
解決編
TextFieldでなんとかするように検討してみたものの難しそうなので諦めてUIKitのUITextFieldをラップして使うことにした。入力中のテキストも綺麗にバインディングできると思っていたがかなり苦労した。
UITextField.textDidChangeNotification
の通知を受け取り、都度バインディングしたtextを書き換えることにした。
import SwiftUI import UIKit struct TextFieldNative: UIViewRepresentable { let hint: String @Binding var text: String @State private var textContentType: UITextContentType? = nil @State private var keyboardType: UIKeyboardType = .default var isFirstResponder: Bool = false class Coordinator: NSObject, UITextFieldDelegate { let target: TextFieldNative var didBecomeFirstResponder = false init(target: TextFieldNative) { self.target = target } func textFieldDidEndEditing(_ textField: UITextField) { target.text = textField.text ?? "" } func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() } /// 1文字ずつの変更で呼ばれる(UITextField.textDidChangeNotification) @objc func textDidChange(_ textField: UITextField) { target.text = textField.text ?? "" } } init(_ hint: String, text: Binding<String>, contentType: UITextContentType?, keyboardType: UIKeyboardType) { self.hint = hint self._text = text self.textContentType = contentType self.keyboardType = keyboardType } func makeCoordinator() -> TextFieldNative.Coordinator { return Coordinator(target: self) } func makeUIView(context: Context) -> UITextField { let textField = UITextField(frame: .zero) textField.placeholder = hint textField.delegate = context.coordinator textField.text = text textField.textContentType = textContentType textField.keyboardType = keyboardType textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: textField, queue: nil) { (_) in context.coordinator.textDidChange(textField) } // NotificationCenter.default.addObserver(forName: UITextField.textDidBeginEditingNotification, object: textField, queue: nil) { (_) in // context.coordinator.textDidChange(textField) // } // NotificationCenter.default.addObserver(forName: UITextField.textDidEndEditingNotification, object: textField, queue: nil) { (_) in // context.coordinator.textDidChange(textField) // } return textField } static func dismantleUIView(_ uiView: UITextField, coordinator: Coordinator) { NotificationCenter.default.removeObserver(self, name: UITextField.textDidChangeNotification, object: uiView) // NotificationCenter.default.removeObserver(self, name: UITextField.textDidBeginEditingNotification, object: uiView) // NotificationCenter.default.removeObserver(self, name: UITextField.textDidEndEditingNotification, object: uiView) } func updateUIView(_ uiView: UITextField, context: Context) { if uiView.text != text { uiView.text = text } if isFirstResponder && !context.coordinator.didBecomeFirstResponder { uiView.becomeFirstResponder() context.coordinator.didBecomeFirstResponder = true } } }
余談ではあるが入力している文字数が長くなると、Viewそのものが横に伸びていくようになってしまった。SwiftUIのView側で指定した制約よりも「強い」ようなので、makeUIView(context:)
で、textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
として明示的に優先度をさげた。