Reputation: 1161
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:
Upvotes: 3
Views: 2179
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
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
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
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