Goal: macOS SwiftUI, sortable Table, double-click-to-edit-cell-contents. Focused on just String for now.
Passing a binding (e.g., TableColumn("Title", value: \.Binding<Track>.title, ...)
) worked great to modify the content.
However, TextField (for String) updates its value binding on each keypress. Table sorts via the same binding, so as the user types, the row is re-sorted on each keypress. Woof.
Considered options:
- Create
Formatter
subclass (TextField with a non-String requires formatter, + doesn't exhibit that trait) - Read
@FocusState
, dupe ourtracks
array, and only save the new val when the focus state changes.
Did number 2. However, I'm getting this strange output.
Focused: Optional(Field.text) -> Optional(Field.text)
=== AttributeGraph: cycle detected through attribute 827216 ===
=== AttributeGraph: cycle detected through attribute 827216 ===
=== AttributeGraph: cycle detected through attribute 827216 ===
=== AttributeGraph: cycle detected through attribute 827976 ===
=== AttributeGraph: cycle detected through attribute 817860 ===
Focused: nil -> nil
(And shoutout to onChange not reading right. Good job, buddy!)
Reduced code example:
struct TrackList: View {
@EnvironmentObject var store: Store
@FocusState private var focusedField: Field?
@State private var sortedTracks: [Binding<Track>] = []
var body: some View {
Table(of: Binding<Track>.self, sortOrder: $sortOrder) {
TableColumn(
"Title",
value: \Binding<Track>.wrappedValue,
comparator: titleComparator)
{ trackBinding in
TextField("Title", text: trackBinding.title)
.focused($focusedField, equals: .text)
}
} rows: {
ForEach(sortedTracks) { $track in
TableRow($track)
}
}
.onAppear {
// Since the new `.onChange(_ of:initial:)` isn't until macOS v14, we gotta do this for now.
sortedTracks = $store.tracks.sorted(using: sortOrder)
}
.onChange(of: focusedField) { newValue in
print("Focused: \(focusedField.debugDescription) -> \(newValue.debugDescription)")
sortedTracks = $store.tracks.sorted(using: sortOrder)
}
}
}