Fix text editing issues on characters added right after mention link
Changelog-Fixed: Fix text editing issues on characters added right after mention link Closes: https://github.com/damus-io/damus/issues/1375 Signed-off-by: Daniel D’Aquino <daniel@daquino.me> Tested-by: William Casarin <jb55@jb55.com> Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
committed by
William Casarin
parent
c464a26151
commit
dcb94635ea
@@ -204,6 +204,8 @@ struct PostView: View {
|
||||
TextViewWrapper(attributedText: $post, postTextViewCanScroll: $postTextViewCanScroll, cursorIndex: newCursorIndex, getFocusWordForMention: { word, range in
|
||||
focusWordAttributes = (word, range)
|
||||
self.newCursorIndex = nil
|
||||
}, updateCursorPosition: { newCursorIndex in
|
||||
self.newCursorIndex = newCursorIndex
|
||||
})
|
||||
.environmentObject(tagModel)
|
||||
.focused($focus)
|
||||
|
||||
@@ -14,6 +14,7 @@ struct TextViewWrapper: UIViewRepresentable {
|
||||
|
||||
let cursorIndex: Int?
|
||||
var getFocusWordForMention: ((String?, NSRange?) -> Void)? = nil
|
||||
let updateCursorPosition: ((Int) -> Void)
|
||||
|
||||
func makeUIView(context: Context) -> UITextView {
|
||||
let textView = UITextView()
|
||||
@@ -34,11 +35,11 @@ struct TextViewWrapper: UIViewRepresentable {
|
||||
|
||||
func updateUIView(_ uiView: UITextView, context: Context) {
|
||||
uiView.isScrollEnabled = postTextViewCanScroll
|
||||
let range = uiView.selectedRange
|
||||
uiView.attributedText = attributedText
|
||||
|
||||
TextViewWrapper.setTextProperties(uiView)
|
||||
setCursorPosition(textView: uiView)
|
||||
let range = uiView.selectedRange
|
||||
|
||||
uiView.selectedRange = NSRange(location: range.location + tagModel.diff, length: range.length)
|
||||
tagModel.diff = 0
|
||||
@@ -52,16 +53,18 @@ struct TextViewWrapper: UIViewRepresentable {
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(attributedText: $attributedText, getFocusWordForMention: getFocusWordForMention)
|
||||
Coordinator(attributedText: $attributedText, getFocusWordForMention: getFocusWordForMention, updateCursorPosition: updateCursorPosition)
|
||||
}
|
||||
|
||||
class Coordinator: NSObject, UITextViewDelegate {
|
||||
@Binding var attributedText: NSMutableAttributedString
|
||||
var getFocusWordForMention: ((String?, NSRange?) -> Void)? = nil
|
||||
let updateCursorPosition: ((Int) -> Void)
|
||||
|
||||
init(attributedText: Binding<NSMutableAttributedString>, getFocusWordForMention: ((String?, NSRange?) -> Void)?) {
|
||||
init(attributedText: Binding<NSMutableAttributedString>, getFocusWordForMention: ((String?, NSRange?) -> Void)?, updateCursorPosition: @escaping ((Int) -> Void)) {
|
||||
_attributedText = attributedText
|
||||
self.getFocusWordForMention = getFocusWordForMention
|
||||
self.updateCursorPosition = updateCursorPosition
|
||||
}
|
||||
|
||||
func textViewDidChange(_ textView: UITextView) {
|
||||
@@ -96,6 +99,65 @@ struct TextViewWrapper: UIViewRepresentable {
|
||||
}
|
||||
return NSRange(location: startOffset, length: length)
|
||||
}
|
||||
|
||||
// This `UITextViewDelegate` method is automatically called by the editor when edits occur, to check whether a change should occur
|
||||
// We will use this method to manually handle edits concerning mention ("@") links, to avoid manual text edits to attributed mention links
|
||||
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
guard let attributedString = textView.attributedText else {
|
||||
return true // If we cannot get an attributed string, just fail gracefully and allow changes
|
||||
}
|
||||
var mutable = NSMutableAttributedString(attributedString: attributedString)
|
||||
|
||||
let entireRange = NSRange(location: 0, length: attributedString.length)
|
||||
var shouldAllowChange = true
|
||||
var performEditActionManually = false
|
||||
|
||||
attributedString.enumerateAttribute(.link, in: entireRange, options: []) { (value, linkRange, stop) in
|
||||
guard value != nil else {
|
||||
return // This range is not a link. Skip checking.
|
||||
}
|
||||
|
||||
if range.contains(linkRange.upperBound) && range.contains(linkRange.lowerBound) {
|
||||
// Edit range engulfs all of this link's range.
|
||||
// This link will naturally disappear, so no work needs to be done in this range.
|
||||
return
|
||||
}
|
||||
else if linkRange.intersection(range) != nil {
|
||||
// If user tries to change an existing link directly, remove the link attribute
|
||||
mutable.removeAttribute(.link, range: linkRange)
|
||||
// Perform action manually to flush above changes to the view, and to prevent the character being added from having an attributed link property
|
||||
performEditActionManually = true
|
||||
return
|
||||
}
|
||||
else if range.location == linkRange.location + linkRange.length && range.length == 0 {
|
||||
// If we are inserting a character at the right edge of a link, UITextInput tends to include the new character inside the link.
|
||||
// Therefore, we need to manually append that character outside of the link
|
||||
performEditActionManually = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if performEditActionManually {
|
||||
shouldAllowChange = false
|
||||
addUnattributedText(text, to: &mutable, inRange: range)
|
||||
attributedText = mutable
|
||||
|
||||
// Move caret to the end of the newly changed text.
|
||||
updateCursorPosition(range.location + text.count)
|
||||
}
|
||||
|
||||
return shouldAllowChange
|
||||
}
|
||||
|
||||
func addUnattributedText(_ text: String, to attributedString: inout NSMutableAttributedString, inRange range: NSRange) {
|
||||
if range.length == 0 {
|
||||
attributedString.insert(NSAttributedString(string: text, attributes: nil), at: range.location)
|
||||
}
|
||||
else {
|
||||
attributedString.replaceCharacters(in: range, with: text)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user