Simplify SelectableText state management

This commit simplifies the state management and information flow for SelectableText.

This also fixes issues and inconsistencies with the selected text for a highlight action,
which often appeared in some scenarios with the symptom of a highlight
action showing the incorrect or outdated selected text.

Previously, the state of the selected text and highlight action was
tracked in two independent state/binding variables which caused
re-renders when they were modified, often leading to inconsistencies as
those two independent variables would not be changed atomically across
renders leading to inconsistent, undefined behavior

The commit addresses this by using a single state object instead of two,
and a direct callback interface when the highlight button is pressed,
which eliminates the need of relying on view re-renders to apply.

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
Daniel D’Aquino
2024-08-17 14:38:23 -07:00
committed by William Casarin
parent d51179189c
commit 5834e1ee9b

View File

@@ -13,8 +13,7 @@ struct SelectableText: View {
let event: NostrEvent? let event: NostrEvent?
let attributedString: AttributedString let attributedString: AttributedString
let textAlignment: NSTextAlignment let textAlignment: NSTextAlignment
@State private var showHighlightPost = false @State private var highlightPostingState: HighlightPostingState = .hide
@State private var selectedText = ""
@State private var selectedTextHeight: CGFloat = .zero @State private var selectedTextHeight: CGFloat = .zero
@State private var selectedTextWidth: CGFloat = .zero @State private var selectedTextWidth: CGFloat = .zero
@@ -37,8 +36,9 @@ struct SelectableText: View {
fixedWidth: selectedTextWidth, fixedWidth: selectedTextWidth,
textAlignment: self.textAlignment, textAlignment: self.textAlignment,
enableHighlighting: self.enableHighlighting(), enableHighlighting: self.enableHighlighting(),
showHighlightPost: $showHighlightPost, postHighlight: { selectedText in
selectedText: $selectedText, self.highlightPostingState = .show_post_view(highlighted_text: selectedText)
},
height: $selectedTextHeight height: $selectedTextHeight
) )
.padding([.leading, .trailing], -1.0) .padding([.leading, .trailing], -1.0)
@@ -53,9 +53,13 @@ struct SelectableText: View {
self.selectedTextWidth = newSize.width self.selectedTextWidth = newSize.width
} }
} }
.sheet(isPresented: $showHighlightPost) { .sheet(isPresented: Binding(get: {
if let event { return self.highlightPostingState.show()
HighlightPostView(damus_state: damus_state, event: event, selectedText: $selectedText) }, set: { newValue in
self.highlightPostingState = newValue ? .show_post_view(highlighted_text: self.highlightPostingState.highlighted_text() ?? "") : .hide
})) {
if let event, case .show_post_view(let highlighted_text) = self.highlightPostingState {
HighlightPostView(damus_state: damus_state, event: event, selectedText: .constant(highlighted_text))
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
.presentationDetents([.height(selectedTextHeight + 150), .medium, .large]) .presentationDetents([.height(selectedTextHeight + 150), .medium, .large])
} }
@@ -66,15 +70,34 @@ struct SelectableText: View {
func enableHighlighting() -> Bool { func enableHighlighting() -> Bool {
self.event != nil self.event != nil
} }
enum HighlightPostingState {
case hide
case show_post_view(highlighted_text: String)
func show() -> Bool {
if case .show_post_view(let highlighted_text) = self {
return true
}
return false
}
func highlighted_text() -> String? {
switch self {
case .hide:
return nil
case .show_post_view(highlighted_text: let highlighted_text):
return highlighted_text
}
}
}
} }
fileprivate class TextView: UITextView { fileprivate class TextView: UITextView {
@Binding var showHighlightPost: Bool var postHighlight: (String) -> Void
@Binding var selectedText: String
init(frame: CGRect, textContainer: NSTextContainer?, showHighlightPost: Binding<Bool>, selectedText: Binding<String>) { init(frame: CGRect, textContainer: NSTextContainer?, postHighlight: @escaping (String) -> Void) {
self._showHighlightPost = showHighlightPost self.postHighlight = postHighlight
self._selectedText = selectedText
super.init(frame: frame, textContainer: textContainer) super.init(frame: frame, textContainer: textContainer)
} }
@@ -91,8 +114,8 @@ fileprivate class TextView: UITextView {
@objc public func highlightText(_ sender: Any?) { @objc public func highlightText(_ sender: Any?) {
guard let selectedRange = self.selectedTextRange else { return } guard let selectedRange = self.selectedTextRange else { return }
selectedText = self.text(in: selectedRange) ?? "" guard let selectedText = self.text(in: selectedRange) else { return }
showHighlightPost.toggle() self.postHighlight(selectedText)
} }
} }
@@ -105,12 +128,11 @@ fileprivate class TextView: UITextView {
let fixedWidth: CGFloat let fixedWidth: CGFloat
let textAlignment: NSTextAlignment let textAlignment: NSTextAlignment
let enableHighlighting: Bool let enableHighlighting: Bool
@Binding var showHighlightPost: Bool let postHighlight: (String) -> Void
@Binding var selectedText: String
@Binding var height: CGFloat @Binding var height: CGFloat
func makeUIView(context: UIViewRepresentableContext<Self>) -> TextView { func makeUIView(context: UIViewRepresentableContext<Self>) -> TextView {
let view = TextView(frame: .zero, textContainer: nil, showHighlightPost: $showHighlightPost, selectedText: $selectedText) let view = TextView(frame: .zero, textContainer: nil, postHighlight: postHighlight)
view.isEditable = false view.isEditable = false
view.dataDetectorTypes = .all view.dataDetectorTypes = .all
view.isSelectable = true view.isSelectable = true