From 41275dafa2c1e287a57aec259d73513c4745d15e Mon Sep 17 00:00:00 2001 From: Joe Hinkle Date: Wed, 14 Apr 2021 16:17:17 -0600 Subject: [PATCH] get cursor position with binding in swiftui view --- .../FireflySyntaxView + TextDelegate.swift | 19 +++++++++++++-- Sources/Views/FireflyTextView.swift | 13 +++++++++++ Sources/Views/FireflyTextViewDelegate.swift | 4 ++++ .../Swift UI/FireflySyntaxViewSwift.swift | 23 +++++++++++++++++-- 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/Sources/Views/FireflySyntaxView + TextDelegate.swift b/Sources/Views/FireflySyntaxView + TextDelegate.swift index 1af96bf..0767a40 100644 --- a/Sources/Views/FireflySyntaxView + TextDelegate.swift +++ b/Sources/Views/FireflySyntaxView + TextDelegate.swift @@ -151,6 +151,10 @@ extension FireflySyntaxView: UITextViewDelegate { return true } + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + updateCursorPosition() + } + public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { delegate?.didClickLink(URL.absoluteString) return false @@ -169,9 +173,20 @@ extension FireflySyntaxView: UITextViewDelegate { return newLinePrefix } -// public func textViewDidChangeSelection(_ textView: UITextView) { + private func updateCursorPosition() { + if let cursorPositionChange = self.delegate?.cursorPositionChange { + if let pos = self.textView.cursorPosition() { + cursorPositionChange(self.textView.convert(pos, to: self.textView.superview)) + } else { + cursorPositionChange(nil) + } + } + } + + public func textViewDidChangeSelection(_ textView: UITextView) { + updateCursorPosition() // textStorage.updatePlaceholders(cursorRange: textView.selectedRange) -// } + } func updateSelectedRange(_ range: NSRange) { if range.location + range.length <= text.utf8.count { diff --git a/Sources/Views/FireflyTextView.swift b/Sources/Views/FireflyTextView.swift index 3ebc534..8f71a1d 100644 --- a/Sources/Views/FireflyTextView.swift +++ b/Sources/Views/FireflyTextView.swift @@ -14,6 +14,19 @@ public class FireflyTextView: UITextView { } } + /// Returns a CGRect for the cursor position in the text view's coordinates. If no cursor is present, it returns nil. + /// source: https://stackoverflow.com/a/43167060/3902590 + public func cursorPosition() -> CGRect? { + if let selectedRange = self.selectedTextRange + { + // `caretRect` is in the `textView` coordinate space. + return self.caretRect(for: selectedRange.end) + } else { + // No selection and no caret in UITextView. + return nil + } + } + public func currentWord() -> String { guard let cursorRange = self.selectedTextRange else { return "" } func getRange(from position: UITextPosition, offset: Int) -> UITextRange? { diff --git a/Sources/Views/FireflyTextViewDelegate.swift b/Sources/Views/FireflyTextViewDelegate.swift index 039e800..d1d8483 100644 --- a/Sources/Views/FireflyTextViewDelegate.swift +++ b/Sources/Views/FireflyTextViewDelegate.swift @@ -7,8 +7,10 @@ // import Foundation +import CoreGraphics public protocol FireflyDelegate: AnyObject { + var cursorPositionChange: ((_ cursorPosition: CGRect?) -> Void)? { get } func didChangeText(_ syntaxTextView: FireflyTextView) @@ -22,6 +24,8 @@ public protocol FireflyDelegate: AnyObject { // Provide default empty implementations of methods that are optional. public extension FireflyDelegate { + var cursorPositionChange: ((_ cursorPosition: CGRect?) -> Void)? { nil } + func didChangeText(_ syntaxTextView: FireflyTextView) { } func didChangeSelectedRange(_ syntaxTextView: FireflyTextView, selectedRange: NSRange) { } diff --git a/Sources/Views/Swift UI/FireflySyntaxViewSwift.swift b/Sources/Views/Swift UI/FireflySyntaxViewSwift.swift index fa5cdbd..d3d3069 100644 --- a/Sources/Views/Swift UI/FireflySyntaxViewSwift.swift +++ b/Sources/Views/Swift UI/FireflySyntaxViewSwift.swift @@ -15,6 +15,7 @@ import SwiftUI public struct FireflySyntaxEditor: UIViewRepresentable { @Binding var text: String + let cursorPosition: Binding? var language: String var theme: String @@ -24,8 +25,18 @@ public struct FireflySyntaxEditor: UIViewRepresentable { var didChangeSelectedRange: (FireflySyntaxEditor, NSRange) -> Void var textViewDidBeginEditing: (FireflySyntaxEditor) -> Void - public init(text: Binding, language: String, theme: String, fontName: String, didChangeText: @escaping (FireflySyntaxEditor) -> Void, didChangeSelectedRange: @escaping (FireflySyntaxEditor, NSRange) -> Void, textViewDidBeginEditing: @escaping (FireflySyntaxEditor) -> Void) { + public init( + text: Binding, + cursorPosition: Binding? = nil, + language: String, + theme: String, + fontName: String, + didChangeText: @escaping (FireflySyntaxEditor) -> Void, + didChangeSelectedRange: @escaping (FireflySyntaxEditor, NSRange) -> Void, + textViewDidBeginEditing: @escaping (FireflySyntaxEditor) -> Void + ) { self._text = text + self.cursorPosition = cursorPosition self.didChangeText = didChangeText self.didChangeSelectedRange = didChangeSelectedRange self.textViewDidBeginEditing = textViewDidBeginEditing @@ -51,8 +62,11 @@ public struct FireflySyntaxEditor: UIViewRepresentable { public func makeCoordinator() -> Coordinator { Coordinator(self) } - + public class Coordinator: FireflyDelegate { + + public var cursorPositionChange: ((CGRect?) -> Void)? + public func didClickLink(_ link: URL) { } let parent: FireflySyntaxEditor @@ -60,6 +74,11 @@ public struct FireflySyntaxEditor: UIViewRepresentable { init(_ parent: FireflySyntaxEditor) { self.parent = parent + if let cursorPosition = parent.cursorPosition { + self.cursorPositionChange = { + cursorPosition.wrappedValue = $0 + } + } } public func didChangeText(_ syntaxTextView: FireflyTextView) {