SwiftUI视图动态更新异常:点击按钮后星标状态未刷新
SwiftUI视图不随@Published属性更新的解决方法
问题场景
我要实现SwiftUI视图动态更新,点击按钮切换空心/实心星标。调试发现FriendModel的isChosen值已修改,但视图不同步更新,哪怕给该属性加了@Published修饰符。
相关代码
MakeVideo视图
struct MakeVideo: View { @EnvironmentObject var modelData: ModelData @State private var chosenFriends: [FriendModel] = [] mutating func addFriend(_friend: FriendModel) -> Void { chosenFriends.append(_friend) } var body: some View { VStack { ForEach(modelData.friends) { friend in HStack { ProfilePic(picture: friend.profilepic!) Text("@"+friend.username!) Button("Add", action: friend.toggleChosen) if friend.isChosen { Image(systemName: "star.fill") .foregroundColor(.yellow) } else { Image(systemName: "star") } } } } } } struct MakeVideo_Previews: PreviewProvider { static var previews: some View { MakeVideo() .environmentObject(ModelData()) } }
FriendModel类
class FriendModel: Identifiable, ObservableObject { init(id: Int, username: String, profilepic: String) { self.id = id self.username = username self.profilepic = profilepic } var id: Int? var username: String? var profilepic: String? @Published var isChosen = false func toggleChosen() { print(self.isChosen) self.isChosen = !self.isChosen print(self.isChosen) } }
ModelData类
var allvideos: [VideoModel] = [VideoModel(id: 1, name: "Beach Trip", length: 25, url: "mona"), VideoModel(id: 2, name: "Dogs", length: 10, url:"dog"), VideoModel(id: 3, name: "Concerts", length: 42, url: "hogrider")] var allfriends: [FriendModel] = [FriendModel(id: 1, username: "bpaul18", profilepic: "profilepic"), FriendModel(id: 2, username: "kmill", profilepic: "profilepic"), FriendModel(id: 3, username: "dudeitsdom", profilepic: "profilepic")] final class ModelData: ObservableObject { @Published var videos: [VideoModel] = allvideos @Published var friends: [FriendModel] = allfriends }
问题原因
- 引用类型的局限性:
FriendModel是class(引用类型),ModelData里的@Published var friends: [FriendModel]只会在数组本身的元素数量、元素引用发生变化时触发视图更新。单个FriendModel对象内部的isChosen属性变化不会改变数组的引用,因此ModelData不会发布更新通知,视图也就不会刷新。 - 视图未监听单个对象变化:当前视图直接访问
friend.isChosen,但没有监听单个FriendModel的@Published事件,所以即使属性值变化,视图也无法感知。
解决方案
方案一:将FriendModel改为值类型(struct)
SwiftUI对值类型的变化追踪更直接,当struct内部属性修改时,数组中的元素会被视为新值,触发ModelData的@Published更新。
修改后的FriendModel
struct FriendModel: Identifiable { init(id: Int, username: String, profilepic: String) { self.id = id self.username = username self.profilepic = profilepic } var id: Int? var username: String? var profilepic: String? var isChosen = false mutating func toggleChosen() { print(self.isChosen) self.isChosen = !self.isChosen print(self.isChosen) } }
修改MakeVideo的ForEach绑定
使用$modelData.friends绑定数组元素,确保修改的是ModelData中的原数据:
ForEach($modelData.friends) { $friend in HStack { ProfilePic(picture: friend.profilepic!) Text("@"+friend.username!) Button("Add", action: friend.toggleChosen) if friend.isChosen { Image(systemName: "star.fill") .foregroundColor(.yellow) } else { Image(systemName: "star") } } }
方案二:保持class类型,监听单个对象变化
如果必须使用class(比如需要共享对象引用),可以通过子视图+@ObservedObject来监听单个FriendModel的变化。
创建FriendRow子视图
struct FriendRow: View { @ObservedObject var friend: FriendModel var body: some View { HStack { ProfilePic(picture: friend.profilepic!) Text("@"+friend.username!) Button("Add", action: friend.toggleChosen) if friend.isChosen { Image(systemName: "star.fill") .foregroundColor(.yellow) } else { Image(systemName: "star") } } } }
在MakeVideo中调用子视图
ForEach(modelData.friends) { friend in FriendRow(friend: friend) }
每个FriendRow会自动监听friend的@Published属性变化,触发视图刷新。
方案对比
- 方案一:符合SwiftUI的数据流设计,代码简洁,值类型的变化追踪更可靠,推荐优先使用。
- 方案二:适合需要共享对象引用的场景,但需要额外的子视图或监听逻辑,代码复杂度稍高。
内容的提问来源于stack exchange,提问作者bpaul18
相关产品推荐
相关产品推荐

