diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift index 49c998c8..d352026e 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift @@ -101,6 +101,9 @@ private struct NewMainTextViewRepresentable: UIViewRepresentable { } } +// laxer than the CharacterCounter regex, because we want to find mentions that are being typed but aren't yet complete (e.g., "@a@b") +private let mentionRegex = try! NSRegularExpression(pattern: "(@[a-z0-9_]+)(?:@[a-z0-9\\-\\.]+)?", options: .caseInsensitive) + private final class WrappedTextViewCoordinator: NSObject { private static let attachment: NSTextAttachment = { let font = UIFont.systemFont(ofSize: 20) @@ -149,6 +152,7 @@ private final class WrappedTextViewCoordinator: NSObject { private func updateAttributes(in textView: UITextView) { let str = NSMutableAttributedString(attributedString: textView.attributedText!) var changed = false + var cursorOffset = 0 // remove existing mentions that aren't valid str.enumerateAttribute(.mention, in: NSRange(location: 0, length: str.length), options: .reverse) { value, range, stop in @@ -157,18 +161,19 @@ private final class WrappedTextViewCoordinator: NSObject { if hasTextAttachment { substr = String(substr.dropFirst()) } - if CharacterCounter.mention.numberOfMatches(in: substr, range: NSRange(location: 0, length: substr.utf16.count)) == 0 { + if mentionRegex.numberOfMatches(in: substr, range: NSRange(location: 0, length: substr.utf16.count)) == 0 { changed = true str.removeAttribute(.mention, range: range) str.removeAttribute(.foregroundColor, range: range) if hasTextAttachment { str.deleteCharacters(in: NSRange(location: range.location, length: 1)) + cursorOffset -= 1 } } } // add mentions for those missing - let mentionMatches = CharacterCounter.mention.matches(in: str.string, range: NSRange(location: 0, length: str.length)) + let mentionMatches = mentionRegex.matches(in: str.string, range: NSRange(location: 0, length: str.length)) for match in mentionMatches.reversed() { var attributeRange = NSRange() let attribute = str.attribute(.mention, at: match.range.location, effectiveRange: &attributeRange) @@ -179,6 +184,7 @@ private final class WrappedTextViewCoordinator: NSObject { if attribute == nil { str.insert(NSAttributedString(attachment: Self.attachment), at: match.range.location) newAttributeRange = NSRange(location: match.range.location, length: match.range.length + 1) + cursorOffset += 1 } else { newAttributeRange = match.range } @@ -190,7 +196,11 @@ private final class WrappedTextViewCoordinator: NSObject { } if changed { + let selection = textView.selectedRange + textView.attributedText = str + + textView.selectedRange = NSRange(location: selection.location + cursorOffset, length: selection.length) } } }