Dynamically hiding view in SwiftUI

I'm trying to conditionally hide a DatePicker in SwiftUI. However, I'm having any issue with mismatched types:

var datePicker = DatePicker($datePickerDate)
if self.showDatePicker {
    datePicker = datePicker.hidden()
}

In this case, datePicker is a DatePicker<EmptyView> type but datePicker.hidden() is a _ModifiedContent<DatePicker<EmptyView>, _HiddenModifier>. So I cannot assign datePicker.hidden() to datePicker. I've tried variations of this and can't seem to find a way that works. Any ideas?

UPDATE

You can unwrap the _ModifiedContent type to get the underlying type using it's content property. However, this doesn't solve the underlying issue. The content property appears to just be the original, unmodified date picker.


Solution 1:

The simplest and most common way to hide a view is like the following:

struct ContentView: View {
    @State private var showText = true

    var body: some View {
        VStack {
            Button("Toggle text") {
                showText.toggle()
            }

            if showText {
                Text("Hello World!")
            }
        }
    }
}

This removes the Text view from the hierarchy when showText equals false. If you wish to have an option to preserve the space or want it as a modifier, see below.


I created an extension, so you can use a modifier, like so to hide the view:

Text("Hello World!")
    .isHidden(true)

Or for complete removal:

Text("Label")
    .isHidden(true, remove: true)

The extension below is also available on GitHub here if you want to use Swift Packages: GeorgeElsham/HidingViews.


Here is the code to create the View modifier:

I recommend you use this code in its own file (remember to import SwiftUI):

extension View {
    /// Hide or show the view based on a boolean value.
    ///
    /// Example for visibility:
    ///
    ///     Text("Label")
    ///         .isHidden(true)
    ///
    /// Example for complete removal:
    ///
    ///     Text("Label")
    ///         .isHidden(true, remove: true)
    ///
    /// - Parameters:
    ///   - hidden: Set to `false` to show the view. Set to `true` to hide the view.
    ///   - remove: Boolean value indicating whether or not to remove the view.
    @ViewBuilder func isHidden(_ hidden: Bool, remove: Bool = false) -> some View {
        if hidden {
            if !remove {
                self.hidden()
            }
        } else {
            self
        }
    }
}

Solution 2:

✅ The correct and Simplest Way:

You can set the alpha instead, this will preserve the layout space of the view too and does not force you to add dummy views like the other answers:

.opacity(isHidden ? 0 : 1)

Demo

Demo


💡 Cleaner Way! - Extend original hidden modifier:

Also, you can implement a custom function to get the visibility state as an argument:

extension View {
    func hidden(_ shouldHide: Bool) -> some View {
        opacity(shouldHide ? 0 : 1)
    }
}

Now just pass the bool to the modifier:

DatePicker($datePickerDate)
    .hidden(showDatePicker)

Note that unlike the original behavior of the hidden modifier, both of these methods preserve the frame of the hiding view.


⛔️ Don't use bad practices !!!

All other answers (including the accepted answer by @Jake) use branches instead of dependent code that cause a performance hit.

🛑 Branch example:

Branch

✅ Dependent Code example:

Dependent Code example

Returning logical SAME view for different states causes the SwiftUI to render engine to re-render and initial a view again and cause a performance hit! (see more at this WWDC session)

Solution 3:

Rather than dynamically setting a variable and using it in my view, I found that I was able to hide or show the date picker this way:

struct ContentView : View {
    @State var showDatePicker = true
    @State var datePickerDate: Date = Date()

    var body: some View {
        VStack {
            if self.showDatePicker {
                DatePicker($datePickerDate)
            } else {
                DatePicker($datePickerDate).hidden()
            }
        }
    }
}

Or, optionally, not including the date picker instead of hiding it:

struct ContentView : View {
    @State var showDatePicker = true
    @State var datePickerDate: Date = Date()

    var body: some View {
        VStack {
            if self.showDatePicker {
                DatePicker($datePickerDate)
            }
        }
    }
}