Skip to content

Instantly share code, notes, and snippets.

@darrarski
Last active January 7, 2021 13:15
Show Gist options
  • Save darrarski/f3886d4d0a3598132a3fdd62b3061eb2 to your computer and use it in GitHub Desktop.
Save darrarski/f3886d4d0a3598132a3fdd62b3061eb2 to your computer and use it in GitHub Desktop.
SwiftUI IfLet helper functions
import SwiftUI
func IfLet<T, ThenOut: View>(
_ value: T?,
then: (T) -> ThenOut
) -> some View {
ViewBuilder.buildIf(value.map { then($0) })
}
func IfLet<T, ThenOut: View, ElseOut: View>(
_ value: T?,
then: (T) -> ThenOut,
`else`: () -> ElseOut
) -> some View {
value.map { ViewBuilder.buildEither(first: then($0)) }
?? ViewBuilder.buildEither(second: `else`())
}
@filimo
Copy link

filimo commented Feb 15, 2020

This extension can help to use short and long methods but I can notice _ConditionalContent is unofficial and might be changed or removed in the future.

extension View {
    func ifLet<Value, Then: View>(
        _ value: Value?,
        then: (Value) -> Then
    ) -> _ConditionalContent<Then, EmptyView> {
        if let value = value {
            return ViewBuilder.buildEither(first: then(value))
        } else {
            return ViewBuilder.buildEither(second: EmptyView())
        }
    }

    func ifLet<Value, Then: View, Else: View>(
        _ value: Value?,
        then: (Value) -> Then,
        else: () -> Else
    ) -> _ConditionalContent<Then, Else> {
        if let value = value {
            return ViewBuilder.buildEither(first: then(value))
        } else {
            return ViewBuilder.buildEither(second: `else`())
        }
    }
}

struct TestView: View {
    var test: String?

    var body: some View {
        Group {
            ifLet(test) { Text($0) }
            ifLet(test, then: { Text($0) }, else: { Text("Empty") })
        }
    }
}

@filimo
Copy link

filimo commented Feb 15, 2020

I did some cosmetic changes to easily understand your helpful solution.

extension View {
    func ifLet<Value, Then: View>(
        _ value: Value?,
        then: (Value) -> Then
    ) -> some View {
        ViewBuilder.buildIf(value.map { then($0) })
    }

    func ifLet<Value, Then: View, Else: View>(
        _ value: Value?,
        then: (Value) -> Then,
        else: () -> Else
    ) -> some View {
        value.map { ViewBuilder.buildEither(first: then($0)) }
            ?? ViewBuilder.buildEither(second: `else`())
    }
}

struct TestView: View {
    var test: String?

    var body: some View {
        Group {
            ifLet(test) { Text($0) }
            ifLet(test, then: { Text($0) }, else: { Text("Empty") })
        }
    }
}

@darrarski
Copy link
Author

@filimo Thanks! I don’t think extending View is a good idea. It looks really nice in your example, but it also allows writing such code:

var test: String? = nil
...
Text(“Hello, World!).ifLet(test) { Test($0) }

Which doesn’t feel right to me. Text(“Hello, World!”) will be ignored permanently, no matter if test is nil or has a value. Such code should not compile in my opinion.

@DevAndArtist
Copy link

@filimo Thanks! I don’t think extending View is a good idea.

I second this, unless this is implemented differently.

extension View {
  @ViewBuilder  
  func showWhen(_ condition: Bool) -> some View {
    if condition {
      self
    }  
  }
}

This would allow you to wrap the whole view with all its modifiers into an Optional.

@filimo
Copy link

filimo commented Feb 15, 2020

I got it. I saw this case in PureSwiftUI here

@State private var toggle = false

//...

// using RenderIf
var body: some View {
    RenderIf(toggle) {
        Text("It's true")
    }.elseRender {
        SFSymbol(.nosign)
    }
}

@filimo
Copy link

filimo commented Feb 16, 2020

@filimo Thanks! I don’t think extending View is a good idea. It looks really nice in your example, but it also allows writing such code:

var test: String? = nil
...
Text(“Hello, World!).ifLet(test) { Test($0) }

Which doesn’t feel right to me. Text(“Hello, World!”) will be ignored permanently, no matter if test is nil or has a value. Such code should not compile in my opinion.

it can be done with modifierIf

struct HideView: ViewModifier {
    func body(content: Content) -> some View {
        content.hidden()
    }
}

struct ModifierIfDemo: View {
    var hide: Bool? = nil

    var body: some View {
        Text("Hello, World!").modifierIf(hide ?? false, HideView())
    }
}

@filimo
Copy link

filimo commented Feb 16, 2020

one more way

func ifLet<T, V>(_ value: T?, then: (T) -> V) -> V? where V: View {
  value.map(then)
}

let optionalString: String? = "swift"

var body: some View {
  optionalString.map { string in 
    Text(string)
  }
}

@DevAndArtist
Copy link

@filimo
Copy link

filimo commented Feb 17, 2020

Oops, I didn't notice))

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