You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确检查SwiftUI View Tree 实现自定义List内容展平为Section数组

实现方案

核心思路

SwiftUI 视图为不透明类型,无法直接遍历解析视图树结构,因此我们采用官方提供的 PreferenceKey 机制,从子视图向上传递 Section 信息,自动合并所有层级的 Section 数据,最终得到平铺的 Section 数组。

具体实现步骤

1. 定义Section存储模型与偏好键

首先我们定义抹掉类型的 Section 存储结构,以及用来传递 Section 数组的偏好键:

import SwiftUI

// 抹掉类型的Section存储结构
struct SectionItem: Identifiable {
    let id = UUID()
    let header: AnyView?
    let content: AnyView
    let footer: AnyView?
}

// 收集Section的偏好键
struct SectionPreferenceKey: PreferenceKey {
    static var defaultValue: [SectionItem] = []
    static func reduce(value: inout [SectionItem], nextValue: () -> [SectionItem]) {
        // 自动合并所有子视图上报的Section
        value.append(contentsOf: nextValue())
    }
}

2. 扩展Section和普通View,添加上报逻辑

给原生 Section 添加扩展,让它自动上报自身的 header、content、footer 信息;同时给所有 View 添加扩展,非 Section 的普通视图会自动被包装成无 header/footer 的 Section 上报:

extension Section {
    func collectSection() -> some View {
        let item = SectionItem(
            header: AnyView(header),
            content: AnyView(content),
            footer: AnyView(footer)
        )
        return self.preference(key: SectionPreferenceKey.self, value: [item])
    }
}

extension View {
    func autoCollectSection() -> some View {
        // 优先匹配Section的collectSection方法,普通View则包装为Section上报
        if let sec = self as? Section<EmptyView, some View, some View> {
            return AnyView(sec.collectSection())
        } else {
            let item = SectionItem(
                header: nil,
                content: AnyView(self),
                footer: nil
            )
            return AnyView(self.preference(key: SectionPreferenceKey.self, value: [item]))
        }
    }
}

3. 实现MyList的Section收集逻辑

在 MyList 中通过偏好值监听获取全量平铺的 Section 数组,再基于该数组实现自定义列表布局:

struct MyList<Content: View>: View {
    private let content: Content
    @State private var sections: [SectionItem] = []
    
    init(@ViewBuilder content: @escaping () -> Content) {
        // 递归给所有子视图添加自动收集修饰符,支持TupleView、ForEach、Group等容器
        self.content = content().transformIfNeeded { $0.autoCollectSection() }
    }
    
    var body: some View {
        // 隐藏原视图仅用来收集偏好值,也可以直接渲染后叠加布局
        content
            .hidden()
            .onPreferenceChange(SectionPreferenceKey.self) { newSections in
                sections = newSections
            }
            .overlay(
                // 以下为自定义列表布局示例,可根据需求修改
                ScrollView {
                    VStack(spacing: 8) {
                        ForEach(sections) { section in
                            if let header = section.header {
                                header
                                    .font(.headline)
                                    .padding(.horizontal)
                            }
                            section.content
                                .padding(.horizontal)
                            if let footer = section.footer {
                                footer
                                    .font(.caption)
                                    .foregroundColor(.secondary)
                                    .padding(.horizontal)
                            }
                            Divider()
                        }
                    }
                    .padding(.vertical)
                }
            )
    }
}

// 辅助扩展:递归处理所有容器类型的子视图
extension View {
    func transformIfNeeded(_ transform: (AnyView) -> some View) -> some View {
        let mirror = Mirror(reflecting: self)
        // 处理TupleView、ForEach等多子元素容器
        if let children = mirror.descendant("children") as? [Any] {
            return AnyView(VStack {
                ForEach(children.indices, id: \.self) { idx in
                    AnyView(children[idx]).transformIfNeeded(transform)
                }
            })
        } else {
            return transform(AnyView(self))
        }
    }
}

注意事项

  • 该方案基于SwiftUI官方 PreferenceKey 实现,兼容性强,支持任意层级嵌套的 ForEach、TupleView、Group 等容器类型
  • 普通非Section视图会自动被包装为无头部尾部的Section,无需额外处理
  • 如果需要适配更多自定义容器,只需在transformIfNeeded方法中添加对应容器的子视图遍历逻辑即可

内容的提问来源于stack exchange,提问作者Lorenzo Fiamingo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.19 14:24:03