在Swiftui中有选择地隐藏子视图

92dk7w1h  于 2023-03-28  发布在  Swift
关注(0)|答案(1)|浏览(202)

我想做一个视图,通过一个viewBuilder接收异构的内容,像正常一样,然后有选择地只显示一个这样提供的子视图,隐藏其余的。
类似于TabView的功能**,但在我的编程控制下,没有实际的标签栏,强制背景和全屏抓取。它应该能够接受任何类型的混合内容项,如ForEach输出,Group元素等。就像TabView一样,或VStack或HStack或List或任何接受许多孩子并以不同方式对待每个孩子的Apple视图。
我拥有的:

但是我只希望这三个子视图中的一个可见(我的选择)。
下面是我想使工作的代码,目标应该是明确的...

import SwiftUI

struct SwitcherView<Content>: View where Content: View {

  let content: () -> Content
  // NOT this, yuck:
  // let content: [AnyView]
  @State var selection: Int = 0 // will be changed by logic not shown

  public init(@ViewBuilder content: @escaping () -> Content) {
    self.content = content
  }

  @MainActor public var body: some View {
    VStack {
      //// bad approaches:

      //// obviously this doesn't work, I wish...
      // (Group { content() })[selection]

      //// This would work, if Layouts could hide
      //// subviews (they can't)
      // MyPickOneLayout(selection) { content() }

      //// With the [AnyView] above, this "works" but
      //// is slow, and awful to use as a client
      // content[selection]

      //// Would work, if modifyEach existed, and would
      //// connect back to this view via Preferences
      //// or Environment:
      // content().modifyEach( MyHideShowModifier() )

      //// Requiring the client to wrap each view in
      //// MyHideShowModifier itself might work but is
      //// intrusive to the client

      //// not what I want:
      content()
    }
  }
}

struct SwitcherView_Previews: PreviewProvider {

  static var previews: some View {
    VStack {
      Text("I would like to show just one of these at a time:")
      SwitcherView {
        // Note heterogeneous content: each element
        // has its own type and state, none of which
        // SwitcherView gets to know about explicitly..
        // just like TabView!
        Text("First Content")
          .frame(width: 100, height: 100)
          .background(.red)
        Button(action: {}, label: {
          Text("I'm a yellow button!")
        })
        .buttonStyle(.borderedProminent)
        .tint(.green)
        .frame(width: 100, height: 100)
        HStack {
          Text("Some thing").background(.yellow)
          Text("Else").background(.blue)
        }
        .frame(width: 100, height:100)
        .background(.brown)
      }
    }
  }
}

TabView做到了,所以这显然是可能的!(对于苹果来说。
如上文评论所述,迄今为止尝试的方法:

    • 在自定义Layout中私下 Package 内容:* Layout s不能隐藏子视图,它们必须定位并显示所有子视图
    • 以数组的形式接收内容:* 非常不受SwiftUI的欢迎,并强制使用AnyView,这是性能毒药
    • 使用特殊的ViewModifier Package 每个子视图,然后使用首选项或环境系统连接回SwitcherView以进行显示/隐藏逻辑:* 强制所有内容的最外层ViewModifier成为此修饰符,并显示缺少它的所有内容。
    • 显式列出我的子视图,并将显示/隐藏逻辑附加到每个子视图:* 封装不好,视图很大。(但这是我的真实的应用程序现在正在做的事情,我想改进)

我是不是找错人了?我该去哪找?

nwnhqdif

nwnhqdif1#

如果你想有选择地隐藏一个子视图,一种方法是使用一个父视图,它包含一个数组和当前选择。有了这个信息,你就可以从数组中选择要显示的子视图。
为了演示这一点,我创建了一个Model结构体,其中包含一个word和一个color

struct Model {
    let word: String
    let color: Color
}

父视图有一个modelselection state variable的数组,表示当前选定数组的索引。
SwitcherView是一个通用视图,它接受一个选择绑定和一个闭包,该闭包返回要显示的内容。在本例中,内容是一个带有所选单词的彩色正方形。
总的来说,这种方法允许我们通过改变选择状态变量来选择性地显示子视图。

struct ContentView: View {
    let data: [Model] = [.init(word: "the",
                               color: .red), 
                         .init(word: "cow", 
                               color: .yellow),
                         .init(word: "say", 
                               color: .green),
                         .init(word: "moo", 
                               color: .blue)]
    
    @State var selection: Int = 0
    
    var body: some View {
        SwitcherView(selection: $selection) {
            Text(data[selection].word)
                .frame(width: 100, height: 100)
                .background(data[selection].color)
            
        }
    }
}

struct SwitcherView<Content>: View where Content: View {
    let content: () -> Content
    @Binding var selection: Int
    
    public init(selection: Binding<Int>,
                @ViewBuilder content: @escaping () -> Content) {
        self._selection = selection
        self.content = content
    }
    
    @MainActor public var body: some View {
        VStack {
            Text("I would like to pick out just #\(selection) of these:")
            content()
            Text("But here they all are...")
            Button {
                selection = (selection + 1) % 4
            } label: {
                Text("next")
            }.buttonStyle(.borderedProminent).padding()
        }
    }
}

此代码中需要改进的一个方面是用于表示数组大小的硬编码值4。如果数组的大小发生更改,则可能导致内存泄漏。
要解决这个问题,建议使用数组的实际大小而不是硬编码的值。这可以通过将数组的count属性传递给子视图或使用一个常量来实现,该常量在数组更改大小时更新。
通过这样做,子视图将始终具有正确的大小,并且您不必担心每次数组更改时手动更新值。

相关问题