The whole point of using a @State is so that the UI reacts to changes.
Here's something a little more complete, but yet simple enough, to show how you can read and update values and the UI, given your setup.
Note the use of ForEach with a bindings to $items, which gives you a $item binding to work with.
This allows for direct update of the array structs without needing to hit a button to run a function.
The example also includes another method, where you select a specific ID from a list, then use it to locate the item in the array and update its value.
import SwiftUI
struct StateArrayItem: Hashable, Codable, Identifiable {
let id: Int
var value: Int
}
struct StateArrayContentView: View {
//State values
@State private var selectedItem: StateArrayItem?
@State private var pickerFieldValue: Int?
@FocusState private var focusedField: StateArrayItem? // Focus state for each text field
//Sample array
@State private var items: [StateArrayItem] = [
StateArrayItem(id: 0, value: 40),
StateArrayItem(id: 1, value: 68),
StateArrayItem(id: 2, value: 52),
StateArrayItem(id: 3, value: 48),
StateArrayItem(id: 4, value: 33),
StateArrayItem(id: 5, value: 71),
StateArrayItem(id: 6, value: 69),
StateArrayItem(id: 7, value: 22),
StateArrayItem(id: 8, value: 28),
StateArrayItem(id: 9, value: 85)
]
//Body
var body: some View {
Form {
//Section
Section("Change a value and hit Return or tap away"){
//Array items binding loop
ForEach($items){ $item in
HStack {
//Actual value for current ID
Text("ID: \($item.id) - Value: \(item.value)")
.foregroundStyle(item == selectedItem ? .blue : .primary)
Spacer()
//Given the $item binding, the value will update as soon as a value is entered
TextField("ID: \($item.id) - Value is: \($item.value)", value: $item.value, format: .number)
.foregroundStyle(item == selectedItem ? .blue : .primary)
.multilineTextAlignment(.trailing)
.frame(width: 80)
.textFieldStyle(.roundedBorder)
.focused($focusedField, equals: item)
.onChange(of: focusedField) { oldValue, newValue in
selectedItem = newValue != nil ? newValue : selectedItem
}
.onSubmit {
selectedItem = item
}
}
.frame(maxWidth: .infinity, alignment: .center)
}
}
//Section
Section("Update by ID"){
HStack {
Picker("Pick an ID:", selection: $selectedItem){
Text("--").tag(nil as StateArrayItem?) // Tag for the placeholder
ForEach($items){ $item in
Text("\($item.id)").tag(item)
}
}
.tint(.blue)
.fixedSize()
}
//Show input form and button if an id is selected in the picker (or another textfield focused)
if let item = selectedItem {
HStack(spacing: 10) {
TextField("Enter new value", value: $pickerFieldValue, format: .number)
.textFieldStyle(.roundedBorder)
.focused($focusedField, equals: item)
.onSubmit{
if let value = pickerFieldValue {
updateValue(of: item, value: value)
}
}
.foregroundStyle(pickerFieldValue == nil ? .blue : pickerFieldValue != nil && item.value != pickerFieldValue ? .red : .green)
Spacer()
//Actual value for ID selected in the picker
Text("ID: \(item.id) Value: \(item.value)")
}
//Update button
Button("Update", action: {
//Guard condition for running the update function
guard let value = pickerFieldValue else {
print("No value to update with...")
return
}
updateValue(of: item, value: value)
})
.buttonStyle(.borderedProminent)
.disabled((pickerFieldValue == nil))
}
}
}
}
//Function to update an item with a new value
func updateValue(of item: StateArrayItem, value: Int) {
//Find the item in the array, based on its index - since item itself is a let constant
guard let index = items.firstIndex(of: item) else { return }
//Update the value
items[index].value = value
//Update the selectedItem with the newly modified item
selectedItem = items[index]
}
}
#Preview {
StateArrayContentView()
}

@Stateworks. You can only observe the change after the view updates. I assume this is an XY Problem. What is the ultimate problem that you are trying to solve? Why do you need to observe the change immediately?updateValueOfItemcall). It's also important how you use / create theidproperty of yourItementity, that's sth. your code doesn't show. Otherwise your demo code works for me and doesn't cause the described issue. So you must have a problem somewhere in your code that we can't see.