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

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
}

问题原因

  1. 引用类型的局限性:FriendModel是class(引用类型),ModelData里的@Published var friends: [FriendModel]只会在数组本身的元素数量、元素引用发生变化时触发视图更新。单个FriendModel对象内部的isChosen属性变化不会改变数组的引用,因此ModelData不会发布更新通知,视图也就不会刷新。
  2. 视图未监听单个对象变化:当前视图直接访问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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 22:27:20