4

Views often don't allow optional parameter values, resulting in an error like Initializer 'init(_:)' requires that 'String?' conform to 'StringProtocol':

struct Person {
    var name : String
}

struct OptionalsExampleView: View {

    var person : Person? = Person(name: "Bob")

    var body: some View {
        VStack() {
            Text("Name:")
            Text(person?.name)
        }

    }
}

Unfortunately, as of Xcode 11.4/iOS 13 the if let statement is not allowed in View Builder blocks, resulting in an error like Closure containing control flow statement cannot be used with function builder 'ViewBuilder':

struct OptionalsExampleView: View {

    var person : Person? = Person(name: "Bob")

    var body: some View {
        VStack() {
            if let person = person { // <-- not allowed
                Text("Name:")
                Text(person?.name)
            }
        }

    }
}

(I know that this question has been answered and asked quite a few times. I wrote this question and answered it myself to have a short overview article that's more concise than the existing articles so that I can link to it in places where this question comes up).

Ralf Ebert
  • 3,556
  • 3
  • 29
  • 43
  • 1
    Also consider [this one](https://stackoverflow.com/a/60225784/12299030) – Asperi May 20 '20 at 10:05
  • I saw those, I just wanted to create a good concise overview article that doesn't cause confusion so that I can link to. I think this article has value on its own, but okay... – Ralf Ebert May 20 '20 at 10:08
  • Please read carefully https://stackoverflow.com/help/how-to-ask – Asperi May 20 '20 at 10:18
  • I read that page. I don't know what you mean. I was not asking a question here, I was writing a Q&A-style wiki article (I answered the question myself) after teaching this to one of my students so that I could link to that piece of knowledge easily in the future. I don't know what's wrong about that, afaik this is welcome on Stack Overflow even if relates to existing questions. But sure, maybe it's too similar to the existing question. Just thought it might be helpful on it's own. btw, I love the work you're doing here, I learned a lot from your answers about SwiftUI so far! – Ralf Ebert May 20 '20 at 18:38

1 Answers1

5

a) In very simple cases, use optional fallbacks or an if-check with a force unwrap (if is allowed in View Builder blocks):

struct OptionalsExampleView: View {

    var person : Person? = Person(name: "Bob")

    var body: some View {
        VStack() {
            Text(person?.name ?? "")
            if person != nil {
                Text("Name:")
                Text(person!.name)
            }
        }
    }
}

b) Use Optional#map to show a single View if a single optional value is present:

struct OptionalsMapExampleView: View {

    var person : Person? = Person(name: "Bob")

    var body: some View {
        VStack() {
            person.map { person in
                VStack {
                    Text("Name:")
                    Text(person.name)
                }
            }
        }
    }

}

c) Wrap the if let block in a Group View with an explicit return type. if let is allowed here if it's the only statement in the View Builder block. If the block can return different View types, the view needs to be wrapped as AnyView:

struct OptionalsGroupExampleView: View {

    var person: Person? = Person(name: "Bob")

    var body: some View {
        VStack {
            Group { () -> AnyView in
                if let person = person {
                    return AnyView(VStack {
                        Text("Name:")
                        Text(person.name)
                    })
                } else {
                    return AnyView(EmptyView())
                }
            }
        }
    }

}

d) Extract the logic into a separate function - if let is allowed here because it's outside of the view builder block. If the function can return different View types, the views need to be wrapped as AnyView:

struct OptionalsFuncExampleView: View {

    var person : Person? = Person(name: "Bob")

    var body: some View {
        VStack() {
            personView()
        }
    }

    func personView() -> some View {
        if let person = person {
            return AnyView(
                VStack {
                    Text("Name:")
                    Text(person.name)
                }
            )
        } else {
            return AnyView(EmptyView())
        }
    }

}

e) Use a helper View type that wraps the if-let condition, example implementations: IfLet / OptionalView; also available as function ifLet.

Ralf Ebert
  • 3,556
  • 3
  • 29
  • 43