【问题标题】:How to loop over viewbuilder content subviews in SwiftUI如何在 SwiftUI 中遍历视图构建器内容子视图
【发布时间】:2021-01-22 01:58:01
【问题描述】:

所以我正在尝试创建一个视图,该视图采用 viewBuilder 内容,遍历内容的视图并在每个视图和另一个视图之间添加分隔符

struct BoxWithDividerView<Content: View>: View {
    let content: () -> Content
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }
    var body: some View {
        VStack(alignment: .center, spacing: 0) {
            // here
            
        }
        .background(Color.black)
        .cornerRadius(14)
    }
}

所以我想在我写“这里”的地方循环查看内容的视图,如果这有意义的话。我将编写一个不起作用的代码,但它解释了我想要实现的目标:

ForEach(content.subviews) { view  in
     view
     Divider()
}

怎么做?

【问题讨论】:

  • 我很确定你不能用ViewBuilder 做到这一点——它只是给你一个单一的视图,它是底层视图的组合。您想保持相同的 DSL 语法,您需要实现自己的 @_functionBuilder,类似于 ViewBuilder
  • 你知道怎么做吗?我正在尝试使用@_functionBuilder:@_functionBuilder struct UIViewFunctionBuilder { static func buildBlock(_ views: [V]) -> some View { return ForEach(views) { view in view Divider() } } }但是 V 应该符合 Identifiable
  • 网上有一些blogs。我没有亲自做过,所以我无法提供帮助。也许如果您对实现@_functionBuilder 有更具体的问题,您可以问另一个问题

标签: swiftui custom-view viewbuilder


【解决方案1】:

所以我最终这样做了

@_functionBuilder
struct UIViewFunctionBuilder {
    static func buildBlock<V: View>(_ view: V) -> some View {
        return view
    }
    static func buildBlock<A: View, B: View>(
        _ viewA: A,
        _ viewB: B
    ) -> some View {
        return TupleView((viewA, Divider(), viewB))
}
}

然后我像这样使用我的函数构建器

struct BoxWithDividerView<Content: View>: View {
    let content: () -> Content
    init(@UIViewFunctionBuilder content: @escaping () -> Content) {
        self.content = content
    }
    var body: some View {
        VStack(spacing: 0.0) {
            content()
        }
        .background(Color(UIColor.AdUp.carbonGrey))
        .cornerRadius(14)
    }
}

但问题是这只适用于最多 2 个表达式视图。我将发布一个单独的问题,说明如何将数组传递给它

【讨论】:

  • 我正在尝试完成完全相同的事情。您有没有想出一个更强大的解决方案,可以处理任意数量的视图?
  • @ab1470 不,我没有 :( 如果你知道,请告诉我
【解决方案2】:

我刚刚回答了另一个类似的问题,link here。将对链接的答案进行任何改进,因此请先检查那里。

Swift 包中此(但更高级)的 GitHub 链接here

但是,这里的答案具有相同的TupleView 扩展名,但查看代码不同。

用法:

struct ContentView: View {
    
    var body: some View {
        BoxWithDividerView {
            Text("Something 1")
            Text("Something 2")
            Text("Something 3")
            Image(systemName: "circle")  // Different view types work!
        }
    }
}

你的BoxWithDividerView

struct BoxWithDividerView: View {
    let content: [AnyView]
    
    init<Views>(@ViewBuilder content: @escaping () -> TupleView<Views>) {
        self.content = content().getViews
    }
    var body: some View {
        VStack(alignment: .center, spacing: 0) {
            ForEach(content.indices) { index in
                if index != 0 {
                    Divider()
                }
                
                content[index]
            }
        }
//        .background(Color.black)
        .cornerRadius(14)
    }
}

最后是主要的,TupleView 扩展:

extension TupleView {
    var getViews: [AnyView] {
        makeArray(from: value)
    }
    
    private struct GenericView {
        let body: Any
        
        var anyView: AnyView? {
            AnyView(_fromValue: body)
        }
    }
    
    private func makeArray<Tuple>(from tuple: Tuple) -> [AnyView] {
        func convert(child: Mirror.Child) -> AnyView? {
            withUnsafeBytes(of: child.value) { ptr -> AnyView? in
                let binded = ptr.bindMemory(to: GenericView.self)
                return binded.first?.anyView
            }
        }
        
        let tupleMirror = Mirror(reflecting: tuple)
        return tupleMirror.children.compactMap(convert)
    }
}

结果:

【讨论】:

  • 嘿,我喜欢你的解决方案,但我想更进一步,使用 ForEach 生成 BoxWithDividerView 的孩子。你会怎么做呢?当我尝试这样做时,我最终会遇到一堆协议错误。
  • @LucasC.Feijo 检查the repo。如果那里不存在该功能,请创建一个更详细的问题,我一定会看看
猜你喜欢
  • 2011-07-09
  • 2020-10-03
  • 2022-12-14
  • 2021-06-06
  • 1970-01-01
  • 2011-02-05
  • 1970-01-01
  • 2011-04-18
  • 2014-11-07
相关资源
最近更新 更多