Dynamically hiding view in SwiftUI

SwiftSwiftui

Swift Problem Overview


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.

Swift Solutions


Solution 1 - Swift

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 - Swift

✅ 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 - Swift

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)
            }
        }
    }
}

Solution 4 - Swift

Here is the simple way to Show/Hide view in SwiftUI.

  1. Add @State variable:

    @State var showLogo = false
    
  2. Add condition like below:

    VStack {
                if showLogo == true {
                Image(systemName: "house.fill")
                    .resizable()
                    .frame(width: 100, height: 100, alignment: .center)
                    .foregroundColor(Color("LightGreyFont"))
                    .padding(.bottom, 20)
                }
                Text("Real State App")
                    .font(Font.custom("Montserrat-Regular", size: 30))
            }.padding(.vertical, 25)
    
  3. Change state of your @State variable to Show/Hide the view like below:

    Button(action: {
                    withAnimation{
                        self.showLogo.toggle()
                    }
                    
                }, label: {
                    Text("Login").font(.system(size: 20, weight: .medium, design: .default))
                        .frame(minWidth: 0, maxWidth: .infinity, maxHeight: 50)
                        .foregroundColor(Color("BlackFont"))
                        .cornerRadius(10)
    
                })
    

enter image description here

Solution 5 - Swift

Edit Nov 4 2021

I now prefer another approach over the one in my original answer (below):

There are two possible solutions depending on if you want to keep the original space occupied or make the other views take the space of the one that's hidden.

Keep the space
DatePicker("Choose date", selection: $datePickerDate)
    .opacity(showDatePicker ? 1 : 0)

Even if we are adjusting just opacity here, touching the space where the DatePicker should be when it's hidden doesn't open the calendar.

Don't keep the space
if showDatePicker {
    DatePicker("Choose date", selection: $datePickerDate)
}

Original answer

For whoever needs it in the future, I created a ViewModifier which takes a Bool as parameter so you can bind a boolean value to show and hide the view declaratively by just setting your showDatePicker: Bool variable.

All code snippets require import SwiftUI.

The ViewModifier:
struct Show: ViewModifier {
    let isVisible: Bool

    @ViewBuilder
    func body(content: Content) -> some View {
        if isVisible {
            content
        } else {
            content.hidden()
        }
    }
}
The function:
extension View {
    func show(isVisible: Bool) -> some View {
        ModifiedContent(content: self, modifier: Show(isVisible: isVisible))
    }
}

And you can use it like this:

var datePicker = DatePicker($datePickerDate)
                     .show(isVisible: showDatePicker)

Solution 6 - Swift

Command-click the view in question and select the Make Conditional option in Beta 5. I did this on one of my views (LiftsCollectionView), and it generated the following:

    if suggestedLayout.size.height > 150 {
      LiftsCollectionView()
    } else {
      EmptyView()
    }

Solution 7 - Swift

You also have the opacity modifier on any View:

ActivityIndicator(tint: .black)
   .opacity(self.isLoading ? 1.0 : 0.0)

Solution 8 - Swift

The following also works even without a placeholder view or calling hidden (iOS13.1 and Swift 5)

struct Foo: View {
    @State var condition: Bool

    var body: some View {
        if self.condition {
            Text("Hello")
        }
    }
}

It's hard to know exactly without peeking at the @ViewBuilder implementation, but when evaluating a conditional, it seems that we are getting an EmptyView if it fails by default.

So this is equivalent to some of the answers here, but it's simpler.

Solution 9 - Swift

The following custom modifier works as .hidden() does by both hiding the view and disabling interaction with it.

ViewModifier and View extension func -

import SwiftUI

fileprivate struct HiddenIfModifier: ViewModifier {
  var isHidden: Bool
  
  init(condition: Bool) {
    self.isHidden = condition
  }
  
  func body(content: Content) -> some View {
    content
      // Conditionally changing the parameters of modifiers
      // is more efficient than conditionally applying a modifier
      // (as in Cristina's ViewModifier implementation).
      .opacity(isHidden ? 0 : 1)
      .disabled(isHidden)
  }
}

extension View {
    /// Hides a view conditionally.
    /// - Parameters:
    ///   - condition: Decides if `View` is hidden.
    /// - Returns: The `View`, hidden if `condition` is `true`.
    func hidden(if condition: Bool) -> some View {
        modifier(HiddenIfModifier(condition: condition))
    }
}

Use -

DatePicker($datePickerDate)
  .hidden(if: !self.showDatePicker)

Note - Conditionally applying a modifier is inefficient because swift sees the unmodified and modified views as different types. This causes the view (and it's state) to be destroyed and rebuilt every time the condition changes. This can become an issue for data heavy views like List. Conditionally changing the parameters of modifiers doesn't cause this issue.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJakeView Question on Stackoverflow
Solution 1 - SwiftGeorgeView Answer on Stackoverflow
Solution 2 - SwiftMojtaba HosseiniView Answer on Stackoverflow
Solution 3 - SwiftJakeView Answer on Stackoverflow
Solution 4 - SwiftiVarunView Answer on Stackoverflow
Solution 5 - SwiftCristina De RitoView Answer on Stackoverflow
Solution 6 - SwiftJustin EkinsView Answer on Stackoverflow
Solution 7 - SwiftPiKeyView Answer on Stackoverflow
Solution 8 - SwiftFranMowinckelView Answer on Stackoverflow
Solution 9 - SwiftZach FosterView Answer on Stackoverflow