SwiftUI/Combine @Published 没有更新@ObservedObject
SwiftUI/Combine no updates to @ObservedObject from @Published
我有一个登录屏幕,然后是一个概览屏幕。当用户成功登录时,登录响应会发回一个项目列表,我想在随后的概览屏幕上显示这些项目。
我可以看到已成功映射响应,但概览视图未收到对@ObservedObject 的任何更新。我可能遗漏了一些明显的东西,但我已经阅读了很多文章,但没有设法使任何工作正常进行。任何帮助表示赞赏!
登录视图
import SwiftUI
struct LogonView: View {
@State private var username: String = ""
@State private var password: String = ""
@State private var inputError: Bool = false
@State private var errorMessage: String = ""
@State private var loading: Bool? = false
@State private var helpShown: Bool = false
@State private var successful: Bool = false
//MARK:- UIView
var body: some View {
NavigationView {
VStack {
VStack {
TextField("Username", text: $username)
.padding(.horizontal)
.disabled(loading! ? true : false)
Rectangle()
.frame(height: 2.0)
.padding(.horizontal)
.foregroundColor(!inputError ? Color("SharesaveLightGrey") : Color("SharesaveError"))
.animation(.easeInOut)
}.padding(.top, 80)
VStack {
SecureField("Password", text: $password)
.padding(.horizontal)
.disabled(loading! ? true : false)
Rectangle()
.frame(height: 2.0)
.padding(.horizontal)
.foregroundColor(!inputError ? Color("SharesaveLightGrey") : Color("SharesaveError"))
.animation(.easeInOut)
}.padding(.top, 40)
if (inputError) {
HStack {
Text(errorMessage)
.padding(.top)
.padding(.horizontal)
.foregroundColor(Color("SharesaveError"))
.animation(.easeInOut)
.lineLimit(nil)
.font(.footnote)
Spacer()
}
}
SharesaveButton(action: {self.submit(user: self.username, pass: self.password)},
label: "Log on",
loading: $loading,
style: .primary)
.padding(.top, 40)
.animation(.interactiveSpring())
NavigationLink(destination: OverviewView(), isActive: $successful) {
Text("")
}
Spacer()
}
.navigationBarTitle("Hello.")
.navigationBarItems(
trailing: Button(action: { self.helpShown = true }) {
Text("Need help?").foregroundColor(.gray)
})
.sheet(isPresented: $helpShown) {
SafariView( url: URL(string: "http://google.com")! )
}
}
}
//MARK:- Functions
private func submit(user: String, pass: String) {
loading = true
inputError = false
let resultsVM = ResultsViewModel()
resultsVM.getGrants(user: user, pass: pass,
successful: { response in
self.loading = false
if ((response) != nil) { self.successful = true }
},
error: { error in
self.inputError = true
self.loading = false
self.successful = false
switch error {
case 401:
self.errorMessage = "Your credentials were incorrect"
default:
self.errorMessage = "Something went wrong, please try again"
}
},
failure: { fail in
self.inputError = true
self.loading = false
self.successful = false
self.errorMessage = "Check your internet connection"
})
}
}
结果视图模型
import Foundation
import Moya
import Combine
import SwiftUI
class ResultsViewModel: ObservableObject {
@Published var results: Results = Results()
func getGrants(
user: String,
pass: String,
successful successCallback: @escaping (Results?) -> Void,
error errorCallback: @escaping (Int) -> Void,
failure failureCallback: @escaping (MoyaError?) -> Void
)
{
let provider = MoyaProvider<sharesaveAPI>()
provider.request(.getSharesave(username: user, password: pass)) { response in
switch response.result {
case .success(let value):
do {
let data = try JSONDecoder().decode(Results.self, from: value.data)
self.results = data
successCallback(data)
} catch {
let errorCode = value.statusCode
errorCallback(errorCode)
}
case .failure(let error):
failureCallback(error)
}
}
}
}
概览视图
import SwiftUI
import Combine
struct OverviewView: View {
@ObservedObject var viewModel: ResultsViewModel = ResultsViewModel()
var body: some View {
let text = "\(self.viewModel.results.market?.sharePrice ?? 0.00)"
return List {
Text(text)
}
}
}
请在初始化 OverviewView
中更改后尝试,如下面的 NavigationLink
NavigationLink(destination: OverviewView(viewModel: self.resultsVM),
isActive: $successful) {
Text("")
}
或
将 results
作为参数传递给 OverviewView
,如下所示
NavigationLink(destination: OverviewView(results: self.resultsVM.results),
isActive: $successful) {
Text("")
}
.....
struct OverviewView: View {
let results: Results
var body: some View {
let text = "\(self.results.market?.sharePrice ?? 0.00)"
return List {
Text(text)
}
}
}
您向 ResultsViewModel
的一个实例提交了请求
private func submit(user: String, pass: String) {
loading = true
inputError = false
let resultsVM = ResultsViewModel() // << here
通过尝试从 ResultsViewModel
的另一个实例读取数据
struct OverviewView: View {
@ObservedObject var viewModel: ResultsViewModel = ResultsViewModel() // << here
但必须是一个实例,所以修改如下
1) 在OverviewView
struct OverviewView: View {
@ObservedObject var viewModel: ResultsViewModel // expects injection
2) 在LogonView
struct LogonView: View {
@ObservedObject var resultsViewModel = ResultsViewModel() // created once
并为 OverviewView
注入相同的实例
NavigationLink(destination: OverviewView(viewModel: self.resultsViewModel), isActive: $successful) {
Text("")
}
并提交
private func submit(user: String, pass: String) {
loading = true
inputError = false
let resultsVM = self.resultsViewModel // use created
我有一个登录屏幕,然后是一个概览屏幕。当用户成功登录时,登录响应会发回一个项目列表,我想在随后的概览屏幕上显示这些项目。
我可以看到已成功映射响应,但概览视图未收到对@ObservedObject 的任何更新。我可能遗漏了一些明显的东西,但我已经阅读了很多文章,但没有设法使任何工作正常进行。任何帮助表示赞赏!
登录视图
import SwiftUI
struct LogonView: View {
@State private var username: String = ""
@State private var password: String = ""
@State private var inputError: Bool = false
@State private var errorMessage: String = ""
@State private var loading: Bool? = false
@State private var helpShown: Bool = false
@State private var successful: Bool = false
//MARK:- UIView
var body: some View {
NavigationView {
VStack {
VStack {
TextField("Username", text: $username)
.padding(.horizontal)
.disabled(loading! ? true : false)
Rectangle()
.frame(height: 2.0)
.padding(.horizontal)
.foregroundColor(!inputError ? Color("SharesaveLightGrey") : Color("SharesaveError"))
.animation(.easeInOut)
}.padding(.top, 80)
VStack {
SecureField("Password", text: $password)
.padding(.horizontal)
.disabled(loading! ? true : false)
Rectangle()
.frame(height: 2.0)
.padding(.horizontal)
.foregroundColor(!inputError ? Color("SharesaveLightGrey") : Color("SharesaveError"))
.animation(.easeInOut)
}.padding(.top, 40)
if (inputError) {
HStack {
Text(errorMessage)
.padding(.top)
.padding(.horizontal)
.foregroundColor(Color("SharesaveError"))
.animation(.easeInOut)
.lineLimit(nil)
.font(.footnote)
Spacer()
}
}
SharesaveButton(action: {self.submit(user: self.username, pass: self.password)},
label: "Log on",
loading: $loading,
style: .primary)
.padding(.top, 40)
.animation(.interactiveSpring())
NavigationLink(destination: OverviewView(), isActive: $successful) {
Text("")
}
Spacer()
}
.navigationBarTitle("Hello.")
.navigationBarItems(
trailing: Button(action: { self.helpShown = true }) {
Text("Need help?").foregroundColor(.gray)
})
.sheet(isPresented: $helpShown) {
SafariView( url: URL(string: "http://google.com")! )
}
}
}
//MARK:- Functions
private func submit(user: String, pass: String) {
loading = true
inputError = false
let resultsVM = ResultsViewModel()
resultsVM.getGrants(user: user, pass: pass,
successful: { response in
self.loading = false
if ((response) != nil) { self.successful = true }
},
error: { error in
self.inputError = true
self.loading = false
self.successful = false
switch error {
case 401:
self.errorMessage = "Your credentials were incorrect"
default:
self.errorMessage = "Something went wrong, please try again"
}
},
failure: { fail in
self.inputError = true
self.loading = false
self.successful = false
self.errorMessage = "Check your internet connection"
})
}
}
结果视图模型
import Foundation
import Moya
import Combine
import SwiftUI
class ResultsViewModel: ObservableObject {
@Published var results: Results = Results()
func getGrants(
user: String,
pass: String,
successful successCallback: @escaping (Results?) -> Void,
error errorCallback: @escaping (Int) -> Void,
failure failureCallback: @escaping (MoyaError?) -> Void
)
{
let provider = MoyaProvider<sharesaveAPI>()
provider.request(.getSharesave(username: user, password: pass)) { response in
switch response.result {
case .success(let value):
do {
let data = try JSONDecoder().decode(Results.self, from: value.data)
self.results = data
successCallback(data)
} catch {
let errorCode = value.statusCode
errorCallback(errorCode)
}
case .failure(let error):
failureCallback(error)
}
}
}
}
概览视图
import SwiftUI
import Combine
struct OverviewView: View {
@ObservedObject var viewModel: ResultsViewModel = ResultsViewModel()
var body: some View {
let text = "\(self.viewModel.results.market?.sharePrice ?? 0.00)"
return List {
Text(text)
}
}
}
请在初始化 OverviewView
中更改后尝试,如下面的 NavigationLink
NavigationLink(destination: OverviewView(viewModel: self.resultsVM),
isActive: $successful) {
Text("")
}
或
将 results
作为参数传递给 OverviewView
,如下所示
NavigationLink(destination: OverviewView(results: self.resultsVM.results),
isActive: $successful) {
Text("")
}
.....
struct OverviewView: View {
let results: Results
var body: some View {
let text = "\(self.results.market?.sharePrice ?? 0.00)"
return List {
Text(text)
}
}
}
您向 ResultsViewModel
private func submit(user: String, pass: String) {
loading = true
inputError = false
let resultsVM = ResultsViewModel() // << here
通过尝试从 ResultsViewModel
struct OverviewView: View {
@ObservedObject var viewModel: ResultsViewModel = ResultsViewModel() // << here
但必须是一个实例,所以修改如下
1) 在OverviewView
struct OverviewView: View {
@ObservedObject var viewModel: ResultsViewModel // expects injection
2) 在LogonView
struct LogonView: View {
@ObservedObject var resultsViewModel = ResultsViewModel() // created once
并为 OverviewView
NavigationLink(destination: OverviewView(viewModel: self.resultsViewModel), isActive: $successful) {
Text("")
}
并提交
private func submit(user: String, pass: String) {
loading = true
inputError = false
let resultsVM = self.resultsViewModel // use created