在SwifUI中使用MVVM
。我的目标是在enum
中拥有一个ViewModel
状态属性,以便View
可以根据状态属性自行进行调整。状态可以是:idle
,busy
,done
和error
。在done
上,我想使用NavigationLink
导航到另一个屏幕,但是问题是它正在期待Binding<Bool>
,因此我想不出一种将枚举状态映射为bool的方法。
这是简化的代码:
struct LoginView: View {
@ObservedObject private var viewModel: LoginViewModel
@ViewBuilder
var body: some View {
...
// success state
NavigationLink(destination: HomeFactory().make(), isActive: self.$viewModel.state /* <---- some sort of mapping should come here */){ EmptyView() }
...
}
}
希望我确实缺少一些基本知识,并且可以轻松地以优雅的方式实现。
编辑:
似乎可以使用下一个方法来实现:
NavigationLink(destination: HomeFactory().make(), tag: .done, selection: self.$viewModel.viewState, label: { EmptyView() })
但是我收到一个错误,但我无法弄清楚出什么问题了:Cannot convert value of type 'Binding<ViewState>' to expected argument type 'Binding<_?>'
代码如下:
final class LoginViewModel: ObservableObject {
@Published var viewState: ViewState = .idle
func begin() {
..
self.viewState = .done
..
}
}
struct LoginView: View {
@ObservedObject private var viewModel: LoginViewModel
@ViewBuilder
var body: some View {
..
NavigationLink(destination: HomeFactory().make(), tag: .done, selection: self.$viewModel.viewState, label: { EmptyView() })
..
}
更新:
我非常亲密。 vm中的ViewState
应该是可选的:
@Published var viewState: ViewState? = .idle
答案 0 :(得分:1)
没有一种优雅的方法可以在视图中进行映射。
但是,在您的LoginViewModel
中,您可以拥有一个@Published变量,该变量在状态更新时会被设置。
这里是一个例子:
class LoginViewModel: ObservableObject {
@Published var shouldNavigate = false
var state: State = .idle {
didSet {
self.shouldNavigate = state == .done
}
}
}
然后将您的NavigationLink
更改为:
NavigationLink(destination: HomeFactory().make(), isActive: self.$viewModel.shouldNavigate){ EmptyView() }
编辑:
您可以使用导航链接根据状态或其他枚举进行导航,如下所示:
NavigationLink(destination: HomeFactory().make(), tag: State.done, selection: self.$state){ EmptyView() }
并将您的虚拟机状态定义更新为:
@Published var state: State = .idle
答案 1 :(得分:0)
有同样的问题。这是我所做的:
final class LoginViewModel: ObservableObject {
@Published var viewState: ViewState = .idle
func begin() {
..
self.viewState = .done
..
}
}
struct LoginView: View {
@ObservedObject private var viewModel: LoginViewModel
@ViewBuilder
var body: some View {
..
NavigationLink(destination: HomeFactory().make(), tag: .done, selection:
Binding<Bool>(get: { viewModel.viewState.isPresentable }, set: { _ in viewModel.viewState = .idle })), label: { EmptyView() })
..
}
答案 2 :(得分:0)
转换的问题在于它们中的大多数是单向的,并且绑定要求其内容既可读又可写。因此,即使您像
一样扩展您的枚举enum State {
case idle, busy, done
var isDone: Bool { self == .done }
}
您仍然无法绑定到 $viewModel.state.isDone
,因为该属性是计算出来的。通过添加 set
使其可写是不可行的,因为您不想通过 isDone
更改枚举值。
然而,这并不意味着它不能完成。您可以在 map
上定义 Binding
,并欺骗系统您有双向通信:
extension Binding {
func map<NewValue>(_ transform: @escaping (Value) -> NewValue) -> Binding<NewValue> {
Binding<NewValue>(get: { transform(wrappedValue) }, set: { _ in })
}
}
,您可以在导航链接中使用它,或者在任何需要枚举中的布尔绑定的地方使用它。
NavigationLink(destination: HomeFactory().make(), isActive: $viewModel.state.isDone)
不过,需要注意的是,映射的 Binding
具有欺骗性的 API,它说它也可以更新值,而实际上它不能。然而,对于大多数使用布尔值来配置可用性的情况,这应该没问题。小心,但不要在上下文之外传播该 Binding
,因为该实例的其他使用者可能不知道其限制。