Skip to content

Instantly share code, notes, and snippets.

@Amzd
Last active July 20, 2020 11:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Amzd/6c62f04793fc5c867e24d5998f9a2bd1 to your computer and use it in GitHub Desktop.
Save Amzd/6c62f04793fc5c867e24d5998f9a2bd1 to your computer and use it in GitHub Desktop.
Moved to repo so you can use it in Swift Package Manager: https://github.com/Amzd/NestedPublished
/// Just like @Published this sends willSet events to the enclosing ObservableObject's ObjectWillChangePublisher
/// but unlike @Published it also sends the wrapped value's published changes on to the enclosing ObservableObject
@propertyWrapper @available(iOS 13.0, *)
public struct NestedPublished<Value: ObservableObject> where Value.ObjectWillChangePublisher == ObservableObjectPublisher {
public init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
self.cancellable = nil
startListening(to: wrappedValue)
}
public var wrappedValue: Value {
willSet { parent.objectWillChange?() }
didSet { startListening(to: wrappedValue) }
}
public static subscript<EnclosingSelf: ObservableObject>(
_enclosingInstance observed: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, NestedPublished>
) -> Value where EnclosingSelf.ObjectWillChangePublisher == ObservableObjectPublisher {
get {
observed[keyPath: storageKeyPath].setParent(observed)
return observed[keyPath: storageKeyPath].wrappedValue
}
set {
observed[keyPath: storageKeyPath].setParent(observed)
observed[keyPath: storageKeyPath].wrappedValue = newValue
}
}
private let parent = Holder()
private var cancellable: AnyCancellable?
private class Holder {
var objectWillChange: (() -> Void)?
init() {}
}
private mutating func setParent<Parent: ObservableObject>(_ parentObject: Parent) where Parent.ObjectWillChangePublisher == ObservableObjectPublisher {
guard parent.objectWillChange == nil else { return }
parent.objectWillChange = { [weak parentObject] in
parentObject?.objectWillChange.send()
}
}
private mutating func startListening(to wrappedValue: Value) {
cancellable = wrappedValue.objectWillChange.sink { [parent] in
parent.objectWillChange?()
}
}
}
/// Force NestedPublished when using ObservableObjects
@available(iOS 13.0, *)
extension Published where Value: ObservableObject {
public init(wrappedValue: Value) {
fatalError("Use NestedPublished with ObservableObjects")
}
}
class Outer: ObservableObject {
@NestedPublished var inner: Inner
init(_ inner: Inner) {
self.inner = inner
}
}
class Inner: ObservableObject {
@Published var value: Int
init(_ int: Int) {
self.value = int
}
}
var cancellable: AnyCancellable?
func testExample() {
let outer = Outer(Inner(1))
cancellable = outer.objectWillChange.sink {
print("Outer willChange called")
}
print("Setting property on Outer (This will send an update with either @Published or @NestedPublished)")
outer.inner = Inner(2)
print("Setting property on Inner (This will only send an update with @NestedPublished)")
outer.inner.value = 3
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment