如何定义协议以包含带有@Published 属性包装器的属性

Und*_*Fox 16 protocols swiftui combine

在遵循当前 SwiftUI 语法使用 @Published 属性包装器时,似乎很难定义包含带有 @Published 的属性的协议,或者我肯定需要帮助:)

当我在 View 和它的 ViewModel 之间实现依赖注入时,我需要定义一个 ViewModelProtocol 以便注入模拟数据以轻松预览。

这是我第一次尝试的,

protocol PersonViewModelProtocol {
    @Published var person: Person
}
Run Code Online (Sandbox Code Playgroud)

我得到“在协议中声明的属性‘人’不能有包装器”。

然后我尝试了这个,

protocol PersonViewModelProtocol {
    var $person: Published
}
Run Code Online (Sandbox Code Playgroud)

显然没有用,因为 '$' 是保留的。

我希望有一种方法可以在 View 和它的 ViewModel 之间建立一个协议,并利用优雅的 @Published 语法。非常感谢。

Mur*_*tam 20

我的 MVVM 方法:

// MARK: View

struct ContentView<ViewModel: ContentViewModel>: View {
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        VStack {
            Text(viewModel.name)
            TextField("", text: $viewModel.name)
                .border(Color.black)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(viewModel: ContentViewModelMock())
    }
}

// MARK: View model

protocol ContentViewModel: ObservableObject {
    var name: String { get set }
}

final class ContentViewModelImpl: ContentViewModel {
    @Published var name = ""
}

final class ContentViewModelMock: ContentViewModel {
    var name: String = "Test"
}
Run Code Online (Sandbox Code Playgroud)

怎么运行的:

  • ViewModel协议继承ObservableObject,因此View将订阅ViewModel更改
  • 属性name有 getter 和 setter,所以我们可以将它用作Binding
  • View更改属性(通过 TextField)时,View 会通过属性name收到更改通知(并且 UI 会更新)@PublishedViewModel
  • View根据您的需要使用真实实现或模拟进行创建

可能的缺点:View必须是通用的。


Myc*_*ner 12

您必须明确并描述所有综合属性:

protocol WelcomeViewModel {
    var person: Person { get }
    var personPublished: Published<Person> { get }
    var personPublisher: Published<Person>.Publisher { get }
}

class ViewModel: ObservableObject {
    @Published var person: Person = Person()
    var personPublished: Published<Person> { _person }
    var personPublisher: Published<Person>.Publisher { $person }
}
Run Code Online (Sandbox Code Playgroud)

  • 更新“ViewModel”时您设置哪个属性?的`。人`,`。人发表`或`。人发布者`? (8认同)

kon*_*iki 7

我认为应该这样做:

public protocol MyProtocol {
    var _person: Published<Person> { get set }
}

class MyClass: MyProtocol, ObservableObject {
    @Published var person: Person

    public init(person: Published<Person>) {
        self._person = person
    }
}
Run Code Online (Sandbox Code Playgroud)

尽管编译器似乎有点喜欢它(至少是“类型”部分),但类和协议之间的属性访问控制不匹配(https://docs.swift.org/swift-book/LanguageGuide /AccessControl.html)。我尝试了不同的组合:privatepublicinternalfileprivate。但没有一个奏效。可能是一个错误?或者缺少功能?


小智 6

我的同事想出的解决方法是使用一个基类来声明属性包装器,然后在协议中继承它。它仍然需要在您的类中继承它也符合协议,但看起来干净并且运行良好。

class MyPublishedProperties {
    @Published var publishedProperty = "Hello"
}

protocol MyProtocol: MyPublishedProperties {
    func changePublishedPropertyValue(newValue: String)
}

class MyClass: MyPublishedProperties, MyProtocol {
    changePublishedPropertyValue(newValue: String) {
        publishedProperty = newValue
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在实施中:

class MyViewModel {
    let myClass = MyClass()

    myClass.$publishedProperty.sink { string in
        print(string)
    }

    myClass.changePublishedPropertyValue("World")
}

// prints:
//    "Hello"
//    "World"
Run Code Online (Sandbox Code Playgroud)