用途:面向 SwiftUI 布局与容器,介于“栈与占位”和“十万行仍顺滑滚动”之间,覆盖 Apple 的 Layout 协议、容器组合与 lazy stack 规则。视图身份与数据流问题转至 swiftui/data-flow。
主要能力与用法要点: 自定义 Layout 协议(而非 GeometryReader):当需要测量子视图并把测量反馈回布局时使用。典型场景为等宽按钮。sizeThatFits 用 .unspecified 读取每个子视图理想尺寸(subviews.map { $0.sizeThatFits(.unspecified) }),处理空子视图,nil 尺寸用 replacingUnspecifiedDimensions()。placeSubviews 不要假设原点为 (0,0),使用 bounds.minX/midX,用 place(at:anchor:proposal:) 并允许 proposal 与理想尺寸不同。尊重间距偏好 subviews[i].spacing.distance(to:along:),冲突取较大值。用 LayoutValueKey 传每子视图数据。缓存需先经 Instruments 证实开销。用 AnyLayout 切换布局可保留身份。替代方案用 ViewThatFits,不把回退写进布局。
Grid 选择:静态二维且跨行对齐用 Grid/GridRow(gridCellColumns 跨列、gridColumnAlignment 按列);可滚动大内容用 LazyVGrid/LazyHGrid;“首个能放下的方案”用 ViewThatFits。
自定义容器:用尾部 @ViewBuilder var content: Content 让调用方可混用静态视图、ForEach 与条件。用 ForEach(subviews: content) 迭代已解析子视图,需整集合用 Group(subviews: content)。区分声明与解析:一个声明的 ForEach 解析为 N 个子视图,EmptyView 为零,按声明计数是错误。Sections 需显式 opt-in:ForEach(sections: content),读取 section.header 与 section.content,渲染插槽前检查 header.isEmpty。用 ContainerValues 做逐子定制,container value 只作用于直接容器,设置在 Section 上会样式化整个 Section。
Lazy stack 与滚动性能规则:每个 ForEach 元素始终保持一个子视图;行内放 if(0 或 1 个视图)会迫使保持离屏视图与 @State;在数据层过滤(@Query 谓词),把鉴权类条件放到栈外。不要在 lazy stack 中用绝对滚动偏移判断(onScrollGeometryChange 看到的是估算值),用 onScrollTargetVisibilityChange(threshold: 0.8)。在 init 而非 onAppear 中初始化(_model = State(initialValue:)),异步加载在 init/task 启动;别把重要状态放在行 @State,上提为 @State 与 @Binding;scrollTransition 变换须留在原帧内(缩放可,旋转出帧会导致提前消失);不要用 onGeometryChange 高度反馈驱动布局。可在 LazyVStack 内嵌套 LazyHStack,但需固定子高度。pinnedViews: [.sectionHeaders] 固定头部;无限滚动在 ForEach 后接尾部 ProgressView().onAppear { fetchNextPage() }。
滚动 API 概览:吸附/分页用 scrollTargetLayout() + scrollTargetBehavior(.viewAligned/.paging);位置跟踪控制用 scrollPosition 绑定与 scrollTo(id:),只要 ID 对应稳定的单子视图元素,未加载目标也可工作;滚动关联效果用 scrollTransition 与 visualEffect;反应监听用 onScrollGeometryChange(在 lazy 估算之外适用)与 onScrollVisibilityChange。
限制与说明:具体价格、平台支持范围、安装方式等资料暂未提供。输出格式建议为“现象 | 违反规则 | 修复”,处理 lazy stack 问题时先查“每元素一子视图”规则。参考资料包括 WWDC22 10056、WWDC24 10146、WWDC26 321。