Skip to content

Instantly share code, notes, and snippets.

@yarshure
Forked from chriseidhof/TodoList.swift
Created July 4, 2019 09:21
Show Gist options
  • Save yarshure/20eba34f71ce17603e4f7dc77cfc5452 to your computer and use it in GitHub Desktop.
Save yarshure/20eba34f71ce17603e4f7dc77cfc5452 to your computer and use it in GitHub Desktop.
//
// ContentView.swift
// TestingMoreSwiftUI
//
// Created by Chris Eidhof on 04.06.19.
// Copyright © 2019 Chris Eidhof. All rights reserved.
//
import SwiftUI
import Combine
var newListCounter = 1
extension Array {
mutating func remove(atOffsets indices: IndexSet) {
for i in indices.reversed() {
remove(at: i)
}
}
subscript(safe index: Int) -> Element? {
get {
guard (startIndex..<endIndex).contains(index) else { return nil }
return self[index]
}
set {
guard (startIndex..<endIndex).contains(index) else { return }
if let v = newValue {
self[index] = v
}
}
}
}
/// Similar to a `Binding`, but this is also observable/dynamic.
@propertyWrapper
@dynamicMemberLookup
final class Derived<A>: BindableObject {
let didChange = PassthroughSubject<A, Never>()
fileprivate var cancellables: [AnyCancellable] = []
private let get: () -> (A)
private let mutate: ((inout A) -> ()) -> ()
init(get: @escaping () -> A, mutate: @escaping ((inout A) -> ()) -> ()) {
self.get = get
self.mutate = mutate
}
var value: A {
get { get() }
set { mutate { $0 = newValue } }
}
subscript<U>(dynamicMember keyPath: WritableKeyPath<A, U>) -> Derived<U> {
let result = Derived<U>(get: {
let value = self.get()[keyPath: keyPath]
return value
}, mutate: { f in
self.mutate { (a: inout A) in
f(&a[keyPath: keyPath])
}
})
var c: AnyCancellable! = nil
c = AnyCancellable(didChange.sink { [weak result] in
// todo cancel the subscription as well
result?.didChange.send($0[keyPath: keyPath])
})
cancellables.append(c)
return result
}
var binding: Binding<A> {
return Binding<A>(getValue: { self.value }, setValue: { self.value = $0 })
}
deinit {
for c in cancellables {
c.cancel()
}
}
}
final class SimpleStore<A>: BindableObject {
let didChange =
PassthroughSubject<A, Never>()
init(_ value: A) { self.value = value }
var value: A {
didSet {
didChange.send(value)
}
}
var bindable: Derived<A> {
let result = Derived<A>(get: {
self.value
}, mutate: { f in
f(&self.value)
})
let c = self.didChange.sink { [weak result] value in
result?.didChange.send(value)
}
result.cancellables.append(AnyCancellable(c))
return result
}
}
struct TodoList: Codable, Equatable, Hashable {
var items: [Todo] = []
var name = "Todos"
}
struct Todo: Codable, Equatable, Hashable {
var text: String
var done: Bool = false
}
struct MyState: Codable, Equatable, Hashable {
var lists: [TodoList] = [
TodoList(items: [
Todo(text: "Buy Milk"),
Todo(text: "Clean")
])
]
}
struct ItemRow: View {
@Binding var item: Todo?
var body: some View {
return Button(action: { self.item!.done.toggle() }) {
HStack {
Text(item!.text)
Spacer()
if item!.done {
Image(systemName: "checkmark")
}
}
}
}
}
struct ListRow: View {
@ObjectBinding var item: Derived<TodoList>
var body: some View {
NavigationLink(destination: TodoListView(list: item)) {
HStack {
Text(item.value.name)
Spacer()
}
}
}
}
struct TodoListView: View {
@ObjectBinding var list: Derived<TodoList>
var body: some View {
List {
ForEach((0..<list.value.items.count)) { index in
ItemRow(item: self.list.items[safe: index].binding)
}.onDelete { indices in
// this crashes...
self.list.value.items.remove(atOffsets: indices)
}
}
.navigationBarTitle(Text("\(list.value.name) - \(list.value.items.count) items"))
.navigationBarItems(leading:
EditButton(),
trailing: Button(action: { self.list.value.items.append(Todo(text: "New Todo")) }) { Image(systemName: "plus.circle")}
)
}
}
struct AllListsView: View {
@ObjectBinding var theState: Derived<MyState>
var body: some View {
List {
ForEach(0..<theState.value.lists.count) { (index: Int) in
ListRow(item: self.theState.lists[index])
}
}
.navigationBarTitle(Text("All Lists"))
.navigationBarItems(trailing:
Button(action: {
newListCounter += 1
self.theState.value.lists.append(TodoList(items: [], name: "New List \(newListCounter)"))
}) { Image(systemName: "plus.circle")}
)
}
}
struct ContentView : View {
@ObjectBinding var store: Derived<MyState>
var body: some View {
NavigationView {
AllListsView(theState: store)
}
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView(store: SimpleStore(MyState()).bindable)
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment