iosswiftuitexteditor

iOS 16 TextEditor long text jumping (SwiftUI)


Text jumping (I don't know how else to name that weird behavior) to the top(?) every time user enters a new character. I can reproduce the issue only on iPad OS 16. It affects the user who attempts to write long (several paragraphs) text in the TextEditor.

enter image description here

Reproduced with following code (just past five or more paragraphs of Lorem Ipsum and start typing)

class EditorViewModel: ObservableObject {
    @Published var text: String = ""
}

struct EditorView: View {
    @ObservedObject var viewModel: EditorViewModel

    var body: some View {
        VStack {
            TextEditor(text: $viewModel.text)
                .frame(height: 80)
                .padding()
                .background(Color.red)
            Spacer()
        }
    }
}

struct ContentView: View {
    var body: some View {
        EditorView(
            viewModel: EditorViewModel()
        )
    }
}

Solution

  • I've likely fiddled with every possible setting in TextEditor to no avail. Can't believe I had to resort to UIViewRepresentable to fix this issue in 2024.

    Tested with a continuous input of ~5000 characters with no bouncing on iPadOS 17.4.

    struct TextViewWrapper: UIViewRepresentable {
        @Binding var text: String
    
        func makeUIView(context: Context) -> UITextView {
            let textView = UITextView()
            textView.delegate = context.coordinator
            return textView
        }
    
        func updateUIView(_ uiView: UITextView, context: Context) {
            uiView.text = text
        }
    
        func makeCoordinator() -> Coordinator {
            Coordinator(self)
        }
    
        class Coordinator: NSObject, UITextViewDelegate {
            var parent: TextViewWrapper
    
            init(_ parent: TextViewWrapper) {
                self.parent = parent
            }
    
            func textViewDidChange(_ textView: UITextView) {
                parent.text = textView.text
            }
        }
    }
    

    Edit:

    func updateUIView(_ uiView: UITextView, context: Context) {
        let selectedRange = uiView.selectedRange
        uiView.text = text
        uiView.selectedRange = selectedRange
    }
    

    selectedRange to be applied to avoid cursor being bounced to the last of the full text when attempting to insert a next line anywhere within the text