Skip to content

Instantly share code, notes, and snippets.

@r-peck
Last active June 6, 2019 23:07
Show Gist options
  • Save r-peck/4de553fcb6c20c7597dbe9b46a7af934 to your computer and use it in GitHub Desktop.
Save r-peck/4de553fcb6c20c7597dbe9b46a7af934 to your computer and use it in GitHub Desktop.
Layering Reader on top of SwiftUI
// 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