Skip to content

Instantly share code, notes, and snippets.

@pcolton
Created May 19, 2021 19:41
Show Gist options
  • Save pcolton/7532cb2f2166f2a46e324333869db19f to your computer and use it in GitHub Desktop.
Save pcolton/7532cb2f2166f2a46e324333869db19f to your computer and use it in GitHub Desktop.
A swift property wrapper that allows you to Observe a scoped down state of an ObservableObject.
// ObserveOnly
//
// Created by Paul Colton on 5/19/21.
//
/**
Given a model publisher, define the state and scoped state that you want to observe.
// Example usage: Given a data model...
struct Book: Identifiable, Equatable {
let id: UUID
var favorite: Bool
var finished: Bool
}
class DataModel: ObservableObject {
@Published var books: [Book] = []
@Published var audioBooks: [Book] = []
}
...
// Observe only changes to the books array property...
struct ContentView: View {
@StateObject
@ObserveOnly({ $0.$books.eraseToAnyPublisher() }, { $0 })
var dataModel = DataModel()
...
}
// Or in the case of a single object...
class BookModel: ObservableObject {
@Published var book: Book
}
// Observe only changes to a specific boolean...
struct BookView: View {
@StateObject
@ObserveOnly<BookModel, Book, Bool>()
var bookModel: BookModel
init(book: Book) {
self._bookModel = StateObject(wrappedValue:
ObserveOnly(wrappedValue: BookModel(book: book),
{ $0.$book.eraseToAnyPublisher() },
{ $0.finished }
)
)
}
...
}
**/
@propertyWrapper class ObserveOnly<Model, State, ScopedState>: ObservableObject {
var model: Model
var state: AnyPublisher<State, Never>
var scopedState: (State) -> ScopedState
@Published public var updates: ScopedState?
private var updatesCancellable: AnyCancellable?
var wrappedValue: Model {
get { model }
set { model = newValue }
}
init(wrappedValue value: Model,
_ publisher: @escaping (Model) -> AnyPublisher<State, Never>,
_ scope: @escaping (State) -> ScopedState,
removeDuplicates isDuplicate: @escaping (ScopedState, ScopedState) -> Bool) {
self.model = value
self.state = publisher(self.model)
self.scopedState = scope
self.updatesCancellable = self.state
.receive(on: DispatchQueue.main)
.sink { [weak self] in
if let newValue = self?.scopedState($0) {
if let updates = self?.updates {
if isDuplicate(updates, newValue) == false {
self?.updates = newValue
}
}
else {
self?.updates = newValue
}
}
}
}
}
extension ObserveOnly where ScopedState: Equatable {
convenience init(wrappedValue value: Model,
_ publisher: @escaping (Model) -> AnyPublisher<State, Never>,
_ scope: @escaping (State) -> ScopedState) {
self.init(wrappedValue: value, publisher, scope, removeDuplicates: ==)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment