SwiftUI 视图看不到 属性 个标有 @Published 的 ObservableObject
SwiftUI View don't see property of ObservableObject marked with @Published
我正在使用 SwiftUI 和 VIPER 编写我的应用程序。为了保留 viper(可测试性、协议等)和 SwiftUI 反应性的想法,我想再添加 1 层 - ViewModel。我的演示者将从交互器询问数据并将其放入 ViewModel,然后视图将只读取此 value.I 检查将数据放入视图模型的方法是否有效 - 是的。但是我的视图只是看不到视图模型的 属性(显示空列表),即使它符合 ObservableObject 并且 属性 标记为已发布。更有趣的是,如果我将数据存储在演示者中,并用已发布和可观察的对象标记它,它将起作用。提前致谢!
class BeersListPresenter: BeersListPresenterProtocol, ObservableObject{
var interactor: BeersListInteractorProtocol
@ObservedObject var viewModel = BeersListViewModel()
init(interactor: BeersListInteractorProtocol){
self.interactor = interactor
}
func loadList(at page: Int){
interactor.loadList(at: page) { beers in
DispatchQueue.main.async {
self.viewModel.beers.append(contentsOf: beers)
print(self.viewModel.beers)
}
}
}
class BeersListViewModel:ObservableObject{
@Published var beers = [Beer]()
}
struct BeersListView: View{
var presenter : BeersListPresenterProtocol
@StateObject var viewModel : BeersListViewModel
var body: some View {
NavigationView{
List{
ForEach(viewModel.beers, id: \.id){ beer in
HStack{
VStack(alignment: .leading){
Text(beer.name)
.font(.headline)
Text("Vol: \(presenter.formattedABV(beer.abv))")
.font(.subheadline)
}
一些注意事项。
您不能链接 ObservableObject
,因此 class
中的 @ObservedObject var viewModel = BeersListViewModel()
将不起作用。
第二,你有 2 个 ViewModel,一个在 View
中,一个在 Presenter
中,你必须选择一个。一个人不会知道另一个人在做什么。
以下是如何让您的代码正常工作
import SwiftUI
struct Beer: Identifiable{
var id: UUID = UUID()
var name: String = "Hops"
var abv: String = "H"
}
protocol BeersListInteractorProtocol{
func loadList(at: Int, completion: ([Beer])->Void)
}
struct BeersListInteractor: BeersListInteractorProtocol{
func loadList(at: Int, completion: ([Beer]) -> Void) {
completion([Beer(), Beer(), Beer()])
}
}
protocol BeersListPresenterProtocol: ObservableObject{
var interactor: BeersListInteractorProtocol { get set }
var viewModel : BeersListViewModel { get set }
func formattedABV(_ abv: String) -> String
func loadList(at page: Int)
}
class BeersListPresenter: BeersListPresenterProtocol, ObservableObject{
var interactor: BeersListInteractorProtocol
//You can't chain `ObservedObject`s
@Published var viewModel = BeersListViewModel()
init(interactor: BeersListInteractorProtocol){
self.interactor = interactor
}
func loadList(at page: Int){
interactor.loadList(at: page) { beers in
DispatchQueue.main.async {
self.viewModel.beers.append(contentsOf: beers)
print(self.viewModel.beers)
}
}
}
func formattedABV(_ abv: String) -> String{
"**\(abv)**"
}
}
//Change to struct
struct BeersListViewModel{
var beers = [Beer]()
}
struct BeerListView<T: BeersListPresenterProtocol>: View{
//This is what will trigger view updates
@StateObject var presenter : T
//The viewModel is in the Presenter
//@StateObject var viewModel : BeersListViewModel
var body: some View {
NavigationView{
List{
Button("load list", action: {
presenter.loadList(at: 1)
})
ForEach(presenter.viewModel.beers, id: \.id){ beer in
HStack{
VStack(alignment: .leading){
Text(beer.name)
.font(.headline)
Text("Vol: \(presenter.formattedABV(beer.abv))")
.font(.subheadline)
}
}
}
}
}
}
}
struct BeerListView_Previews: PreviewProvider {
static var previews: some View {
BeerListView(presenter: BeersListPresenter(interactor: BeersListInteractor()))
}
}
现在我无论如何都不是 VIPER 专家,但我认为你在混淆概念。在 VIPER 中混合 MVVM 和 VIPER.Because presenter
存在于 View/ViewModel 之下,不在同一水平。
我刚才找到了this教程。它适用于 UIKit,但如果我们使用 ObservableObject
替代 UIViewController
并且 SwiftUI View
作为故事板。
它使作为 ObservableObject
的 ViewModel
和作为 SwiftUI struct
的 View
成为单个 View
VIPER 层。
你会得到这样的代码
protocol BeersListPresenterProtocol{
var interactor: BeersListInteractorProtocol { get set }
func formattedABV(_ abv: String) -> String
func loadList(at: Int, completion: ([Beer]) -> Void)
}
struct BeersListPresenter: BeersListPresenterProtocol{
var interactor: BeersListInteractorProtocol
init(interactor: BeersListInteractorProtocol){
self.interactor = interactor
}
func loadList(at: Int, completion: ([Beer]) -> Void) {
interactor.loadList(at: at) { beers in
completion(beers)
}
}
func formattedABV(_ abv: String) -> String{
"**\(abv)**"
}
}
protocol BeersListViewProtocol: ObservableObject{
var presenter: BeersListPresenterProtocol { get set }
var beers: [Beer] { get set }
func loadList(at: Int)
func formattedABV(_ abv: String) -> String
}
class BeersListViewModel: BeersListViewProtocol{
@Published var presenter: BeersListPresenterProtocol
@Published var beers: [Beer] = []
init(presenter: BeersListPresenterProtocol){
self.presenter = presenter
}
func loadList(at: Int) {
DispatchQueue.main.async {
self.presenter.loadList(at: at, completion: {beers in
self.beers = beers
})
}
}
func formattedABV(_ abv: String) -> String {
presenter.formattedABV(abv)
}
}
struct BeerListView<T: BeersListViewProtocol>: View{
@StateObject var viewModel : T
var body: some View {
NavigationView{
List{
Button("load list", action: {
viewModel.loadList(at: 1)
})
ForEach(viewModel.beers, id: \.id){ beer in
HStack{
VStack(alignment: .leading){
Text(beer.name)
.font(.headline)
Text("Vol: \(viewModel.formattedABV(beer.abv))")
.font(.subheadline)
}
}
}
}
}
}
}
struct BeerListView_Previews: PreviewProvider {
static var previews: some View {
BeerListView(viewModel: BeersListViewModel(presenter: BeersListPresenter(interactor: BeersListInteractor())))
}
}
如果你不想将 VIPER View Layer 分成 ViewModel
和 SwiftUI
View
你可以选择像下面的代码那样做更难替换 UI 并且通常不是一个好习惯。因为当 interactor
.
有更新时,您将无法从 presenter
调用方法
struct BeerListView<T: BeersListPresenterProtocol>: View, BeersListViewProtocol{
var presenter: BeersListPresenterProtocol
@State var beers: [Beer] = []
var body: some View {
NavigationView{
List{
Button("load list", action: {
loadList(at: 1)
})
ForEach(beers, id: \.id){ beer in
HStack{
VStack(alignment: .leading){
Text(beer.name)
.font(.headline)
Text("Vol: \(formattedABV(beer.abv))")
.font(.subheadline)
}
}
}
}
}
}
func loadList(at: Int) {
DispatchQueue.main.async {
self.presenter.loadList(at: at, completion: {beers in
self.beers = beers
})
}
}
func formattedABV(_ abv: String) -> String {
presenter.formattedABV(abv)
}
}
struct BeerListView_Previews: PreviewProvider {
static var previews: some View {
BeerListView<BeersListPresenter>(presenter: BeersListPresenter(interactor: BeersListInteractor()))
}
}
我正在使用 SwiftUI 和 VIPER 编写我的应用程序。为了保留 viper(可测试性、协议等)和 SwiftUI 反应性的想法,我想再添加 1 层 - ViewModel。我的演示者将从交互器询问数据并将其放入 ViewModel,然后视图将只读取此 value.I 检查将数据放入视图模型的方法是否有效 - 是的。但是我的视图只是看不到视图模型的 属性(显示空列表),即使它符合 ObservableObject 并且 属性 标记为已发布。更有趣的是,如果我将数据存储在演示者中,并用已发布和可观察的对象标记它,它将起作用。提前致谢!
class BeersListPresenter: BeersListPresenterProtocol, ObservableObject{
var interactor: BeersListInteractorProtocol
@ObservedObject var viewModel = BeersListViewModel()
init(interactor: BeersListInteractorProtocol){
self.interactor = interactor
}
func loadList(at page: Int){
interactor.loadList(at: page) { beers in
DispatchQueue.main.async {
self.viewModel.beers.append(contentsOf: beers)
print(self.viewModel.beers)
}
}
}
class BeersListViewModel:ObservableObject{
@Published var beers = [Beer]()
}
struct BeersListView: View{
var presenter : BeersListPresenterProtocol
@StateObject var viewModel : BeersListViewModel
var body: some View {
NavigationView{
List{
ForEach(viewModel.beers, id: \.id){ beer in
HStack{
VStack(alignment: .leading){
Text(beer.name)
.font(.headline)
Text("Vol: \(presenter.formattedABV(beer.abv))")
.font(.subheadline)
}
一些注意事项。
您不能链接 ObservableObject
,因此 class
中的 @ObservedObject var viewModel = BeersListViewModel()
将不起作用。
第二,你有 2 个 ViewModel,一个在 View
中,一个在 Presenter
中,你必须选择一个。一个人不会知道另一个人在做什么。
以下是如何让您的代码正常工作
import SwiftUI
struct Beer: Identifiable{
var id: UUID = UUID()
var name: String = "Hops"
var abv: String = "H"
}
protocol BeersListInteractorProtocol{
func loadList(at: Int, completion: ([Beer])->Void)
}
struct BeersListInteractor: BeersListInteractorProtocol{
func loadList(at: Int, completion: ([Beer]) -> Void) {
completion([Beer(), Beer(), Beer()])
}
}
protocol BeersListPresenterProtocol: ObservableObject{
var interactor: BeersListInteractorProtocol { get set }
var viewModel : BeersListViewModel { get set }
func formattedABV(_ abv: String) -> String
func loadList(at page: Int)
}
class BeersListPresenter: BeersListPresenterProtocol, ObservableObject{
var interactor: BeersListInteractorProtocol
//You can't chain `ObservedObject`s
@Published var viewModel = BeersListViewModel()
init(interactor: BeersListInteractorProtocol){
self.interactor = interactor
}
func loadList(at page: Int){
interactor.loadList(at: page) { beers in
DispatchQueue.main.async {
self.viewModel.beers.append(contentsOf: beers)
print(self.viewModel.beers)
}
}
}
func formattedABV(_ abv: String) -> String{
"**\(abv)**"
}
}
//Change to struct
struct BeersListViewModel{
var beers = [Beer]()
}
struct BeerListView<T: BeersListPresenterProtocol>: View{
//This is what will trigger view updates
@StateObject var presenter : T
//The viewModel is in the Presenter
//@StateObject var viewModel : BeersListViewModel
var body: some View {
NavigationView{
List{
Button("load list", action: {
presenter.loadList(at: 1)
})
ForEach(presenter.viewModel.beers, id: \.id){ beer in
HStack{
VStack(alignment: .leading){
Text(beer.name)
.font(.headline)
Text("Vol: \(presenter.formattedABV(beer.abv))")
.font(.subheadline)
}
}
}
}
}
}
}
struct BeerListView_Previews: PreviewProvider {
static var previews: some View {
BeerListView(presenter: BeersListPresenter(interactor: BeersListInteractor()))
}
}
现在我无论如何都不是 VIPER 专家,但我认为你在混淆概念。在 VIPER 中混合 MVVM 和 VIPER.Because presenter
存在于 View/ViewModel 之下,不在同一水平。
我刚才找到了this教程。它适用于 UIKit,但如果我们使用 ObservableObject
替代 UIViewController
并且 SwiftUI View
作为故事板。
它使作为 ObservableObject
的 ViewModel
和作为 SwiftUI struct
的 View
成为单个 View
VIPER 层。
你会得到这样的代码
protocol BeersListPresenterProtocol{
var interactor: BeersListInteractorProtocol { get set }
func formattedABV(_ abv: String) -> String
func loadList(at: Int, completion: ([Beer]) -> Void)
}
struct BeersListPresenter: BeersListPresenterProtocol{
var interactor: BeersListInteractorProtocol
init(interactor: BeersListInteractorProtocol){
self.interactor = interactor
}
func loadList(at: Int, completion: ([Beer]) -> Void) {
interactor.loadList(at: at) { beers in
completion(beers)
}
}
func formattedABV(_ abv: String) -> String{
"**\(abv)**"
}
}
protocol BeersListViewProtocol: ObservableObject{
var presenter: BeersListPresenterProtocol { get set }
var beers: [Beer] { get set }
func loadList(at: Int)
func formattedABV(_ abv: String) -> String
}
class BeersListViewModel: BeersListViewProtocol{
@Published var presenter: BeersListPresenterProtocol
@Published var beers: [Beer] = []
init(presenter: BeersListPresenterProtocol){
self.presenter = presenter
}
func loadList(at: Int) {
DispatchQueue.main.async {
self.presenter.loadList(at: at, completion: {beers in
self.beers = beers
})
}
}
func formattedABV(_ abv: String) -> String {
presenter.formattedABV(abv)
}
}
struct BeerListView<T: BeersListViewProtocol>: View{
@StateObject var viewModel : T
var body: some View {
NavigationView{
List{
Button("load list", action: {
viewModel.loadList(at: 1)
})
ForEach(viewModel.beers, id: \.id){ beer in
HStack{
VStack(alignment: .leading){
Text(beer.name)
.font(.headline)
Text("Vol: \(viewModel.formattedABV(beer.abv))")
.font(.subheadline)
}
}
}
}
}
}
}
struct BeerListView_Previews: PreviewProvider {
static var previews: some View {
BeerListView(viewModel: BeersListViewModel(presenter: BeersListPresenter(interactor: BeersListInteractor())))
}
}
如果你不想将 VIPER View Layer 分成 ViewModel
和 SwiftUI
View
你可以选择像下面的代码那样做更难替换 UI 并且通常不是一个好习惯。因为当 interactor
.
presenter
调用方法
struct BeerListView<T: BeersListPresenterProtocol>: View, BeersListViewProtocol{
var presenter: BeersListPresenterProtocol
@State var beers: [Beer] = []
var body: some View {
NavigationView{
List{
Button("load list", action: {
loadList(at: 1)
})
ForEach(beers, id: \.id){ beer in
HStack{
VStack(alignment: .leading){
Text(beer.name)
.font(.headline)
Text("Vol: \(formattedABV(beer.abv))")
.font(.subheadline)
}
}
}
}
}
}
func loadList(at: Int) {
DispatchQueue.main.async {
self.presenter.loadList(at: at, completion: {beers in
self.beers = beers
})
}
}
func formattedABV(_ abv: String) -> String {
presenter.formattedABV(abv)
}
}
struct BeerListView_Previews: PreviewProvider {
static var previews: some View {
BeerListView<BeersListPresenter>(presenter: BeersListPresenter(interactor: BeersListInteractor()))
}
}