Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
【SwiftUI】OptionalObservedObject & OptionalStateObject
//
// OptionalObservedObject.swift
//
// Created by ribilynn on 2021/06/20.
//
import SwiftUI
import Combine
/// A wrapper of the underlying optional observable object
/// that will emit a notification when the optional state or the observable object changes.
public class OptionalWrapper<ObjectType: ObservableObject>: ObservableObject {
/// The subscrption of underlying observable object.
private var cancellable: AnyCancellable?
/// The underlying optional observable object.
public var optionalObject: ObjectType? {
willSet { observe(newValue) }
}
/// Creates an OptionalWrapper with an optional observable object.
public init(optionalObject: ObjectType?) {
self.optionalObject = optionalObject
observe(optionalObject)
}
/// Observe the new observable object.
private func observe(_ newObject: ObjectType?) {
objectWillChange.send()
cancellable = newObject?.objectWillChange.sink { [weak self] _ in
self?.objectWillChange.send()
}
}
}
/// A property wrapper type that subscribes to an optional observable object and
/// invalidates a view whenever the optional state or the observable object changes.
@propertyWrapper
public struct OptionalObservedObject<ObjectType: ObservableObject>: DynamicProperty {
@dynamicMemberLookup
public struct Wrapper {
internal let optionalWrapper: OptionalWrapper<ObjectType>
public subscript<Subject>(dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject>) -> Binding<Optional<Subject>> {
Binding<Optional<Subject>>.init {
optionalWrapper.optionalObject?[keyPath: keyPath]
} set: { subject in
if let subject = subject {
optionalWrapper.optionalObject?[keyPath: keyPath] = subject
}
}
}
}
/// The underlying wrapper for the optional observable object.
@ObservedObject private var objectWrapper: OptionalWrapper<ObjectType>
public init(wrappedValue: ObjectType?) {
_objectWrapper = .init(initialValue: OptionalWrapper(optionalObject: wrappedValue))
}
public var wrappedValue: ObjectType? {
get { objectWrapper.optionalObject }
nonmutating set { objectWrapper.optionalObject = newValue }
}
public var projectedValue: OptionalObservedObject<ObjectType>.Wrapper {
Wrapper(optionalWrapper: objectWrapper)
}
}
/// A property wrapper type that instantiates an optional observable object.
@propertyWrapper
public struct OptionalStateObject<ObjectType: ObservableObject>: DynamicProperty {
@dynamicMemberLookup
public struct Wrapper {
internal let objectWrapper: OptionalWrapper<ObjectType>
public subscript<Subject>(dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject>) -> Binding<Optional<Subject>> {
Binding<Optional<Subject>>.init {
objectWrapper.optionalObject?[keyPath: keyPath]
} set: { subject in
if let subject = subject {
objectWrapper.optionalObject?[keyPath: keyPath] = subject
}
}
}
}
/// The underlying wrapper for the optional observable object.
@StateObject var objectWrapper: OptionalWrapper<ObjectType>
public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType?) {
_objectWrapper = .init(wrappedValue: OptionalWrapper(optionalObject: thunk()))
}
public var wrappedValue: ObjectType? {
get { objectWrapper.optionalObject }
nonmutating set { objectWrapper.optionalObject = newValue }
}
public var projectedValue: OptionalStateObject<ObjectType>.Wrapper {
Wrapper(objectWrapper: objectWrapper)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment