Как реализовать настраиваемую оболочку свойств, которая будет публиковать изменения для SwiftUI, чтобы повторно отобразить его представление

Попытка реализовать настраиваемую оболочку свойств, которая также публиковала бы свои изменения таким же образом @Publishделает. Например, разрешить моему SwiftUI получать изменения в моей собственности с помощью моей пользовательской оболочки.

Рабочий код у меня есть:

import SwiftUI

@propertyWrapper
struct MyWrapper<Value> {
    var value: Value

    init(wrappedValue: Value) { value = wrappedValue }

    var wrappedValue: Value {
        get { value }
        set { value = newValue }
    }
}

class MySettings: ObservableObject {
    @MyWrapper
    public var interval: Double = 50 {
        willSet { objectWillChange.send() }
    }
}

struct MyView: View {
    @EnvironmentObject var settings: MySettings

    var body: some View {
        VStack() {
            Text("\(settings.interval, specifier: "%.0f")").font(.title)
            Slider(value: $settings.interval, in: 0...100, step: 10)
        }
    }
}

struct MyView_Previews: PreviewProvider {
    static var previews: some View {
        MyView().environmentObject(MySettings())
    }
}

Однако мне не нравится необходимость звонить objectWillChange.send() для каждой собственности в MySettings учебный класс.

В @Published оболочка работает хорошо, поэтому я попытался реализовать ее как часть @MyWrapper, но мне это не удалось.

Хорошим вдохновением, которое я нашел, был https://github.com/broadwaylamb/OpenCombine, но я потерпел неудачу даже при попытке использовать код оттуда.

Когда боролся с реализацией, я понял, что для получения @MyWrapper мне нужно точно понимать, как @EnvironmentObject а также @ObservedObject подписаться на изменения @Published.

Любая помощь будет оценена.

1 ответ

Решение

Пока не будет реализован https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md, я пришел с решением ниже.

Как правило, я сдаю objectWillChange ссылка на MySettings ко всем свойствам, помеченным @MyWrapper используя отражение.

import Cocoa
import Combine
import SwiftUI

protocol PublishedWrapper: class {
    var objectWillChange: ObservableObjectPublisher? { get set }
}

@propertyWrapper
class MyWrapper<Value>: PublishedWrapper {
    var value: Value
    weak var objectWillChange: ObservableObjectPublisher?

    init(wrappedValue: Value) { value = wrappedValue }

    var wrappedValue: Value {
        get { value }
        set {
            value = newValue
            objectWillChange?.send()
        }
    }
}

class MySettings: ObservableObject {
    @MyWrapper
    public var interval1: Double = 10

    @MyWrapper
    public var interval2: Double = 20

    /// Pass our `ObservableObjectPublisher` to the property wrappers so that they can announce changes
    init() {
        let mirror = Mirror(reflecting: self)
        mirror.children.forEach { child in
            if let observedProperty = child.value as? PublishedWrapper {
                observedProperty.objectWillChange = self.objectWillChange
            }
        }
    }
}

struct MyView: View {
    @EnvironmentObject
    private var settings: MySettings

    var body: some View {
        VStack() {
            Text("\(settings.interval1, specifier: "%.0f")").font(.title)
            Slider(value: $settings.interval1, in: 0...100, step: 10)

            Text("\(settings.interval2, specifier: "%.0f")").font(.title)
            Slider(value: $settings.interval2, in: 0...100, step: 10)
        }
    }
}

struct MyView_Previews: PreviewProvider {
    static var previews: some View {
        MyView().environmentObject(MySettings())
    }
}
Другие вопросы по тегам