Conditional rendering with optionals in SwiftUI

Viewed 6710

Coming from a React background it's easy to only render a view if a value is defined. It looks like this:

function Component ({ profile }) {
  return (
    <div>{profile && <div>{profile.name}}</div>
  )
}

But I'm finding this pattern much more difficult to replicate in SwiftUI. Ideally we could use conditional unwrapping in our Views, but this doesn't currently work. The only solution I've been able to figure out is really inelegant:

struct ProfileView : View {
    var profile: Profile?

    var body : some View {
        if let profile = profile {
            return Text("profile: \(profile.bio)")
        }  else {
            return Text("")
        }
    }
}

struct LayoutView : View {
    @State var profile: Profile?
    var body : some View {
        Group {
            ProfileView(profile: profile)
        }
    }.onAppear(perform: fetch)

    // fetch method 
}

Does anyone have some better strategies for conditional rendering using optional values?

3 Answers

You can go the other way around using map to take care of the optional with something like this:

struct ProfileView : View {
    var profile: Profile?

    var body : some View {
        profile.map { Text("profile: \($0.bio)") }
    }
}

($0 is your unwrapped profile in this example.)

If you need the else case:

profile.map { Text($0.bio) } ?? Text("Not available")

Just do this:

struct LayoutView : View {
    @State var profile: Profile?
    var body : some View {
        Group {
            if profile != nil {
                ProfileView(profile: profile!)
            }
        }
    }.onAppear(perform: fetch)

    // fetch method 
}

I would use @Binding in a child view ProfileView to ensure the text will be in sync with profile data as it changes. This is how I would do it all:

struct Profile {
    var bio: String = "Biography"
}

struct ProfileView : View {
    @Binding var profile: Profile?
    private var profileText: String {
        get {
            profile == nil ? "" : "profile: " + profile!.bio
        } // set part is not necessary if we don't set the text from the view
        set {
            self.profile?.bio = newValue
        }
    }

    var body : some View {
        Text(profileText)
    }
}

struct LayoutView : View {
    @State var profile: Profile?
    var body : some View {
        Group {
            ProfileView(profile: $profile)
        }
    }
}

// check it via preview
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        LayoutView(profile: Profile())
    }
}
Related