You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中无法给绑定变量内的数组追加元素问题求助

SwiftUI中无法修改结构体内部数组的问题及最佳实践

问题描述

我定义了一个包含字符串数组的Person结构体,将其存储在App的@State数组变量中,在PersonView里点击按钮尝试给strings数组追加元素,但点击后数组未在视图中更新,调试输出无报错。同时想了解SwiftUI中管理对象内集合的惯用方式。

实际需求:实现一个顶层实体(Person)列表,每个实体拥有专属的待执行活动及影响活动的特性。例如Joe聪明但体弱,喜欢下棋和举重,下棋成功率+20%但卧推成功率-15%;Sally喜欢跑步,可利用她30%的腿部速度优势。

相关代码

Person结构体

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) {
        hasher.combine(id)
        hasher.combine(name)
    }
}

PersonView视图

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)
    }
}

应用其余代码

App入口

import SwiftUI

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

ContentView视图

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)
            }
        }
    }
}

PersonNewSheet视图

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"]))
                        }
                    }
                }
        }
    }
}

PersonEditView视图

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

问题原因

Person是值类型(结构体),虽然使用了@Binding var person: Person,但直接修改person.strings时,SwiftUI无法检测到结构体内部数组的变化——绑定监听的是整个Person实例的替换,而非内部属性的修改。此时控制台打印能看到数组已追加元素,但视图因未感知到绑定值变化而不会刷新,导致看起来数组没更新。

解决方法&SwiftUI管理对象内集合的惯用方式

方式1:替换整个Person实例(适合简单场景)

不直接修改结构体内部数组,而是创建新的Person实例替换原绑定对象,触发SwiftUI的视图更新:

Button(action: {
    print("BEFORE", person.strings)
    var newPerson = person
    newPerson.strings.append("foo")
    person = newPerson
    print("AFTER", person.strings)
}) {
    Image(systemName: "plus")
}

方式2:将Person改为引用类型(类)(推荐用于复杂实体场景)

针对你的实际需求(多属性、多集合的复杂实体),将Person改为类并遵循ObservableObject,用@Published标记需要监听变化的集合属性,是SwiftUI中管理复杂状态的惯用方式:

class Person: Identifiable, Hashable, ObservableObject {
    let id: UUID = UUID()
    var name: String
    @Published var strings: [String] = []
    
    init(name: String, strings: [String] = []) {
        self.name = name
        self.strings = strings
    }
    
    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.id == rhs.id
    }

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

对应视图修改:

  • ContentView中ForEach直接传递类实例:
ForEach(people) { person in
    NavigationLink(destination: PersonView(person: person)) {
        Text(person.name)
    }
}
  • PersonView使用@ObservedObject监听实例变化:
struct PersonView: View {
    @ObservedObject 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)
    }
}

@Published会自动发布数组的变化,SwiftUI会同步更新视图,后续扩展skills、activities等属性时,只需给对应集合添加@Published即可,代码更易维护。

方式3:使用Binding投影(适合坚持用结构体的场景)

若必须保留结构体,可通过手动创建数组的绑定来实现,但代码复杂度较高,不如前两种方式直观,不推荐用于复杂场景。

针对实际需求的建议

你的场景涉及多实体、多子属性集合,**引用类型(类+ObservableObject+@Published)**是最贴合SwiftUI设计理念的方案,能高效管理实体内部状态的变化,降低后续扩展的代码复杂度。

内容的提问来源于stack exchange,提问作者Gene

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 17:27:05