iOSアプリ開発中に SwiftUI の TextField で日本語入力したときに、テキストがうまく入力できない場合があって困りました。
とりあえず、UITextField で回避する方法を書いておきます。
準備
- Xcode 11.5
UITextField と代用する
SwiftUI.TextField
の入力がおかしいときは、マークされたテキストのときに起こっているようだったので、そのときの入力だけ無視するように UITextField を代わりに使って実装します。
※ マークされたテキストは、日本語のような multistage text input
のときに起きるそうです。
UITextField を UIViewRepresentableでラップしつつ、SwiftUI.TextField と同じようなインターフェイスが取れるようにしてます。
import Combine
import SwiftUI
import UIKit
struct MyTextField: UIViewRepresentable {
private var placeholder: String
@Binding private var text: String
private var textField = UITextField()
init(_ placeholder: String, text: Binding<String>) {
self.placeholder = placeholder
self._text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(textField: self.textField, text: self._text)
}
func makeUIView(context: Context) -> UITextField {
textField.placeholder = self.placeholder
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = self.$text.wrappedValue
}
class Coordinator: NSObject {
private var dispose = Set<AnyCancellable>()
@Binding var text: String
init(textField: UITextField, text: Binding<String>) {
self._text = text
super.init()
textField.delegate = self
NotificationCenter.default
.publisher(for: UITextField.textDidChangeNotification, object: textField)
.compactMap { $0.object as? UITextField }
.filter { $0.markedTextRange == nil } // マークされたテキストを無視する
.compactMap { $0.text }
.receive(on: RunLoop.main)
.assign(to: \.text, on: self)
.store(in: &dispose)
}
}
}
extension MyTextField.Coordinator: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
}
}
使い方
struct MyTextField_Previews: PreviewProvider {
struct ContentView: View {
@State var text: String = ""
var body: some View {
VStack {
Text(self.text)
.foregroundColor(.red).padding()
MyTextField("placeholder", text: self.$text)
.frame(height: 28).padding()
Button(action: { self.text = "" }) {
Text("Clear")
}
}
}
}
static var previews: some View {
ContentView()
}
}
上記の MyTextField
は SwiftUI.TextField
と同じように使えるので、そのうち、バージョンアップで修正されたらすぐ差し替えられます。
ただし、@State
の text
には変換中のテキスト(マークされているときのテキスト)は入ってきませんが、変換が決定したときにバインドされます。
とりあえずひとつの解決策としてありかなと思います。