@Binding var updates only once

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?

Could you show where you put test onChange for debug ?

I see nowhere Observable in your code. Shouldn't Subject be Observable ?

@Claude31 Hi, Claude. Subject can't be @Observable, because only classes can be that and it's a struct. From the code (to keep it short) I omitted the observable class that embeds the subjects array, because it doesn't make any difference. In the Apple sample project the piece of code I posted works perfectly... and stops to work once copied to the new project created in Xcode 16 :)

Does the Apple project works with Xcode 16 ?

One idea: have you checked the deployment targets in Apple's project and yours ?

I was also guessing if there's a "new" directive in deployment target, following your idea... It's quite a boring task, but I'll try to do that: I'm really curios, because if I don't understand I tend to became mind sick :)

@Claude31 Maybe I found the reason for this strange behavior: the problem seems to be in the Equatable implementation I made for the Subject structure.

I thought the Equatable was involved only when dealing with lists and selections: only today (unable to see updates "randomly" after editing a resource), I realize that the Equatable seems to rule also the redraw of the containing view.

Following my first interpretation, I checked for Equality only the 4 main attributes, thus any change made to the other attributes had no influence on inequality.

Now I added a lastUpdate timeStamp to the struct, always up-to-date with updates and placed it in the Equatable == function.

Even if I turn-arounded my original problem using a temporary Observable Class editSubject, I'll study deeper this behavior, making a test project where in the Equatable are compared ALL the structures attributes... just to be sure to fully understand this, that seems to be a very basic for SwuiftUI, but not so emphasized in documentation (all sample are always too simple, compared to real life needs, unfortunately).

If you are interested, I'll keep you posted. And maybe could be useful for others :)

(Editing, 'cose I forgot to tell..)

of course, it seems to be an error of mine!
@Binding var updates only once
 
 
Q