Last active
November 19, 2019 22:02
-
-
Save rjchatfield/336a3653e14e14c79353cdb6a4627ac8 to your computer and use it in GitHub Desktop.
SwiftUI either type many different (non-scalable) approaches
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
// MARK: - Either Attempt 1 - FAIL! | |
struct EitherAttempt1 { | |
@State var state: ThreewayState | |
func either<V1: View, V2: View>(block: () -> (V1?, V2?)) -> some View { | |
TupleView<(V1?, V2?)>(block()) | |
} | |
func either<V1: View, V2: View, V3: View>(block: () -> (V1?, V2?, V3?)) -> some View { | |
TupleView<(V1?, V2?, V3?)>(block()) | |
} | |
// var body: some View { | |
// either { // Warning: Generic parameter 'V1' could not be inferred | |
// switch state { | |
// case .a(let aStr): | |
// return (Optional.some(AView(state: aStr)), nil, nil) | |
// case .b(let bInt): | |
// return (nil, Optional.some(BView(state: bInt)), nil) | |
// case .c(let bool): | |
// return (nil, nil, Optional.some(CView(state: bool))) | |
// } | |
// } | |
// } | |
} | |
// MARK: - Either Attempt 2 - FAIL! | |
struct EitherAttempt2 { | |
@State var state: ThreewayState | |
func either<V1: View, V2: View, V3: View>(block: (inout V1?, inout V2?, inout V3?) -> Void) -> some View { | |
var v1: V1? | |
var v2: V2? | |
var v3: V3? | |
block(&v1, &v2, &v3) | |
return TupleView<(V1?, V2?, V3?)>((v1, v2, v3)) | |
} | |
// var body: some View { | |
// either { (v1, v2, v3) in // Warning: Generic parameter 'V1' could not be inferred | |
// switch state { | |
// case .a(let aStr): | |
// v1 = AView(state: aStr) | |
// case .b(let bInt): | |
// v2 = BView(state: bInt) | |
// case .c(let bool): | |
// v3 = CView(state: bool) | |
// } | |
// } | |
// } | |
} | |
// MARK: - SwitchCase - Success (but ugly) | |
func switchCase<S1, S2, V1: View, V2: View>( | |
getValue: () -> (S1?, S2?), | |
make1: (S1) -> V1?, | |
make2: (S2) -> V2? | |
) -> some View { | |
let state: (S1?, S2?) = getValue() | |
return Group { | |
if state.0 != nil { make1(state.0!) } | |
if state.1 != nil { make2(state.1!) } | |
} | |
} | |
func switchCase<S1, S2, S3, V1: View, V2: View, V3: View>( | |
getValue: () -> (S1?, S2?, S3?), | |
make1: (S1) -> V1?, | |
make2: (S2) -> V2?, | |
make3: (S3) -> V3? | |
) -> some View { | |
let state: (S1?, S2?, S3?) = getValue() | |
return Group { | |
if state.1 != nil { make2(state.1!) } | |
if state.0 != nil { make1(state.0!) } | |
if state.2 != nil { make3(state.2!) } | |
} | |
} | |
struct EitherAttempt3: View { | |
@State var state: ThreewayState | |
var body: some View { | |
switchCase( | |
getValue: { | |
switch state { | |
case .a(let aStr): return (aStr, nil, nil) | |
case .b(let bInt): return (nil, bInt, nil) | |
case .c(let bool): return (nil, nil, bool) | |
} | |
}, | |
make1: { aStr in | |
AView(state: aStr) | |
}, | |
make2: { bInt in | |
BView(state: bInt) | |
}, | |
make3: { bool in | |
CView(state: bool) | |
} | |
).onTapGesture { self.state.toggle() } | |
} | |
} | |
struct EitherAttempt3_1: View { | |
var states: [(id: String, state: ThreewayState)] | |
/// Second example, point-free in a list | |
var body: some View { | |
List { | |
Section(header: Text("All the states!")) { | |
ForEach(states, id: \.id) { tuple in | |
VStack(alignment: .leading) { | |
Text("This state is:") | |
switchCase( | |
getValue: tuple.state.asTuple, | |
make1: AView.init(state:), | |
make2: BView.init(state:), | |
make3: CView.init(state:) | |
) | |
} | |
} | |
} | |
} | |
} | |
} | |
extension ThreewayState { | |
func asTuple() -> (String?, Int?, Bool?) { | |
switch self { | |
case .a(let aStr): return (aStr, nil, nil) | |
case .b(let bInt): return (nil, bInt, nil) | |
case .c(let bool): return (nil, nil, bool) | |
} | |
} | |
} | |
struct SomeView_Previews: PreviewProvider { | |
static var previews: some View { | |
Group { | |
EitherAttempt3(state: .a("Testr")) | |
EitherAttempt3_1(states: [ | |
("1", .a("Testr")), | |
("2", .b(42)), | |
("3", .a("Testr, but a secon time")), | |
("4", .c(false)), | |
("5", .c(true)), | |
]) | |
} | |
.previewDevice("iPhone 11 Pro") | |
} | |
} |
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
import SwiftUI | |
// MARK: - Imagine 3 different views ... | |
struct AView: View, Equatable { | |
let state: String | |
var body: some View { Text("A: \(state)") } | |
} | |
struct BView: View, Equatable { | |
let state: Int | |
var body: some View { Text("B: \(state)") } | |
} | |
struct CView: View, Equatable { | |
let state: Bool | |
var body: some View { Text("C: \(String(describing: state))") } | |
} | |
// MARK: - ... But we only want to render 1 at a time | |
struct SumView: View { | |
@State var state: ThreewayState | |
/* | |
if case .a(let aStr) = state { | |
AView(state: aStr) | |
} | |
if case .b(let i) = state { | |
BView(state: i) | |
} | |
if case .c(let bool) = state { | |
CView(state: bool) | |
} | |
*/ | |
var body: some View { | |
// v: Group<TupleView<(AView?, BView?, CView?, HStack<TupleView<(AView, AView)>>?, BView?, CView?)>> | |
let v = Group { | |
/// 1. Enum properties (PointFree style) | |
ifLet({ state.a }) { aStr in | |
AView(state: aStr) | |
} | |
/// 2. Local helper method | |
ifLet(self.getB) { bInt in | |
BView(state: bInt) | |
} | |
/// 3. Inline the guard | |
ifLet( | |
{ () -> Bool? in | |
guard case .c(let value) = state else { return nil } | |
return value | |
}, | |
then: { cBool in | |
CView(state: cBool) | |
} | |
) | |
/// 4. Doubly nested if statement | |
ifLet({ state.a }) { outer in | |
ifLet({ state.a }) { inner in | |
HStack { | |
AView(state: "Outer " + outer) | |
AView(state: "Inner " + inner) | |
} | |
} | |
} | |
/// 5. Local helper method | |
optionalBView() | |
/// 6. Failable initialiser | |
CView(optionalState: { | |
guard case .c(let value) = state else { return nil } | |
return value | |
}) | |
} | |
let description = String(describing: type(of: v)) | |
.replacingOccurrences(of: ", ", with: ", \n") | |
.replacingOccurrences(of: "(", with: "\n(") | |
.replacingOccurrences(of: ")", with: "\n)") | |
return VStack { | |
v | |
Divider() | |
Spacer() | |
Divider() | |
Text(description) | |
Spacer() | |
} | |
.onTapGesture { self.state.toggle() } | |
} | |
func getB() -> Int? { | |
guard case .b(let value) = state else { return nil } | |
return value | |
} | |
func optionalBView() -> BView? { | |
guard case .b(let value) = state else { return nil } | |
return BView(state: value + 100) | |
} | |
} | |
extension CView { | |
init?(optionalState: () -> Bool?) { | |
guard let state = optionalState() else { return nil } | |
self.init(state: state) | |
} | |
} | |
enum ThreewayState { | |
case a(String) | |
case b(Int) | |
case c(Bool) | |
/// Enum property (PointFree style) | |
var a: String? { | |
guard case .a(let value) = self else { return nil } | |
return value | |
} | |
mutating func toggle() { | |
switch self { | |
case .a(let str): self = .b(str.count) | |
case .b(let int): self = .c(int.isMultiple(of: 2)) | |
case .c(let bool): self = .a(bool ? "yep" : "nope") | |
} | |
} | |
} | |
// MARK: - IfLet view | |
//struct IfLet<Content: View>: View { | |
// let content: Content? | |
// init<T>(_ value: () -> T?, then: (T) -> Content?) { | |
// content = value().flatMap(then) | |
// } | |
// var body: some View { content } | |
//} | |
func ifLet<T, Content: View>(_ value: () -> T?, then: (T) -> Content?) -> Content? { | |
value().flatMap(then) | |
} | |
struct SomeView_Previews: PreviewProvider { | |
static var previews: some View { | |
SumView(state: .a("Test")) | |
.previewDevice("iPhone 11 Pro Max") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I don't see any way to guarantee that for a given enum case we get an exact type. You have to wrap all the variations into a Group, and use some helper to let you unwrap some value (in some non-exclusive way).
Instead of a
Tuple<A, B, C>
I want anEither<A, B, C>
.