iOS 16 TextEditor long text jumping (SwiftUI)
Asked Answered
H

2

6

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()
        )
    }
}
Highhanded answered 3/2, 2023 at 11:22 Comment(4)
I am seeing this behavior too on all devices. It started with iOS16. As you say, it only happens with long text. In my testing the first few edits of existing text might be fine, but if I scroll down in a document and start to type, the displayed text jumps all over the place even though the charters are inserted in the correct spot. This also happens in a UITextView.Palimpsest
Same problem here, on iOS 16.3.1 still not fixed. Maybe this edge case is not widely known? This seems to be a huge bug in iOS.Coryza
I've written a post on Apple Forums, but there is no reaction to it.Highhanded
Getting this on iOS 17, macOS 14. Just garbage.Extra
E
2

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

Ez answered 26/8 at 22:16 Comment(0)
H
0

In the ContentView/parent view add

@StateObject private var viewModel = EditorViewModel()

Then change the body

EditorView(
    viewModel: viewModel
)

Per Apple

Don’t specify a default or initial value for the observed object. Use the attribute only for a property that acts as an input for a view, as in the above example.

https://developer.apple.com/documentation/swiftui/observedobject

Heelpost answered 21/3 at 11:18 Comment(1)
That's important but unrelatedJabin

© 2022 - 2024 — McMap. All rights reserved.