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
永远不会为空,因为我有那些静态文本元素。我还读到如果你使用没有 id 的 ForEach
它可能会失败 - 但你可以看到我正在使用 id 所以我有点不知所措。
我在 onAppear()
中有打印语句所以我知道它运行并成功设置了 @Published accounts
但是查看 UI 并在 ForEach
中放置断点我可以看到视图没有更新。但是,如果我在我的应用程序中导航到其他地方,然后返回到 ParentView
,那么由于 @Published accounts
是非空的(已经获取),它会完美更新。
看起来你 运行 遇到了问题,因为观察到的对象有两个级别,model: AccountsSummaryViewModel
包含 accounts: [AccountIdentifiable]
。
SwiftUI 只会观看一个级别,导致您的 ParentView
在 accounts
设置 多个 [=25] 时不会更新=] UI 等级下降。
作为 , one option is to use PublishedObject 通过 Xcode 中的 Swift 包管理器。将 SummaryView
中的 model
更改为 @PublishedObject
可能是解决此问题所需的全部。
它不起作用的原因是我在 SummaryView 中使用 @ObservedObject
而不是 @StateObject
。进行更改解决了问题。
我有一个 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
永远不会为空,因为我有那些静态文本元素。我还读到如果你使用没有 id 的 ForEach
它可能会失败 - 但你可以看到我正在使用 id 所以我有点不知所措。
我在 onAppear()
中有打印语句所以我知道它运行并成功设置了 @Published accounts
但是查看 UI 并在 ForEach
中放置断点我可以看到视图没有更新。但是,如果我在我的应用程序中导航到其他地方,然后返回到 ParentView
,那么由于 @Published accounts
是非空的(已经获取),它会完美更新。
看起来你 运行 遇到了问题,因为观察到的对象有两个级别,model: AccountsSummaryViewModel
包含 accounts: [AccountIdentifiable]
。
SwiftUI 只会观看一个级别,导致您的 ParentView
在 accounts
设置 多个 [=25] 时不会更新=] UI 等级下降。
作为 SummaryView
中的 model
更改为 @PublishedObject
可能是解决此问题所需的全部。
它不起作用的原因是我在 SummaryView 中使用 @ObservedObject
而不是 @StateObject
。进行更改解决了问题。