在 SwiftUI 中动态隐藏视图

我试图在 SwiftUI 中有条件地隐藏 DatePicker。但是,我遇到了不匹配类型的问题:

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

在这种情况下,datePickerDatePicker<EmptyView>类型,而 datePicker.hidden()_ModifiedContent<DatePicker<EmptyView>, _HiddenModifier>类型。所以我不能把 datePicker.hidden()分配给 datePicker。我已经尝试了不同的方法,但似乎找不到一种有效的方法。有什么想法吗?

更新

您可以展开 _ModifiedContent类型以使用它的 content属性获取基础类型。然而,这并不能解决根本问题。content属性似乎只是原始的、未修改的日期选择器。

109919 次浏览

而不是动态设置一个变量并在我的视图中使用它,我发现我能够以这种方式隐藏或显示日期选择器:

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

或者,可以选择不包括日期选择器,而是隐藏它:

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


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

Something 正确和最简单的方法:

你可以改为设置 alpha,这样也可以保留视图的布局空间,并且不会像其他答案那样强迫你添加虚拟视图:

.opacity(isHidden ? 0 : 1)

演示

Demo


更干净的方式!-扩展原来的 hidden修改器:

此外,您还可以实现一个自定义函数来获取可见性状态作为参数:

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

现在只需将 bool传递给修饰符:

DatePicker($datePickerDate)
.hidden(showDatePicker)

注意,hidden修饰符的原始行为不同,这两个方法都保留了隐藏视图的框架。


⛔️ Don't use bad practices !!!

所有其他的答案(包括@Jake 接受的答案)都使用分支,而不是导致性能下降的依赖代码。

例如:

Branch

✅ Dependent Code example:

Dependent Code example

返回不同状态的 逻辑 SAME 观点会导致 SwiftUI 呈现引擎重新呈现并再次初始化视图,从而导致性能下降!(详情请参阅 本次 WWDC 会议)

命令单击所讨论的视图,并在 Beta 5中选择“制作条件”选项。我在我的一个视图(LiftsCollectionView)上做了这个操作,它生成了以下内容:

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

隐藏视图最简单和最常见的方法如下:

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)

或者完全移除:

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

如果您想使用 Swift Packages: 乔治 · 埃尔舍姆/隐藏视图,下面的扩展也可以在 GitHub 上找到。


下面是创建 View修饰符的代码:

我建议您在自己的文件中使用这段代码(请记住 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
}
}
}

你也可以在任何 View上使用 opacity修饰符:

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

编辑 Nov 42021

我现在更喜欢另一种方法,而不是我原来的答案(如下) :

有两种可能的解决方案,取决于您是想保持原始空间被占用,还是让其他视图占用隐藏视图的空间。

保持空间

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.

不要占用空间

if showDatePicker {
DatePicker("Choose date", selection: $datePickerDate)
}

原始答案

对于将来需要它的人,我创建了一个 ViewModifier,它接受一个 Bool作为参数,这样您就可以通过设置 showDatePicker: Bool变量绑定一个布尔值来声明性地显示和隐藏视图。

所有代码段都需要 import SwiftUI

返回文章页面 ViewModifier:

struct Show: ViewModifier {
let isVisible: Bool


@ViewBuilder
func body(content: Content) -> some View {
if isVisible {
content
} else {
content.hidden()
}
}
}

功能:

extension View {
func show(isVisible: Bool) -> some View {
ModifiedContent(content: self, modifier: Show(isVisible: isVisible))
}
}

你可以这样使用它:

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

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

  1. 添加 @State变量:

    @State private var showLogo = false
    
  2. 添加如下条件:

    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. 将@State 变量的状态更改为显示/隐藏视图,如下所示:

    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

即使没有占位符视图或调用 hide (iOS13.1和 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.

所以这个等价于这里的一些答案,但是它更简单。

下面的自定义修饰符通过隐藏视图和禁用与视图的交互来起作用。

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

使用-

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

注 - 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.

我遇到了同样的问题,我用以下方法解决了它:

注意: 我使用 有约束力来隐藏和/或动态显示。

1-创建修饰符

struct HiddenModifier: ViewModifier{
var isHide:Binding<Bool>
    

func body(content: Content) -> some View {
if isHide.wrappedValue{
content
.hidden()
}
else{
content
}
}
}

2-添加修饰符到视图:

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

3-使用修饰语

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

上述解决方案的不良部分 .isHidden(true, remove: true)是 onAppear 回调将不会在 remove = true时调用。

.opacity(isHidden ? 0 : 1)绝对是正确的方法。