【问题标题】:Dynamically hiding view in SwiftUI在 SwiftUI 中动态隐藏视图
【发布时间】:2019-10-22 17:18:03
【问题描述】:

我正在尝试在 SwiftUI 中有条件地隐藏 DatePicker。但是,我对不匹配的类型有任何问题:

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

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

更新

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

【问题讨论】:

    标签: swift swiftui


    【解决方案1】:

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

    struct ContentView: View {
        @State private var showText = true
    
        var body: some View {
            VStack {
                Button("Toggle text") {
                    showText.toggle()
                }
    
                if showText {
                    Text("Hello World!")
                }
            }
        }
    }
    

    showText 等于false 时,这将从层次结构中删除Text 视图。如果您希望选择保留空间或将其用作修饰符,请参见下文。


    我创建了一个扩展,所以你可以使用修饰符,比如隐藏视图:

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

    或完全删除:

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

    如果你想使用 Swift Packages,下面的扩展也可以在 GitHub 上找到:GeorgeElsham/HidingViews


    这里是创建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
            }
        }
    }
    

    【讨论】:

    • 如果用于隐藏表单中的对象,它仍然会显示一个可点击的空白视图。
    • 这太棒了!你有没有在任何地方发表过这个?我很想分享它,但分享 StackOverflow 答案而不是代码仓库很奇怪。
    • @BenLeggiero 现在可以找到here
    • @atulkhatri 如果要完全删除视图,可以编辑正文函数以在组中返回 EmptyView() 而不是 content。我想建议将此作为修饰符的小调整/标志以及@George_E。
    • 我导入了你的包(Xcode 12.3),效果很好!
    【解决方案2】:

    ✅正确和最简单的方法:

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

    .opacity(isHidden ? 0 : 1)
    

    演示


    ? 更清洁的方式! - 扩展原来的hidden修饰符:

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

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

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

    DatePicker($datePickerDate)
        .hidden(showDatePicker)
    

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


    ⛔️不要使用不良做法!!!

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

    ? 分支示例:

    ✅依赖代码示例:

    为不同状态返回逻辑SAME视图会导致 SwiftUI 渲染引擎重新渲染并再次初始化视图并导致性能下降! (在 this WWDC session 上查看更多信息)

    【讨论】:

    • 我喜欢这个答案,因为它仍然会保留视图的布局空间。使用 .hidden() 也有好处,但必须使用带有 .hidden() 的 if else 条件来保留空间似乎不是最佳选择。
    • 我同意,将视图保留在层次结构中是一种更好的实践 IMO,因为如果您决定引入动画,动画引擎将有一些东西可以使用,而不是“弹出”视图存在。
    • 为什么保留布局空间是一件“好事”?
    • @zaitsman 视情况而定,但在某些情况下,它可以防止不需要的内容跳转
    • “不要使用不良做法!!!”考虑到动态添加/删除视图需要分支,这有点极端。如果不能,您能否提供一种不使用分支的方法?
    【解决方案3】:

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

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

    【讨论】:

    • 我创建了一个ViewModifier,我认为它更干净,请参阅below
    • .hidden()的目的是什么?
    • @MichaelOzeryansky 不确定。我可能会使用第二个示例。
    • @Eugene 我想我主要想知道为什么hidden() 不接受布尔值。
    • @MichaelOzeryansky 是的我也不知道,我也觉得应该。
    【解决方案4】:

    这是在 SwiftUI 中显示/隐藏视图的简单方法。

    1. 添加@State变量:

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

      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. 将@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)
      
                  })
      

    【讨论】:

    • 这应该是公认的答案!这是声明式接口的最佳实践!谢谢!
    【解决方案5】:

    2021 年 11 月 4 日编辑

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

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

    保留空间

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

    即使我们在这里只调整不透明度,触摸 DatePicker 隐藏时应该所在的空间也不会打开日历。

    不要保留空间

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

    原答案

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

    所有代码 sn-ps 都需要 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)
    

    【讨论】:

    • 由于Show 不会改变isVisible,它不需要绑定,或者var。您可以将其声明为正常的let isVisible: Bool,删除$,SwiftUI 仍会在更改后重新创建视图。
    • @AvielGross 你是对的,谢谢!我编辑了我的原始答案。那时我还在为新范式苦苦挣扎。
    • 不用担心!我也花了一段时间来解决这个问题! SwiftUI 几乎就像重新学习编程一样(:
    【解决方案6】:

    按住 Command 键单击有问题的视图并选择 Beta 5 中的 Make Conditional 选项。我在我的一个视图 (LiftsCollectionView) 上执行此操作,它生成了以下内容:

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

    【讨论】:

    • EmptyView() 是重点。它实际上消除了 hidden() 简单地使透明但仍然存在的视图的存在。
    【解决方案7】:

    您还可以在任何View 上使用opacity 修饰符:

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

    【讨论】:

      【解决方案8】:

      即使没有占位符视图或调用隐藏,以下内容也可以工作(iOS13.1 和 Swift 5)

      struct Foo: View {
          @State var condition: Bool
      
          var body: some View {
              if self.condition {
                  Text("Hello")
              }
          }
      }
      

      如果不查看 @ViewBuilder 实现,很难确切知道,但是在评估条件时,如果默认失败,我们似乎会得到一个 EmptyView

      所以这相当于这里的一些答案,但更简单。

      【讨论】:

        【解决方案9】:

        以下自定义修饰符的工作方式与 .hidden() 一样,隐藏视图并禁用与它的交互。

        ViewModifier 和视图扩展函数 -

        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)
        

        注意 - 有条件地应用修饰符是低效的,因为 swift 将未修改和已修改的视图视为不同的类型。这会导致视图(及其状态)在每次条件更改时被销毁和重建。对于 List 等数据量大的视图,这可能会成为一个问题。有条件地更改修饰符的参数不会导致此问题。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-02-15
          • 1970-01-01
          • 2020-09-10
          • 2020-06-21
          • 2021-03-03
          • 1970-01-01
          • 2020-05-09
          相关资源
          最近更新 更多