【问题标题】:SwiftUI: Using @Binding with @Property variable from a protocolSwiftUI:将@Binding 与来自协议的@Property 变量一起使用
【发布时间】:2020-07-10 12:30:59
【问题描述】:

我有一个 ParentView,我想将 @Published 变量传递给一个子视图,在那里它将用作 @Bindable。

这在像这样使用 MyViewModel 时有效:

class MyViewModel: ObservableObject {
    @Published var soundOn = true
}

struct ParentView: View {
    @ObservedObject var myViewModel: MyViewModel
    var body: some View {
        Subview(soundOn: $myViewModel.soundOn)
    }
}

struct Subview: View {
    @Binding var soundOn: Bool
    var body: some View {
        Image(soundOn ? "soundOn" : "soundOff")
    }
}

但我想为所有符合 HasSoundOnOff 协议的 ViewModel 重用 Subview。 使用 HasSoundOnOff 协议时,我无法在协议中定义 @Published,这意味着 ParentView 只能看到一个普通的非@Published 变量,并且不能使用 $viewModel.soundOn。

protocol HasSoundOnOff {
    var soundOn: Bool { get set }
}

class MyViewModel: HasSoundOnOff {
    @Published var soundOn = true
}

struct ParentView<ViewModel: ObservableObject & HasSoundOnOff>: View {
    @ObservedObject var viewModel: ViewModel
    var body: some View {
        Subview(soundOn: $viewModel.soundOn) //<----- error: "Expression type 'Binding<_>' is ambiguous without more context" because protocols can't have @Published and therefor soundOn is treated like a non-@Published variable
    }
}

我可以让 MyViewModel 从定义 @Published 变量的类继承,所以下面的代码可以工作:

class InheritFromPublishedVarClass: ObservableObject {
    @Published var soundOn = true
}

class MyViewModel: ObservableObject & InheritFromPublishedVarClass {}

struct ParentView<ViewModel: ObservableObject & InheritFromPublishedVarClass>: View {
    @ObservedObject var viewModel: ViewModel
    var body: some View {

        Subview(soundOn: $viewModel.soundOn)
    }
}

这意味着我可以重用我的 @Published 变量,但这不会扩展,因为不允许多重继承。

这严重限制了我的代码可重用性。必须有一种方法以更具可扩展性的方式实现这一目标。 有任何想法吗? 要求是让 ParentView 接受 Generic ViewModel 参数。

【问题讨论】:

  • C++ 遗留(多重继承)?没有限制,只需重新考虑您的设计,例如,将InheritFromPublishedVarClass 用作HasSoundOnOffBase 非常基类。协议不能通过语言设计来存储属性(因此不排除 @Published,它只是存储属性的属性包装器)。
  • 谢谢,你能不能给我看一个实际的例子?

标签: swift swiftui


【解决方案1】:

我不完全理解为什么会这样,但似乎如果协议扩展了 ObservableObject,那么 @ObservedObject 动态成员查找会起作用:

protocol HasSoundOnOff: ObservableObject {
    var soundOn: Bool { get set }
}

或者,您可以手动实现 @ObservedObject@StateObject 属性包装器背后的魔法。具体来说,您可以调整您的协议定义以返回一个 Binding,然后您可以手动实例化一个 Binding。

首先,在你的协议中添加一个对应的 Binding 参数:

protocol HasSoundOnOff {
    var soundOn: Bool { get set }
    var soundOnBinding: Binding<Bool> { get }
}

二、手动实例化一个Binding:

class MyViewModel: ObservableObject, HasSoundOnOff {
    @Published var soundOn = true
    var soundOnBinding: Binding<Bool> {
        Binding(get: { self.soundOn }, set: { self.soundOn = $0 })
    }
}

最后,使用你的绑定(而不是@ObservedObject):

struct ParentView<ViewModel: ObservableObject & HasSoundOnOff>: View {
    @ObservedObject var viewModel: ViewModel
    var body: some View {
        Subview(soundOn: viewModel.soundOnBinding)
    }
}

SwiftUI 的属性包装器似乎不能很好地处理协议继承。希望未来对 Swift 的更新,例如 this proposal,将提供更多的兼容性。

【讨论】:

    【解决方案2】:

    你不能用吗

    @EnvironmentObject var myViewModel: MyViewModel
    

    在您需要访问它的视图中。然后将其用作

    myViewModel.soundOn 
    

    您不需要将它作为绑定传递给视图,因为您可以在您声明 @EnvironmentObject 的任何视图中访问 soundOn。

    【讨论】:

    • 整件事是关于使用 MyViewModel1、MyViewModel2、MyViewModel3... 和只有一个子视图。我需要创建 3 次 Subview 才能让您的解决方案正常工作
    猜你喜欢
    • 2020-04-17
    • 1970-01-01
    • 2019-12-28
    • 2020-10-20
    • 2011-12-11
    • 2019-05-20
    • 1970-01-01
    • 1970-01-01
    • 2019-04-11
    相关资源
    最近更新 更多