Skip to content

Instantly share code, notes, and snippets.

@garsdle
Last active June 22, 2021 15:43
Show Gist options
  • Save garsdle/07413847dbe8c5f2c0b29a705c62aae1 to your computer and use it in GitHub Desktop.
Save garsdle/07413847dbe8c5f2c0b29a705c62aae1 to your computer and use it in GitHub Desktop.
import Combine
import SwiftUI
class ScopedGetObservable<T>: ObservableObject {
var cancellable: AnyCancellable?
let getter: () -> T
init<P>(getter: @autoclosure @escaping () -> T, publisher: P) where P: Publisher, P.Output == T, P.Failure == Never {
self.getter = getter
cancellable = publisher.sink { [weak self] _ in
self?.objectWillChange.send()
}
}
}
@propertyWrapper
struct ScopedGet<T>: DynamicProperty {
var wrappedValue: T {
scopedObservable.getter()
}
@ObservedObject private var scopedObservable: ScopedGetObservable<T>
init<P>(getter: @autoclosure @escaping () -> T, publisher: P) where P: Publisher, P.Output == T, P.Failure == Never {
self.scopedObservable = .init(getter: getter(), publisher: publisher)
}
}
// MARK: - MODELS
class AppViewModel: ObservableObject {
@Published var count = 0
@Published var favorites: Set<Int> = []
func remove(number: Int) {
favorites.remove(number)
}
func insert(number: Int) {
favorites.insert(number)
}
func incrementCount() {
count += 1
}
func decrementCount() {
count -= 1
}
}
var viewModel = AppViewModel()
// MARK: - VIEWS
struct VanillaContentView: View {
@ScopedGet var count: Int
@ScopedGet var favoriteCount: Int
var body: some View {
TabView {
VanillaCounterView(count: ScopedGet(getter: viewModel.count,
publisher: viewModel.$count),
favorites: ScopedGet(getter: viewModel.favorites,
publisher: viewModel.$favorites),
onRemove: viewModel.remove(number:),
onInsert: viewModel.insert(number:),
onIncrement: viewModel.incrementCount,
onDecrement: viewModel.decrementCount)
.tabItem { Text("Counter \(count)") }
VanillaProfileView(favorites: ScopedGet(getter: viewModel.favorites,
publisher: viewModel.$favorites),
onRemove: viewModel.remove(number:))
.tabItem { Text("Profile \(favoriteCount)") }
}
}
}
struct VanillaCounterView: View {
@ScopedGet var count: Int
@ScopedGet var favorites: Set<Int>
let onRemove: (Int) -> Void
let onInsert: (Int) -> Void
let onIncrement: () -> Void
let onDecrement: () -> Void
var body: some View {
VStack {
HStack {
Button("-", action: onDecrement)
Text("\(count)")
Button("+", action: onIncrement)
}
if favorites.contains(count) {
Button("Remove") {
onRemove(count)
}
} else {
Button("Save") {
onInsert(count)
}
}
}
}
}
struct VanillaProfileView: View {
@ScopedGet var favorites: Set<Int>
let onRemove: (Int) -> Void
var body: some View {
List {
ForEach(favorites.sorted(), id: \.self) { number in
HStack {
Text("\(number)")
Spacer()
Button("Remove", action: { onRemove(number) })
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
VanillaContentView(count: .init(getter: viewModel.count, publisher: viewModel.$count),
favoriteCount: ScopedGet(getter: viewModel.favorites.count, publisher: viewModel.$favorites.map(\.count)))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment