LoginSignup
3
2

[SwiftUI] UITextViewで意図せずカーソルが末尾に移動してしまう場合の対応方法

Posted at

はじめに

SwiftUIのTextEditorでは実現できない実装があり、UITextViewをUIViewRepresentableでラップして実装したところ、文中への入力時に改行をするとカーソルが末尾に移動してしまう現象に遭遇しました。
同じような現象で困っている方の参考になればと思い対応方法をまとめます。

環境

Xcode 15.0

内容

UITextViewをUIViewRepresentable使って実装したコードは以下の通りです。
最低限の実装をしており、カスタム部分は省いています。

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の処理の中で無駄な更新をしていたのが原因でした。UITextViewDelegatetextViewDidChangeで取得した値で再度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
        }
    }
}

おわりに

原因はとても単純なことでしたが、一見正常な挙動を見せている分気づきにくいのかなと思います。

3
2
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
3
2