Skip to content

Instantly share code, notes, and snippets.

@rjchatfield
Last active November 19, 2019 22:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rjchatfield/336a3653e14e14c79353cdb6a4627ac8 to your computer and use it in GitHub Desktop.
Save rjchatfield/336a3653e14e14c79353cdb6a4627ac8 to your computer and use it in GitHub Desktop.
SwiftUI either type many different (non-scalable) approaches
// 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")
}
}
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")
}
}
@rjchatfield
Copy link
Author

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 an Either<A, B, C>.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment