最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swiftui - Cannot append to array inside bound variable - Stack Overflow

programmeradmin0浏览0评论

I have a person object with a list of strings

struct Person: Identifiable, Hashable {
    var id: UUID = UUID()
    var name: String
    var strings: [String] = []
    
    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.id == rhs.id
    }

    func hash(into hasher: inout Hasher) {
        hasherbine(id)
        hasherbine(name)
    }
}

which is stored in a @State array variable in the app struct and from my PersonView I'd like to add elements to the strings array.

struct PersonView: View {
    @Binding var person: Person

    var body: some View {
        List {
            Section(header: HStack {
                Text("Strings")
                Spacer()
                Button(action: {
                    print("BEFORE", person.strings)
                    person.strings.append("foo")
                    print("AFTER", person.strings)
                }) {
                    Image(systemName: "plus")
                }
            }) {
                ForEach($person.strings, id: \.self) { $str in
                    NavigationLink(destination: Text(str)) {
                        Text(str)
                    }
                }
            }
        }
        .navigationTitle(person.name)
    }
}

I'm not reading the string or anything to make the value dynamic because I wanted to simplify the problem as much as I could. This would eventually be replaced by a struct or class, whichever is appropriate.

When I click the button in the PersonView nothing is appended to the array and no errors are shown in the debug output. What am I doing wrong? What is the idiomatic way of managing collections of objects within another object?

Basically, I want to have a list of top level entities (Person) that have activities they want to do (specific to each Person) and characteristic that affect those activities (also specific to each Person). For instance, Joe is smart but weak and likes to play chess and lift weights. His chess attempts have 20% better chance of success but his bench presses are -15% likely to succeed. Sally just likes to run which takes advantage of her 30% leg speed.

Here is the rest of the application's code: (app)

import SwiftUI

@main
struct bindingsApp: App {
    @State private var people:[Person] = []
    
    var body: some Scene {
        WindowGroup {
            ContentView(people: $people)
        }
    }
}

(content view)

struct ContentView: View {
    @Binding var people: [Person]
    @State private var isPresentingTheEditView: Bool = false
    @State private var tmpPerson: Person = Person(name: "")

    var body: some View {
        NavigationStack {
            List {
                Section(header: HStack {
                    Text("People")
                    Spacer()
                    Button(action: {
                        isPresentingTheEditView = true
                    }) {
                        Image(systemName: "plus")
                    }
                }) {
                    ForEach($people) { $person in
                        NavigationLink(destination: PersonView(person: $person)) {
                            Text(person.name)
                        }
                    }
                }
            }
            .sheet(isPresented: $isPresentingTheEditView) {
                PersonNewSheet(targetPeople: $people, isPresentingTheEditView: $isPresentingTheEditView)
            }
        }
    }
}

(new person sheet)

struct PersonNewSheet: View {
    @State private var name: String = ""
    
    @Binding var targetPeople: [Person]
    @Binding var isPresentingTheEditView: Bool
    
    var body: some View {
        NavigationStack {
            PersonEditView(name: $name)
                .toolbar {
                    ToolbarItem(placement: .cancellationAction) {
                        Button("Dismiss") {
                            isPresentingTheEditView = false
                        }
                    }
                    ToolbarItem(placement: .confirmationAction) {
                        Button("Add") {
                            isPresentingTheEditView = false
                            targetPeople.append(Person(name: name, strings: ["bar"]))
                        }
                    }
                }
        }
    }
}

(person form)

struct PersonEditView: View {
    @Binding var name: String
    
    var body: some View {
        NavigationView {
            Form {
                Section {
                    TextField("Name", text: $name)
                }
            }
        }
        .navigationTitle("Person Edit")
    }
}

Thanks!

I have a person object with a list of strings

struct Person: Identifiable, Hashable {
    var id: UUID = UUID()
    var name: String
    var strings: [String] = []
    
    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.id == rhs.id
    }

    func hash(into hasher: inout Hasher) {
        hasherbine(id)
        hasherbine(name)
    }
}

which is stored in a @State array variable in the app struct and from my PersonView I'd like to add elements to the strings array.

struct PersonView: View {
    @Binding var person: Person

    var body: some View {
        List {
            Section(header: HStack {
                Text("Strings")
                Spacer()
                Button(action: {
                    print("BEFORE", person.strings)
                    person.strings.append("foo")
                    print("AFTER", person.strings)
                }) {
                    Image(systemName: "plus")
                }
            }) {
                ForEach($person.strings, id: \.self) { $str in
                    NavigationLink(destination: Text(str)) {
                        Text(str)
                    }
                }
            }
        }
        .navigationTitle(person.name)
    }
}

I'm not reading the string or anything to make the value dynamic because I wanted to simplify the problem as much as I could. This would eventually be replaced by a struct or class, whichever is appropriate.

When I click the button in the PersonView nothing is appended to the array and no errors are shown in the debug output. What am I doing wrong? What is the idiomatic way of managing collections of objects within another object?

Basically, I want to have a list of top level entities (Person) that have activities they want to do (specific to each Person) and characteristic that affect those activities (also specific to each Person). For instance, Joe is smart but weak and likes to play chess and lift weights. His chess attempts have 20% better chance of success but his bench presses are -15% likely to succeed. Sally just likes to run which takes advantage of her 30% leg speed.

Here is the rest of the application's code: (app)

import SwiftUI

@main
struct bindingsApp: App {
    @State private var people:[Person] = []
    
    var body: some Scene {
        WindowGroup {
            ContentView(people: $people)
        }
    }
}

(content view)

struct ContentView: View {
    @Binding var people: [Person]
    @State private var isPresentingTheEditView: Bool = false
    @State private var tmpPerson: Person = Person(name: "")

    var body: some View {
        NavigationStack {
            List {
                Section(header: HStack {
                    Text("People")
                    Spacer()
                    Button(action: {
                        isPresentingTheEditView = true
                    }) {
                        Image(systemName: "plus")
                    }
                }) {
                    ForEach($people) { $person in
                        NavigationLink(destination: PersonView(person: $person)) {
                            Text(person.name)
                        }
                    }
                }
            }
            .sheet(isPresented: $isPresentingTheEditView) {
                PersonNewSheet(targetPeople: $people, isPresentingTheEditView: $isPresentingTheEditView)
            }
        }
    }
}

(new person sheet)

struct PersonNewSheet: View {
    @State private var name: String = ""
    
    @Binding var targetPeople: [Person]
    @Binding var isPresentingTheEditView: Bool
    
    var body: some View {
        NavigationStack {
            PersonEditView(name: $name)
                .toolbar {
                    ToolbarItem(placement: .cancellationAction) {
                        Button("Dismiss") {
                            isPresentingTheEditView = false
                        }
                    }
                    ToolbarItem(placement: .confirmationAction) {
                        Button("Add") {
                            isPresentingTheEditView = false
                            targetPeople.append(Person(name: name, strings: ["bar"]))
                        }
                    }
                }
        }
    }
}

(person form)

struct PersonEditView: View {
    @Binding var name: String
    
    var body: some View {
        NavigationView {
            Form {
                Section {
                    TextField("Name", text: $name)
                }
            }
        }
        .navigationTitle("Person Edit")
    }
}

Thanks!

Share Improve this question edited Feb 1 at 7:59 Joakim Danielson 52.1k5 gold badges33 silver badges71 bronze badges asked Feb 1 at 4:21 GeneGene 5644 silver badges7 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

ForEach expect a collection of unique (preferably Identifiable) items, see ForEach.
Using ForEach($person.strings, id: \.self) is not a good idea, since the array [strings] could contain multiple same string.

Note, with your struct Person don't try to cook up Hashable (or Equatable) yourself, let the system do it for you automatically. This is the source of your original problem (static func == ...), remove those funcs and it will work.

Note also, NavigationView is deprecated, use NavigationStack.

Try this updated code where Person has a var items: [Item] and each Item is unique and Identifiable to hold your string, ready for any ForEach.

If you are targeting iOS17+, have a look at this option for Managing model data in your app

struct PersonView: View {
    @Binding var person: Person

    var body: some View {
        List {
            Section(header: HStack {
                Text("Strings")
                Spacer()
                Button(action: {
                    print("BEFORE", person.items)
                    person.items.append(Item(string: "bar")) // <--- here
                    print("AFTER", person.items)
                }) {
                    Image(systemName: "plus")
                }
            }) {
                ForEach($person.items) { $item in   // <--- here, $ not really needed in this example
                    NavigationLink(destination: Text(item.string)) {  // <--- here
                        Text(item.string)  // <--- here
                    }
                }
            }
        }
        .navigationTitle(person.name)
    }
}

struct PersonNewSheet: View {
    @State private var name: String = ""
    
    @Binding var targetPeople: [Person]
    @Binding var isPresentingTheEditView: Bool
    
    var body: some View {
        NavigationStack {
            PersonEditView(name: $name)
                .toolbar {
                    ToolbarItem(placement: .cancellationAction) {
                        Button("Dismiss") {
                            isPresentingTheEditView = false
                        }
                    }
                    ToolbarItem(placement: .confirmationAction) {
                        Button("Add") {
                            isPresentingTheEditView = false
                            targetPeople.append(Person(name: name, items: [Item(string: "bar")]))  // <--- here
                        }
                    }
                }
        }
    }
}

struct PersonEditView: View {
    @Binding var name: String
    
    var body: some View {
        NavigationStack { // <--- here
            Form {
                Section {
                    TextField("Name", text: $name)
                }
            }
        }
        .navigationTitle("Person Edit")
    }
}

struct Person: Identifiable { // <--- here
    let id: UUID = UUID()
    var name: String
    var items: [Item] = [] // <--- here
}

struct Item: Identifiable {   // <--- here
    let id: UUID = UUID()
    var string: String
}
发布评论

评论列表(0)

  1. 暂无评论