Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

struct AppKitTextSelectionView: View {
@Environment(TextSelectionModel.self) private var textSelectionModel: TextSelectionModel?
@State private var selectionRects: [TextSelectionRect] = []

private let layout: Text.Layout
private let origin: CGPoint
Expand All @@ -22,7 +21,8 @@
}

var body: some View {
Group {
let selectionRects = self.selectionRects
return Group {
if selectionRects.isEmpty {
Color.clear
} else {
Expand All @@ -37,18 +37,22 @@
}
}
}
.onChange(of: textSelectionModel?.selectedRange, initial: true, updateSelectionRects)
.onChange(of: layout, initial: true, updateSelectionRects)
}

private func updateSelectionRects() {
if let textSelectionModel,
let selectedRange = textSelectionModel.selectedRange
{
selectionRects = textSelectionModel.selectionRects(for: selectedRange, layout: layout)
} else {
selectionRects = []
/// Selection rectangles for the current range within this layout.
///
/// Derived in `body` rather than stored in `@State` and seeded from an
/// `onChange(initial: true)`: that initial action wrote state during the first
/// view update, which SwiftUI flags ("Modifying state during view update", and
/// previously "tried to update multiple times per frame" when both the
/// `selectedRange` and `layout` handlers fired on the same frame). As an
/// `@Observable`-tracked computation it still recomputes whenever the selected
/// range or `layout` changes, with no state mutation.
private var selectionRects: [TextSelectionRect] {
guard let textSelectionModel, let selectedRange = textSelectionModel.selectedRange else {
return []
}
return textSelectionModel.selectionRects(for: selectedRange, layout: layout)
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,18 @@
return
}

// Try to reconcile the selected text range
self.selectedRange = layoutCollection.reconcileRange(
// Try to reconcile the selected text range. Only write when it actually
// changes: as the overlay's GeometryReader/preferences converge, this runs
// several times per frame with the same result, and a redundant write to the
// observed `selectedRange` is what SwiftUI flags as "onChange(of:
// AnyTextLayoutCollection) action tried to update multiple times per frame".
let reconciled = layoutCollection.reconcileRange(
selectedRange,
from: oldLayoutCollection
)
if reconciled != self.selectedRange {
self.selectedRange = reconciled
}
}

func setCoordinator(_ coordinator: TextSelectionCoordinator?) {
Expand Down