Created
May 19, 2021 19:41
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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