I have a simple app with a sidebar listing items and an edit view to edit the details of the selected item.
One of the parameters that can be changed in the edit view is the name of the item that is also shown in the sidebar.
Below is a simple app demonstrating the problem. It lits 4 people and you can change the name. When you change the name, the sidebar does not change until another is selected.
I would like the name in the sidebar to change as it is edited. If I added a text view in the edit view, then it would change as the TextField changes. Why does the item in the sidebar not change (or only change when selecting different item)?
class Person: NSObject, ObservableObject {
@Published public var name: String
init(name: String) {
self.name = name
}
}
class Database {
public var people: [Person]
init() {
people = [Person(name: "Susan"), Person(name: "John"),
Person(name: "Jack"), Person(name: "Mary")]
}
}
@main
struct BindingTestApp: App {
@State private var database = Database(
var body: some Scene {
WindowGroup {
ContentView(people: $database.people)
}
}
}
struct ContentView: View {
struct SidebarView: View {
@Binding var people: [Person]
@Binding var selectedPerson: Person?
var body: some View {
List(people, id: \.self, selection: $selectedPerson) { person in
Text(person.name)
}.listStyle(SidebarListStyle())
}
}
struct PersonView: View {
@ObservedObject public var person: Person
var body: some View {
TextField("Name", text: $person.name)
}
}
@Binding var people: [Person]
@State private var selectedPerson: Person?
var body: some View {
NavigationView {
SidebarView(people: $people, selectedPerson: $selectedPerson)
if let selectedPerson = selectedPerson {
PersonView(person: selectedPerson)
} else {
Text("Please select or create a person.")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
@State static private var database = Database()
static var previews: some View {
ContentView(people: $database.people)
}
}
The problem is that person is never observed. The Text
view in the list will not be notified when the person's name changes because it is not observed.
To fix the problem, create another view that observes the class (ObservableObject).
That entire view is invalidated when the @Published field
in the @ObservedObject
changes.
The code below fixes the problem and the sidebar is correctly updated in real time as the name is edited.
class Person: NSObject, ObservableObject {
@Published public var name: String
init(name: String) {
self.name = name
}
}
class Database {
public var people: [Person]
init() {
people = [Person(name: "Susan"), Person(name: "John"),
Person(name: "Jack"), Person(name: "Mary")]
}
}
@main
struct BindingTestApp: App {
@State private var database = Database()
var body: some Scene {
WindowGroup {
ContentView(people: $database.people)
}
}
}
struct PersonView: View {
@ObservedObject var person: Person
var body: some View {
Text(person.name)
}
}
struct ContentView: View {
struct SidebarView: View {
@Binding var people: [Person]
@Binding var selectedPerson: Person?
var body: some View {
List(people, id: \.self, selection: $selectedPerson) { person in
PersonView(person: person)
}.listStyle(SidebarListStyle())
}
}
struct PersonView: View {
@ObservedObject public var person: Person
var body: some View {
TextField("Name", text: $person.name)
}
}
@Binding var people: [Person]
@State private var selectedPerson: Person?
var body: some View {
NavigationView {
SidebarView(people: $people, selectedPerson: $selectedPerson)
if let selectedPerson = selectedPerson {
PersonView(person: selectedPerson)
} else {
Text("Please select or create a person.")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
@State static private var database = Database()
static var previews: some View {
ContentView(people: $database.people)
}
}