The simpler approach is to just use .map()
It will show a UserProfile
if user
has a value, and nothing if user
is nil:
var user: User?
var body: some View {
user.map { UserProfile(user: $0) }
}
If you want to provide a default view for nil value, you can just use the nil-coalescing operator:
var user: User?
var body: some View {
user.map { UserProfile(user: $0) } ?? UserProfile(user: .default)
}
However this requires the two views to be of the same type, and in practice if they're of the same type you're better off just writing UserProfile(user: $0 ?? .default)
.
What's interesting is the case where the two views are not of the same type. You could just erase their type by wrapping them in AnyView
, but it does get a bit cumbersome and difficult to read at this point:
var user: User?
var body: some View {
user.map { AnyView(UserProfile(user: $0)) } ?? AnyView(Text("Not logged in"))
}
So in this case, my preferred approach is to use a custom IfLet
struct (same name as Procrastin8's, but a different implementation and syntax) that allows me to write something like this, which I find very clear in intent and easy to type.
var user: User?
var body: some View {
IfLet(user) { UserProfile(user: $0) }
.else { Text("Not logged in") }
}
The code for my IfLet
component is as follows.
struct IfLet<Wrapped, IfContent> : View where IfContent: View {
let optionalValue: Wrapped?
let contentBuilder: (Wrapped) -> IfContent
init(_ optionalValue: Wrapped?, @ViewBuilder contentBuilder: @escaping (Wrapped) -> IfContent) {
self.optionalValue = optionalValue
self.contentBuilder = contentBuilder
}
var body: some View {
optionalValue.map { contentBuilder($0) }
}
func `else`<ElseContent:View>(@ViewBuilder contentBuilder: @escaping () -> ElseContent) -> some View {
if optionalValue == nil {
return AnyView(contentBuilder())
} else {
return AnyView(self)
}
}
}
Enjoy!