SwiftUI:将 @Binding 与协议中的 @Property 变量一起使用

Dev*_*B2F 6 swift swiftui

我有一个 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")
    }
}
Run Code Online (Sandbox Code Playgroud)

但我想为所有符合 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
    }
}
Run Code Online (Sandbox Code Playgroud)

我可以让 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

小智 0

你能不能不使用

@EnvironmentObject var myViewModel: MyViewModel
Run Code Online (Sandbox Code Playgroud)

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

myViewModel.soundOn 
Run Code Online (Sandbox Code Playgroud)

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