Last active
June 6, 2019 23:07
-
-
Save r-peck/4de553fcb6c20c7597dbe9b46a7af934 to your computer and use it in GitHub Desktop.
Layering Reader on top of SwiftUI
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
// started from https://gist.github.com/chriseidhof/f6d9b42c709a83413e84cafc0f7295cd | |
import SwiftUI | |
import Combine | |
struct Reader<R, A> { | |
public let run: (R) -> A | |
public static func pure(_ a: A) -> Reader<R, A> { | |
return Reader<R, A> { _ in a } | |
} | |
} | |
// Function builder that lets us pass blocks of Readers that result in views the same way ViewBuilder lets us do with plain Views | |
@_functionBuilder struct ReaderViewBuilder<R> { | |
public static func buildBlock<A: View>(_ a: Reader<R, A>) -> Reader<R, A> { | |
return a | |
} | |
public static func buildBlock<A: View, B: View>(_ a: Reader<R, A>, _ b: Reader<R, B>) -> Reader<R, TupleView<(A, B)>> { | |
return Reader { r in | |
return ViewBuilder.buildBlock(a.run(r), b.run(r)) | |
} | |
} | |
public static func buildBlock<A: View, B: View, C: View>(_ a: Reader<R, A>, _ b: Reader<R, B>, _ c: Reader<R, C>) -> Reader<R, TupleView<(A, B, C)>> { | |
return Reader { r in | |
return ViewBuilder.buildBlock(a.run(r), b.run(r), c.run(r)) | |
} | |
} | |
} | |
// Wrappers of SwiftUI Views to support use with ReaderViewBuilder | |
func navigationView<Env, Root: View>(@ReaderViewBuilder<Env> _ makeRoot: @escaping () -> Reader<Env, Root>) -> Reader<Env, NavigationView<Root>> { | |
return Reader { env in | |
NavigationView { | |
makeRoot().run(env) | |
} | |
} | |
} | |
func navigationButton<Env, Label: View, Destination: View>(destination: Reader<Env, Destination>, @ReaderViewBuilder<Env> makeLabel: @escaping () -> Reader<Env, Label>) -> Reader<Env, NavigationButton<Label, Destination>> { | |
return Reader { env in | |
NavigationButton(destination: destination.run(env)) { | |
makeLabel().run(env) | |
} | |
} | |
} | |
func list<Env, Selection: SelectionManager, Content: View>(selection: Binding<Selection>?, @ReaderViewBuilder<Env> _ makeContent: @escaping () -> Reader<Env, Content>) -> Reader<Env, List<Selection, Content>> { | |
return Reader { env in | |
List(selection: selection) { | |
makeContent().run(env) | |
} | |
} | |
} | |
func list<Env, Content: View>(@ReaderViewBuilder<Env> _ makeContent: @escaping () -> Reader<Env, Content>) -> Reader<Env, List<Never, Content>> { | |
return Reader { env in | |
List { | |
makeContent().run(env) | |
} | |
} | |
} | |
func vStack<Env, Content: View>(@ReaderViewBuilder<Env> _ makeContent: @escaping () -> Reader<Env, Content>) -> Reader<Env, VStack<Content>> { | |
return Reader { env in | |
VStack { | |
makeContent().run(env) | |
} | |
} | |
} | |
func text(_ t: String) -> Reader<Any, Text> { | |
return Reader.pure(Text(t)) | |
} | |
func text<Env>(_ t: String) -> Reader<Env, Text> { | |
return Reader.pure(Text(t)) | |
} | |
// Example utilizing ReaderViewBuilder instead of ViewBuilder | |
class CounterStore: BindableObject { | |
let didChange = PassthroughSubject<Void, Never>() | |
var count: Int = 0 { | |
didSet { | |
didChange.send(()) | |
} | |
} | |
} | |
struct CounterView: View { | |
@ObjectBinding var store: CounterStore | |
var body: some View { | |
VStack { | |
Text("Count: \(store.count)") | |
Stepper(value: $store.count, in: 0...10) { | |
Text("Counter") | |
} | |
} | |
} | |
} | |
let counter = Reader(run: CounterView.init) | |
struct ContentView : View { | |
@State var store = CounterStore() | |
var body: some View { | |
navigationView { | |
list { | |
counter | |
navigationButton(destination: list { counter }) { | |
text("Pass through navigation") | |
} | |
} | |
} .run(store) // if the type of the value passed to run does not line up with the dependencies downstream, the example won't compile | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment