I have a very annoying problem editing a property of one of my model structs: the binding var (subject) property (gender) is updated only once by a selection list. The first time I use my control, all works correctly; from the second time on, nothing happens (not even an .onChange() placed for debug... it simply doesn't fire up).
EVEN MORE STRANGE BEHAVIOR: The SAME control, copied inside an Apple sample projects works perfectly (Recipies Sample, downloaded from Apple Developer site). This sample project is, I guess, at least two years old, still with @ObservableObject, @Publish, ...and so on, that I converted to @Observable protocol.
FINAL CHERRY ON THE CAKE: THE SAME sample Project, copied ASIS into a new Xcode projects, doesn't work anymore!
It seems an error buried deep inside Xcode compiler optimizations (maybe to avoid unnecessary views redraw carried too far...).
Anyway: I'm asking for help, because I'm not able to see any reason for this behavior and - just to add a bit of frustration - a working project developed with Xcode 15, without any problem (Stager, available on the App Store), reopenend with Xcode 16 acquires the same odd behavior.
Any Apple developer can help? Many thanks in advace
Simplified code follows (I made a new project just with the few things needed to show the case)
MODEL
import Foundation
import SwiftUI
enum Gender : String, Codable, CaseIterable, Equatable {
case male = "M"
case female = "F"
case nonbinary = "N"
case unknown = "U"
var description : String {
switch self {
case .male : "Male"
case .female : "Female"
case .nonbinary : "Not binary"
case .unknown : "Unknown"
}
}
var iconName : String {
"iconGender\(self.rawValue)"
}
}
struct Subject : Identifiable, Codable, Equatable, Hashable {
var id : Int
var name : String
var surname : String
var nickName : String // Identificativo alternativo all’anagrafica
var gender : Gender // Sesso del soggetto [ M | F | * ]
var imageName : String {
"foto" + self.nickName.replacingOccurrences(of: " ", with: "") + ".jpg"
}
static func == (lhs: Subject, rhs: Subject) -> Bool {
return (lhs.id, lhs.nickName, lhs.surname, lhs.name) == (rhs.id, rhs.nickName, rhs.surname, rhs.name)
}
static func emptySubject() -> Subject {
return Subject(id: -1, name: "", surname: "", nickName: "", gender: .unknown)
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(nickName)
hasher.combine(surname)
hasher.combine(name)
}
}
CONTROL
import SwiftUI
struct FormPickerGender: View {
@Binding var value : Gender
let isEdit : Bool
@State var presentPicker : Bool = false
var body: some View {
HStack {
Text("Gender:").foregroundStyle(.gray).italic()
if isEdit {
Image(systemName: "text.magnifyingglass")
.foregroundStyle(.tint)
.onTapGesture {
presentPicker = true
}
}
Text("\(value.description)")
}
.sheet(isPresented: $presentPicker, content: {
PickGender(currentGender: $value)
})
}
}
struct PickGender: View {
@Environment(\.dismiss) var dismiss
@Binding var currentGender : Gender
var body: some View {
Text("Gender selection")
.font(.title2)
.foregroundStyle(.tint)
Button("Cancel") {
dismiss()
}
.buttonBorderShape(.capsule)
List {
ForEach(Gender.allCases, id: \.self) { genderCase in
HStack {
Image("\(genderCase.iconName)")
if currentGender == genderCase {
Text(genderCase.description)
.font(.title3)
.foregroundStyle(.blue)
} else {
Text(genderCase.description)
.font(.title3)
}
Spacer()
}
.onTapGesture {
currentGender = genderCase
dismiss()
}
}
.listRowInsets(EdgeInsets(top: 10, leading: 50, bottom: 10, trailing: 50))
}
}
}
struct GenderPreviewWrapper: View {
@State var subject = Subject.emptySubject()
var body: some View {
Form {
FormPickerGender(value: $subject.gender, isEdit: true)
}
}
}
#Preview {
return GenderPreviewWrapper()
}
Just for completion... If instead of $subject.gender, I use a state variable valued with gender and then $stateGender it works, but to create a specific state var for EACH property of a structure, seems to me to nullify the concept itself of struct: why bother to foreseen properties, if you can't manage them as a whole?
Probably the solution will be to create a specific CLASS object of the STRUCT object, just for edit... something like :
static func <STRUCT>.getEditObject() -> classObject
static func <CLASS>.getStructObject() -> structObject
...once again: why have structs?