I have a Core Data entity with a few properties, the ones of interest here are a UUID column named "id" and an Int16 column containing values between 0 and 3.
I have a Table in SwiftUI which is correctly arranging the items stored by Core Data into its rows. The "selection:" binds a Set of the "id" values.
I have another view to which I have sent the Set of "id" values (Set<Bin.ID>) (Bin is the name of my entity) using @Binding var ... - so far so good.
Within that view, I can determine that I am correctly getting the set of interest, and most of what I am trying to accomplish I can get to work.
What is driving me up the wall, however, is that I am trying to get a Picker in the child view to let me change the value of the Int16 property (called playMode) of the first selected object from the table.
I have tried numerous things to create that binding but I can't seem to find the magic combination that works in any sensible way.
Here is what I am currently doing, which seems to come closest.
I set up a @State variable within the child view that has the same type as the property of the entity and created an initializer that calls a function to find the first selected item and set that state variable, plus a function to update the entity when the state changes:
@Binding var whichBins: Set<Bin.ID>
@State var playMode: Int16 = 0
init(whichBins: Binding<Set<Bin.ID>>)
{
self._whichBins = whichBins
playMode = getPlayMode(whichBins.wrappedValue)
}
private func getPlayMode(_ val: Set<Bin.ID>) -> Int16
{
if val.count > 0
{
do
{
let fr = try PersistenceController.shared.container.viewContext.fetch(Bin.fetchRequest(forID: val.first!))
for bin in fr
{
if (bin.typ == 1)
{
print("getPlayMode: actual return \(bin.playMode)")
return bin.playMode
}
}
} catch {
let nsError = error as NSError
print("getPlayMode fetch error \(nsError), \(nsError.userInfo)")
}
}
print("getPlayMode: default return existing \(playMode)")
return playMode
}
private func setPlayMode(value:Int16)
{
print("setPlayMode: value=\(value)")
if whichBins.count > 0
{
do
{
let fr = try PersistenceController.shared.container.viewContext.fetch(Bin.fetchRequest(forID: whichBins.first!))
for bin in fr
{
print("setPlayMode: GO GO GO")
bin.playMode = value
try? PersistenceController.shared.container.viewContext.save()
}
} catch {
let nsError = error as NSError
print("setPlayMode fetch error \(nsError), \(nsError.userInfo)")
}
}
}
Within the body of the view, I set up the picker with onChange handlers to update the state variable when the table selection changes and to call the function to set the value in the entity when the picker makes a change:
Picker("Play Mode:", selection: $playMode)
{
Text("Single").tag(Int16(0))
Text("Repeat One").tag(Int16(1))
Divider()
Text("Through End").tag(Int16(2))
Text("Repeat All").tag(Int16(3))
}
.onChange(of: playMode)
{ val in
setPlayMode(value: val)
}
.onChange(of: whichBins)
{ val in
playMode = getPlayMode(val)
}
What is happening is that when I switch the selection in the Table, I can see (from the logging output being produced by "print") that the getPlayMode function is returning the intended value - the one stored in the object - and that when I change the value in the Picker, the setPlayMode function is being called with the intended value (which then gets returned by getPlayMode).
However, when I change the table selection, while getPlayMode is returning the value that it should (as evidenced by "actual return"), the Picker is consistently showing the first item in the list - it never comes up with the intended selected item.
What am I missing?
Post
Replies
Boosts
Views
Activity
I am trying to build a user interface with a NavigationSplitView, with the sidebar containing a List that is broken into sections, with different sections built from objects of different classes (these being stored using swift data), as well as two items which are not in sections, which are not associated with underlying objects.
Basically the list is:
All
(type A section)
A1
A2
(type B section)
B1
B2
Other Thing
For the "All" option and each of the A and B objects, the view that is displayed in the content pain will have a list of a third class of object (another swift data "table"), giving C1, C2, etc.
The "Other Thing" selection will similarly have D1, D2, etc.
I am trying to add an inspector which will show controls based on the most recently selected item - this could be an A, B, C D, or "All" or "Other thing" - but the view in the inspector will need to show different controls and fields depending on the type of thing selected.
The problem I am running into is that I can't figure out how to track what is selected in the NavigationSplitView List.
If I try to use "selection:" on the List there is no common type that I can put in the Set<> to pull the objects - I can add dummy objects for "All" and "Other Thing" easily enough, but Swift Data seems to break if I make these child classes (the constructors won't compile if I try to call super.init()) and the "all" syntax can't be used because it requires Hashable and for some reason Swift seems convinced that "all Hashable" cannot conform to Hashable (!)
Evidently at one time NavigationLink could set a boolean binding if the selected item was active - but that was deprecated some time back, and I'm not sure how it would work with an arbitrary number of items in the list anyway?
I have been looking through the various APIs and I can't seem to find any way to solve this problem in Swift UI. I would think this would be a relatively common requirement for many types of apps?
Any ideas?
If it makes a difference, I am building from and targeting macOS 14.
macOS 14.6.1, SwiftUI, Xcode 16.0 (developing for macOS)
I have a TextField with a custom formatter and a custom binding (one created using Binding(get:set:).
My understanding is that the TextField should only call on the formatter and update the binding when I commit the TextField by moving focus away from it, hitting return, etc.
Unfortunately, that is not the case. I was able to verify that the formatter is being called (via NSLog added to the formatter code) every time I add a character to the field.
Not only is this a waste of CPU resources, but in combination with the custom binding, it is causing a data entry issue for my application.
I am writing a simple emulator for a computer system I concocted, and the formatter is formatting a memory location into its assembly language - the formatter's string method disassembles the instruction from the underlying memory value and the getObjectValue method assembles an input string back into the memory location.
One example of an instruction would be the "set" instruction. All of these are valid forms for input to the assembler:
set
set 0
set x
The "set" by itself is equivalent to "set 0", so what is happening is that I type "set" and the TextField immediately gets a valid response back from the formatter and updates the binding with the value of the "set 0" instruction.
The custom binding updates the value in memory, which triggers the "get" method of the binding and updates the TextField with the disassembled value which helpfully adds the "0" to the field, setting it to "set 0" before I have a chance to type the "x". I need to manually delete the "0" from the line which I never typed there in the first place in order to finish typing the line of input.
I either need a way to cause the TextField to behave the way it is supposed to and only update the binding when the value is committed, or a way to prevent the binding's get method from being retriggered by its own set method.
Any ideas how to go about fixing this?
Note that I set up a temporary TextField which was bound using a simple $binding to a state variable, and while the variable is updated whenever valid input is presented, it does not trigger the TextField to reread and thus I do not have the same input issue with that TextField. There is a reason why I need to use the custom get/set in context, however, so this is still an issue for me.