5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SwiftUI で UITextField を使ってマークされたテキストを無視する

Posted at

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()
    }
}

上記の MyTextFieldSwiftUI.TextField と同じように使えるので、そのうち、バージョンアップで修正されたらすぐ差し替えられます。
ただし、@Statetext には変換中のテキスト(マークされているときのテキスト)は入ってきませんが、変換が決定したときにバインドされます。
とりあえずひとつの解決策としてありかなと思います。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?