Barrrdi
Barrrdi

Reputation: 1161

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

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

Upvotes: 3

Views: 2179

Answers (4)

AncientRoman
AncientRoman

Reputation: 123

Like Leo mentioned in the comments to his answer, this is caused by setting the text inside updateUIView(). That in and of itself is a good thing; when SwiftUI sees an update, you want your UITextView to update.
The problem is that textViewDidChange() updates your binding (as it should) and then SwiftUI sees that update and calls updateUIView(), so you end up with duplicate updates. To fix this issue, you just need to avoid that duplicate update.
Something like this:

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

Upvotes: 4

Dmitry Yurlagin
Dmitry Yurlagin

Reputation: 1

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

Upvotes: 0

Andriy Pohorilko
Andriy Pohorilko

Reputation: 131

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)
}

Upvotes: 2

Leo Dabus
Leo Dabus

Reputation: 236350

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
}

Upvotes: 5

Related Questions