はじめに
SwiftUIのTextEditorでは実現できない実装があり、UITextViewをUIViewRepresentableでラップして実装したところ、文中への入力時に改行をするとカーソルが末尾に移動してしまう現象に遭遇しました。
同じような現象で困っている方の参考になればと思い対応方法をまとめます。
環境
Xcode 15.0
内容
UITextViewをUIViewRepresentable使って実装したコードは以下の通りです。
最低限の実装をしており、カスタム部分は省いています。
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F631585%2Fc5476940-c593-b9e1-925a-d367a6b31d32.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=cf128f425f96ecd36cdba807f1a13749)
import SwiftUI
struct CustomTextEditor: View {
@State var text: String = ""
var body: some View {
VStack {
CustomTextView(
text: $text
)
.border(Color.gray, width: 1)
.padding(20)
}
}
}
struct CustomTextView: UIViewRepresentable {
@Binding var text: String
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.font = .systemFont(ofSize: 14)
// 本来はここで細かい設定を行う
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: CustomTextView
init(_ parent: CustomTextView) {
self.parent = parent
}
func textViewDidChange(_ textView: UITextView) {
parent.text = textView.text
}
}
}
一見問題なく入力できますが、複数行入力後に途中の行に入力&改行するとカーソルが末尾に移動してしまいます。
対応方法
updateUIView
の処理の中で無駄な更新をしていたのが原因でした。UITextViewDelegate
のtextViewDidChange
で取得した値で再度UITextView
のtextを更新したことで、カーソルが末尾に移動する現象が起きていました。
updateUIView
内のtextの更新は、値に変更があった場合のみにしてあげることでカーソルの移動が発生しなくなりました。
struct CustomTextView: UIViewRepresentable {
@Binding var text: String
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.font = .systemFont(ofSize: 14)
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
- uiView.text = text
+ if uiView.text != text {
+ uiView.text = text
+ }
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: CustomTextView
init(_ parent: CustomTextView) {
self.parent = parent
}
func textViewDidChange(_ textView: UITextView) {
parent.text = textView.text
}
}
}
おわりに
原因はとても単純なことでしたが、一見正常な挙動を見せている分気づきにくいのかなと思います。