How to define a protocol to include a property with @Published property wrapper

后端 未结 6 533
情话喂你
情话喂你 2020-12-15 09:42

When using @Published property wrapper following current SwiftUI syntax, it seems very hard to define a protocol that includes a property with @Published, or I definitely ne

相关标签:
6条回答
  • 2020-12-15 10:17

    A workaround my coworker came up with is to use a base class that declares the property wrappers, then inherit it in the protocol. It still requires inheriting it in your class that conforms to the protocol as well, but looks clean and works nicely.

    class MyPublishedProperties {
        @Published var publishedProperty = "Hello"
    }
    
    protocol MyProtocol: MyPublishedProperties {
        func changePublishedPropertyValue(newValue: String)
    }
    
    class MyClass: MyPublishedProperties, MyProtocol {
        changePublishedPropertyValue(newValue: String) {
            publishedProperty = newValue
        }
    }
    

    Then in implementation:

    class MyViewModel {
        let myClass = MyClass()
    
        myClass.$publishedProperty.sink { string in
            print(string)
        }
    
        myClass.changePublishedPropertyValue("World")
    }
    
    // prints:
    //    "Hello"
    //    "World"
    
    0 讨论(0)
  • 2020-12-15 10:21

    This is how I suppose it should be done:

    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
        }
    }
    

    Although the compiler seems to sort of like it (the "type" part at least), there is a mismatch in the property's access control between the class and the protocol (https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html). I tried different combinations: private, public, internal, fileprivate. But none worked. Might be a bug? Or missing functionality?

    0 讨论(0)
  • 2020-12-15 10:21

    We've encountered this as well. As of Catalina beta7, there doesn't seem to be any workaround, so our solution is to add in a conformance via an extension like so:

    
    struct IntView : View {
        @Binding var intValue: Int
    
        var body: some View {
            Stepper("My Int!", value: $intValue)
        }
    }
    
    protocol IntBindingContainer {
        var intValue$: Binding<Int> { get }
    }
    
    extension IntView : IntBindingContainer {
        var intValue$: Binding<Int> { $intValue }
    }
    

    While this is a bit of extra ceremony, we can then add in functionality to all the IntBindingContainer implementations like so:

    extension IntBindingContainer {
        /// Reset the contained integer to zero
        func resetToZero() {
            intValue$.wrappedValue = 0
        }
    }
    
    
    0 讨论(0)
  • 2020-12-15 10:21

    Try this

    import Combine
    import SwiftUI
    
    // MARK: - View Model
    
    final class MyViewModel: ObservableObject {
    
        @Published private(set) var value: Int = 0
    
        func increment() {
            value += 1
        }
    }
    
    extension MyViewModel: MyViewViewModel { }
    
    // MARK: - View
    
    protocol MyViewViewModel: ObservableObject {
    
        var value: Int { get }
    
        func increment()
    }
    
    struct MyView<ViewModel: MyViewViewModel>: View {
    
        @ObservedObject var viewModel: ViewModel
    
        var body: some View {
    
            VStack {
                Text("\(viewModel.value)")
    
                Button("Increment") {
                    self.viewModel.increment()
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-15 10:32

    You have to be explicit and describe all synthetized properties:

    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 }
    }
    
    0 讨论(0)
  • 2020-12-15 10:34

    I succeeded in just requiring the plain variable, and by adding the @Published in the fulfilling class:

    final class CustomListModel: IsSelectionListModel, ObservableObject {
    
    
    
        @Published var list: [IsSelectionListEntry]
    
    
        init() {
    
            self.list = []
        }
    ...
    
    protocol IsSelectionListModel {
    
    
        var list: [IsSelectionListEntry] { get }
    ...
    
    0 讨论(0)
提交回复
热议问题