SwiftUI 可识别的 ForEach 最初从空开始时不会更新

SwiftUI identifiable ForEach doesn't update when initially starting with empty

我有一个 SwiftUI 应用程序,它在视图出现时从后端获取一些信息,然后尝试通过在 ObservableObject 中设置 @Published 变量来更新状态。我遇到的问题是它在第一次获取时没有更新(它保持为空,因为它是用一个空数组初始化的)但是如果我单击另一个视图并返回它会更新(因为信息已经被获取)。

显然,我使用 @Published 的目的是在获取信息后更新视图。这是一个更大的应用程序的一部分,但我有以下内容的简化版本。

首先,我们有一个包含我要更新的视图的父视图。

struct ParentView: View {
    
    var body: some View {
        NavigationView {
        ScrollView {
            VStack {
                SummaryView()
                // In real life I have various forms of summary
                // but to simplify here I will just use this one SummaryView.
                SummaryView()
                SummaryView()
            }
        }
        }
    }
}

这是摘要视图本身:

struct SummaryView: View {
    
    @ObservedObject var model = AccountsSummaryViewModel()
    
    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Text("Accounts")
                    .font(.title)
                
                Spacer()
                
                NavigationLink(
                    destination: AccountView(),
                    label: {
                        Image("RightArrow")
                    })
                
            }
            
            if model.accounts.count > 0 {
                Divider()
            }
            
            // And if I add the following line for debugging
            //Text(model.accounts.count)
            // It remains 0.

            ForEach(model.accounts, id: \.id) { account in
                Text(account.account.text)
            }
        }
        .padding()
        .onAppear() {
            model.onAppear()
        }
    }
}

这是简单的视图模型:

class AccountsSummaryViewModel: ObservableObject, Identifiable {
    
    @Published var accounts: [AccountIdentifiable] = []
    
    func onAppear() {
        
        AccountsService.accounts { (success, error, response) in
            DispatchQueue.main.async {
                // This always succeeds
                if let response = response {
                    
                    // All AccountIdentifiable does is make a struct that is Identifiable (has an account and a var id = UUID())
                    self.accounts = Array(response.accounts.map { AccountIdentifiable(account: [=12=]) }.prefix(3))
                }
            }
        }
        
    }
    
}

这也是 AccountsService 的内容,我会注意到 URL 是本地主机,但我不确定这是否重要:

public struct AccountsService {
    
    public static func accounts(completion: @escaping ((Bool, Error?, AccountsResponse?) -> Void)) {
        guard let url = getAllAccountsURL() else {
            completion(false, nil, nil)
            return
        }

        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.allHTTPHeaderFields = ["Content-Type": "application/json",
                                       BusinessConstants.SET_COOKIE : CredentialsObject.shared.jwt]

        let task = URLSession.shared.dataTask(with: request) { (data, urlResponse, error) in
            guard let data = data else {
                completion(false, error, nil)
                return
            }
            
            guard let response = try? JSONDecoder().decode(AccountsResponse.self, from: data) else {
                completion(false, error, nil)
                return
            }

            // This does successfully decode and return here. 
            completion(true, nil, response)
            
            return
        }

        task.resume()

    }
    
    private static func getAllAccountsURL() -> URL? {
        let address = "\(BusinessConstants.SERVER)/plaid/accounts"

        return URL(string: address)
    }
    
}

我了解到空 ScrollView 存在问题,但是,我的 ScrollView 永远不会为空,因为我有那些静态文本元素。我还读到如果你使用没有 idForEach 它可能会失败 - 但你可以看到我正在使用 id 所以我有点不知所措。

我在 onAppear() 中有打印语句所以我知道它运行并成功设置了 @Published accounts 但是查看 UI 并在 ForEach 中放置断点我可以看到视图没有更新。但是,如果我在我的应用程序中导航到其他地方,然后返回到 ParentView,那么由于 @Published accounts 是非空的(已经获取),它会完美更新。

看起来你 运行 遇到了问题,因为观察到的对象有两个级别,model: AccountsSummaryViewModel 包含 accounts: [AccountIdentifiable]

SwiftUI 只会观看一个级别,导致您的 ParentViewaccounts 设置 多个 [=25] 时不会更新=] UI 等级下降。

作为 , one option is to use PublishedObject 通过 Xcode 中的 Swift 包管理器。将 SummaryView 中的 model 更改为 @PublishedObject 可能是解决此问题所需的全部。

它不起作用的原因是我在 SummaryView 中使用 @ObservedObject 而不是 @StateObject。进行更改解决了问题。