Swift Combine - @Published 属性数组

Posted

技术标签:

【中文标题】Swift Combine - @Published 属性数组【英文标题】:Swift Combine - @Published property Array 【发布时间】:2019-08-04 16:52:23 【问题描述】:

我目前正在使用 SwiftUI 和 Combine 做一个项目。我正在使用 Xcode11 Beta 5。我想获取我的 Github 存储库,显示它们,然后能够为其中的一些添加书签。

我能够获取它们并显示它们。我正在使用结合 @Published 属性包装器来更新我的视图。到现在为止,一切都按预期进行。

所以我继续下一步,为存储库添加书签。我想使用 Realm 来持久化这些存储库。

我的问题是,我有一个 Observable 类,它有一个 @Published 存储库数组。 Repository 是一个可解码的类。在我的 Repository 类中,我有一个简单的类属性 isFavorite 现在设置为 false。

在我的存储库列表中,当我单击存储库以查看详细信息时,我希望能够将其加入书签。所以我从我的存储库数组中通过它的 id 检索它的索引,并将它的属性 isFavorite 设置为 true。但我的观点没有更新。我的意思是我有一个条件渲染,它被固定为假。

这是我的代码:

import SwiftUI
import Combine

struct RepositoryList : View 
    @ObservedObject var store: Store

    func move(from source: IndexSet, to destination: Int) 
        store.repositories.swapAt(source.first!, destination)
    

    @State private var isTapped = false
    @State private var bgColor = Color.white

    var body: some View 
        NavigationView 
            VStack 
                List 
                    Section(header: Text("\(String(store.repositories.count)) repositories")) 
                        ForEach(store.repositories)  repository in
                            NavigationLink(destination: RepositoryDetail(store: self.store, repository: repository)) 
                                RepositoryRow(repository: repository)
                            .padding(.vertical, 8.0)
                        .onDelete  index in
                            self.store.repositories.remove(at: index.first!)
                        .onMove(perform: move)
                    
                
            
        .navigationBarTitle("Github user").navigationBarItems(trailing: EditButton())
    


struct RepositoryDetail : View 
    @ObservedObject var store: Store
    var repository: Repository

    var repoIndex: Int 
        let repoIndex = store.repositories.firstIndex(where: $0.id == repository.id)!
        print("### IS FAVORITE \(String(store.repositories[repoIndex].isFavorite))")
        return repoIndex
    

    var body: some View 
        VStack(alignment: .leading) 
            Text(String(store.repositories[repoIndex].isFavorite))
            Button(action:  self.store.repositories[self.repoIndex].isFavorite.toggle() ) 
                if (self.store.repositories[self.repoIndex].isFavorite) 
                    Image(systemName: "star.fill").foregroundColor(Color.yellow)
                 else 
                    Image(systemName: "star").foregroundColor(Color.gray)
                
            
        .navigationBarTitle(Text(repository.name), displayMode: .inline)
    


class Store: ObservableObject 
    private var cancellable: AnyCancellable? = nil
    @Published var repositories: [Repository] = []

    init () 
        var urlComponents = URLComponents(string: "https://api.github.com/users/Hurobaki/repos")!
        var request = URLRequest(url: urlComponents.url!)
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")

        cancellable = URLSession.shared.send(request: request)
            .decode(type: [Repository].self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion:  completion in
                print("### .sink() received the completion", String(describing: completion))
                switch completion 
                case .finished:
                    break
                case .failure(_):
                   print("### ERROR")
                
            , receiveValue:  repositories in
                self.repositories = repositories
            )

    


class Repository: Decodable, Identifiable 
    var id: Int = 0
    var name: String = ""
    var desc: String? = nil

    var isFavorite = false

    private enum CodingKeys: String, CodingKey 
        case id, name
        case desc = "description"
    

我不知道为什么我的 Button 组件从不显示 Image(systemName: "star.fill").foregroundColor(Color.yellow) 当我打印 isFavorite 属性时,它的值从 true 变为 false,反之亦然,但我的视图没有更新。

我从 Apple Developer 那里完成了教程,他们做了完全相同的事情,所以我不知道为什么会出现这个错误,我错过了什么?

非常感谢一些帮助和/或对我的代码的评论:)

谢谢

PS:为了不发布更长的代码,我已将其上传到 Pastebin https://pastebin.com/zjDwQSGq

【问题讨论】:

【参考方案1】:

感谢您发布完整代码。它使故障排除变得更加容易。

你的问题很简单。您将 Repository 设为类,而不是结构。这意味着当您更改.isFavorite 时,数组中的对象不会更改,因为它是一个引用类型。如果您更改并将 Repository 改为结构,您的代码将起作用,因为当您更改 .isFavorite 时,您实际上是在改变一个值,而 Publisher 会捕获它。

如果您不知道引用类型和值类型之间的区别,请查看此页面:https://developer.apple.com/swift/blog/?id=10,其中详细解释了它。但请记住,类是引用类型,结构是值类型。

如果您仍想保留Repository 一个类,您可以这样做,但您需要手动调用您商店的 objectWillChange 主题的 send:

Button(action: 
                self.store.repositories[self.repoIndex].isFavorite.toggle()
                self.store.objectWillChange.send()
            ) 
                if (self.store.repositories[self.repoIndex].isFavorite) 
                    Image(systemName: "star.fill").foregroundColor(Color.yellow)
                 else 
                    Image(systemName: "star").foregroundColor(Color.gray)
                
            

【讨论】:

感谢您的回答。我没有考虑结构和类的区别......实际上我需要将 Repository 保留为一个类,因为我将使用 RealmSwift 来持久化数据。我会尝试您的解决方案并与您保持联系! :) 效果很好!感谢您的回答和您的工作示例! 优秀的代码。为了澄清,“objectWillChange.send()”需要附加到包含数组的对象上,而不是像我第一次尝试的那样,数组元素。我发现它放在要更改元素的视图的 onAppear() 中时有效。

以上是关于Swift Combine - @Published 属性数组的主要内容,如果未能解决你的问题,请参考以下文章

使用 Swift 和 Combine 链接 + 压缩多个网络请求

Xcode 11 中 Swift Combine.framework 的可选链接

Swift/Combine - 将过滤后的对象分配给类的属性

Swift Combine:没有“distinct”运算符?

Swift Combine - @Published 属性数组

如何在 Swift 中使用 Combine 读取 JSON 错误对象的属性值?