swiftswiftui

SwiftUI - understanding optional views modifiers


Please consider the following view which uses a failable initializer:

struct MyOptionalView: View {
    
    private let someParameter: String
    
    init?(someParameter: String?) {
        guard let someParameter else {
            return nil
        }
        self.someParameter = someParameter
    }
    
    var body: some View {
        VStack {
            Text(someParameter)
        }
    }
}

I can use it as

struct ContentView: View {
    var body: some View {
        VStack {
            MyOptionalView(someParameter: nil)
        }
    }
}

If I now try to add a modifier, e.g. .background(Color.green), XCode will automatically add a ? before the dot:

MyOptionalView(someParameter: nil)
        ?.background(Color.green)

but this code will not compile - XCode now shows an error

Expected ':' after '? ...' in ternary expression

I can remove the question mark and now the code will be built fine.

My question is - what does exactly happen while using optional views ? Why can I add the modifiers without unwrapping ? Are there any risks associated with these ?
I could barely find any information on the handling of the optional views in SwiftUI.


Solution

  • You are able to use values of optional types without unwrapping because Optional<T> conforms to View when T conforms to View! The effect is similar to when you use an if in a ViewBuilder.

    MyOptionalView(someParameter: nil)
    // is similar to
    if let view = MyOptionalView(someParameter: nil) {
        view
    }
    
    // and
    
    MyOptionalView(someParameter: nil)
        .background(.green)
    // is similar to
    Group {
        if let view = MyOptionalView(someParameter: nil) {
            view
        }
    }
    .background(.green)
    

    ?.background(Color.green) doesn't compile simply because that's not the correct syntax. If you want to put the view modifier on another line, the ? should be on the first line, and . on the second line.

    MyOptionalView(someParameter: nil)?
        .background(.green)