168

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.

14 Answers 14

222

✅ 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 causes a performance hit.

🛑 Branch example: (from WWDC)

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)

Sign up to request clarification or add additional context in comments.

27 Comments

I like this answer because it will still preserve the layout space of the view. Using .hidden() is good for that too but it doesn't seem optimum to have to use an if else condition with the .hidden() to preserve the space.
Why is preserving layout space a 'GOOD' thing?
@zaitsman Depends on situation, but in some cases it prevents unwanted content jumps
"Don't use bad practices !!!" is a bit extreme considering branching is needed to dynamically add/remove a view. If not can you provide a way that doesn't use branching?
One thing to watch out for here is that changing the opacity will not update the accessibility hierarchy correctly. Using .hidden() will preserve the layout while removing the item from the accessibility hierarchy.
|
191

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

11 Comments

If used to hide an object in a Form, it will still show a tappable blank view.
This is great! Have you published this anywhere? I'd love to share it but it's odd sharing a StackOverflow answer instead of a code repo.
@BenLeggiero It can now be found here.
@atulkhatri if you want to completely remove the view, you can edit the body function to return an EmptyView() in the group instead of the content. I would like to suggest this as minor tweak/flag for the modifier as wel @George_E.
I imported your package (Xcode 12.3) and it works great!
|
63

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

9 Comments

I create a ViewModifier which I think is much cleaner, see it below.
What is the purpose of .hidden()?
@MichaelOzeryansky Not sure. I would likely use the second example.
@Eugene I guess I'm mostly wondering why hidden() doesn't take a bool.
@MichaelOzeryansky Yeah I don't know either, I also feel that it should.
|
33

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

  1. Add @State variable:

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

    VStack {
        if showLogo {
            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)
    
    })
    

If you want to preserve space you can also use .opacity(showLogo ? 1 : 0) modifier.

enter image description here

2 Comments

This should be the accepted answer! It is best practice for a declarative interface! Thanks!
if showLogo == true Camparison to boolean seems suspicious. Also looks like using branches in view code considered to be a bad provtice.
20

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)

3 Comments

Since Show does not mutate isVisible, it does not need to be binding, or var. You can just declare it as normal let isVisible: Bool, drop the $, and SwiftUI will still re-create the view upon changes.
@AvielGross you're right, thank you! I edited my original answer. I still struggled with the new paradigm back then.
no worries! It also took me a WHILE to wrap my head around this! SwiftUI is almost like learning to program all over again (:
6

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

2 Comments

EmptyView() is the point. It actually erases existence of the view where hidden() simply make is transparent but still exists.
For "Don't keep the space" I don't see any other solution other than the if..else condition.
6

I had the same problem, and I solved it in the following way:

Note: I use binding to hide and/or show dynamically.

1 - Create Modifier

struct HiddenModifier: ViewModifier{
    var isHide:Binding<Bool>
    
    func body(content: Content) -> some View {
        if isHide.wrappedValue{
            content
                .hidden()
        }
        else{
            content
        }
    }
}

2 - Add Modifier to view:

extension View{
    func hiddenModifier(isHide:Binding<Bool>) -> some View{
        return self.modifier(HiddenModifier(isHide: isHide))
    }
}

3 - Use Modifier

struct CheckHiddenView: View {
    @State var hide:Bool = false
    
    var body: some View {
        VStack(spacing: 24){
            Text("Check Hidden")
                .font(.title)
            
            RoundedRectangle(cornerRadius: 20)
                .fill(Color.orange)
                .frame(width: 150, height: 150, alignment: .center)
                .hiddenModifier(hide: $hide)
            
            Button {
                withAnimation {
                    hide.toggle()
                }
                
            } label: {
                Text("Toggle")
            }
            .buttonStyle(.bordered)
            
        }
    }
}

Test

Check Swift hidden

2 Comments

Best answer! This is the way to go.
Why do you use a binding since you don't change the wrappedValue in the ViewModifier?
5

Please review Apple's article Choosing the right way to hide a view.

Contrary to what many other answers here suggest, Apple's article states that a conditional is the correct solution.

The exception is when you want to preserve an empty space where the hidden view used to be. In that case, using .opacity(0) is the correct solution.

.hidden() is only suggested if the visibility of the view is always off. I suspect that's equivalent to .opacity(0), perhaps simply being a clearer expression of intent for exactly the same solution under the hood.

If you do want a modifier to make it convenient to do this, I would offer the following tweak on other people's work:

extension View {
    /// Hide or show a view based on a boolean value.
    ///
    /// Example for hiding while reclaiming space:
    ///
    ///     Text("Label")
    ///         .isHidden(true)
    ///
    /// Example for hiding, but leaving a gap where the hidden item was:
    ///
    ///     Text("Label")
    ///         .isHidden(true, remove: false)
    ///
    /// - Parameters:
    ///   - hidden: whether to hide the view.
    ///   - remove: whether you want to reclaim the space taken by the hidden view.
    @ViewBuilder func isHidden(_ hidden: Bool, remove: Bool = true) -> some View {
        if remove {
            if !hidden {
                self
            }
        } else {
            self.opacity(hidden ? 0 : 1)
        }
    }
}

Arranging the conditional that way has the advantage that, at least in the case where remove is set to false, the structural identity of the hidden view is consistent through animation of the hidden parameter.

On the other hand, if remove is set to true, there is no view in the hidden case, so it's not possible to maintain structural identity. So if you are animating the hidden parameter and remove is true, you might run into some unpleasant animation (depending on where SwiftUI determines is the best guess for the geometry of the removed view). To solve that, the answer, in theory, is to use .matchedGeometryEffect (see this to explore this technique). But I have found matchedGeometryEffect to be buggy, so for now, I would recommend that you turn animation off, and avoid all these headaches.

Comments

4

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.

Comments

3

SwiftUI’s built-in .hidden() can be enhanced to handle accessibility and interaction better. Here’s a custom extension:

extension View {
    @inlinable nonisolated public func hidden(_ hidden: Bool) -> some View {
        self
            .opacity(hidden ? 0 : 1)
            .disabled(hidden)
            .accessibilityHidden(hidden)
    }
}

Usage:

Text("Hello, World!")
    .hidden(true)

Comments

2

The bad part of the above solution .isHidden(true, remove: true) is onAppear callback will not be called when remove = true.

.opacity(isHidden ? 0 : 1) is definitely the right way.

Comments

1

You also have the opacity modifier on any View:

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

Comments

1

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.

Comments

1

the opacity modifier is good if you want to reserve the space. If however you want to remove the view then my approach would me to simply to only show it if condition is met

if requiredCondition {
 aViewOnlyShownIfConditionRequirmentIsMet()
}

So not even in the view hierarchy. If need be make requiredCondition a @State Variables

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.