Cursor always jumps to the end of the UIViewRepresentable TextView when a newline is started before the final line + after last character on the line

Viewed 904

I’m depending on the TextView using UIViewRepresentable created here https://www.appcoda.com/swiftui-textview-uiviewrepresentable/.

struct TextView: UIViewRepresentable {
    
    @Binding var text: String
    @Binding var textStyle: UIFont.TextStyle
    
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        
        textView.delegate = context.coordinator
        textView.font = UIFont.preferredFont(forTextStyle: textStyle)
        textView.autocapitalizationType = .sentences
        textView.isSelectable = true
        textView.isUserInteractionEnabled = true
        
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text
        uiView.font = UIFont.preferredFont(forTextStyle: textStyle)
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator($text)
    }
    
    class Coordinator: NSObject, UITextViewDelegate {
        var text: Binding<String>

        init(_ text: Binding<String>) {
            self.text = text
        }
        
        func textViewDidChange(_ textView: UITextView) {
            self.text.wrappedValue = textView.text
        }
    }
}

struct ContentView: View {
    
    @State private var message = ""
    @State private var textStyle = UIFont.TextStyle.body
    
    var body: some View {
        ZStack(alignment: .topTrailing) {
            TextView(text: $message, textStyle: $textStyle)
                .padding(.horizontal)
            
            Button(action: {
                self.textStyle = (self.textStyle == .body) ? .title1 : .body
            }) {
                Image(systemName: "textformat")
                    .imageScale(.large)
                    .frame(width: 40, height: 40)
                    .foregroundColor(.white)
                    .background(Color.purple)
                    .clipShape(Circle())
                
            }
            .padding()    
        }
    }
}

The problem I’m having is whenever I start a newline 1) before the final line + 2) after the last character on the line, the cursor always jumps to after the final character in the text. Any ideas?

UPDATE:

Leo’s response technically addresses this issue, but it doesn’t seem perfect as there’s undesired scroll behaviour whereby although the caret position is now correct that doesn’t stop an auto-scroll to the bottom. See below:

enter image description here

3 Answers

You can save the caret position (selectedRange) and set your text view selected range after setting the text property inside updateUIView method:

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

In addition to Leo answer, what I noticed is if you start editing with a 'new line' character your textView might also jump to the end. What works for me is adding

func updateUIView(_ uiView: UITextView, context: Context) {
   //previous code
   uiView.scrollRangeToVisible(selectedRange)
}

it happens because you set font every symbol change. Just try not to set style too often if it is not needed

Related