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
💡 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:
✅ 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)
}
}
}
}