如何为VStack添加自定义ViewModifier实现Form内嵌List样式
实现方案
SwiftUI 原生 ViewModifier 无法直接读取宿主 VStack 的子视图集合,我们可以通过自定义布局容器配合修饰器的方式实现需求,完全还原 Form 内嵌 List 的样式,最终调用方式和预期几乎一致。
自定义 Layout 方案需要 iOS 16 及以上版本支持,如果需要适配更低系统版本,可以使用下文的行修饰器方案。
完整实现代码
import SwiftUI // 和系统List/Form样式对齐的基础常量 private let listItemPadding: CGFloat = 16 private let listCornerRadius: CGFloat = 10 private let listSeparatorColor = Color(uiColor: .separator) private let listRowHeight: CGFloat = 44 private let listBackgroundColor = Color(uiColor: .systemBackground) // 自定义容器,替代原生VStack,自动处理子项分隔线 struct ListStyleVStack: Layout { let spacing: CGFloat = 0 func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { let totalHeight = subviews.reduce(0) { sum, subview in sum + subview.sizeThatFits(.init(width: proposal.width, height: listRowHeight)).height } + CGFloat(subviews.count - 1) * (1 / UIScreen.main.scale) return CGSize(width: proposal.width ?? .infinity, height: totalHeight) } func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { var yOffset = bounds.minY for (index, subview) in subviews.enumerated() { let rowHeight = subview.sizeThatFits(.init(width: bounds.width, height: listRowHeight)).height subview.place( at: CGPoint(x: bounds.minX + listItemPadding, y: yOffset), proposal: ProposedViewSize(width: bounds.width - listItemPadding * 2, height: rowHeight) ) yOffset += rowHeight // 非最后一个子项添加分隔线 if index != subviews.count - 1 { let separator = Rectangle() .fill(listSeparatorColor) .frame(height: 1 / UIScreen.main.scale) separator.place( at: CGPoint(x: bounds.minX + listItemPadding, y: yOffset), proposal: ProposedViewSize(width: bounds.width - listItemPadding, height: 1 / UIScreen.main.scale) ) yOffset += 1 / UIScreen.main.scale } } } } // 外层样式修饰器 struct ListStyleModifier: ViewModifier { func body(content: Content) -> some View { content .background(listBackgroundColor) .cornerRadius(listCornerRadius) .padding(.horizontal) .background(Color(uiColor: .systemGroupedBackground)) } } // 简化调用的扩展 extension View { func listStyle() -> some View { self.modifier(ListStyleModifier()) } }
调用方式
ListStyleVStack { Text("Group name") Text("Group hashtags") Text("Group description") } .listStyle()
低版本兼容方案(iOS 15及以下)
如果需要兼容旧系统,可以使用行修饰器手动标注最后一个元素,效果完全一致:
// 行样式修饰器 struct ListRowModifier: ViewModifier { let isLast: Bool func body(content: Content) -> some View { VStack(spacing: 0) { content .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, listItemPadding) .frame(height: listRowHeight) if !isLast { Divider() .padding(.leading, listItemPadding) } } } } // 调用方式 VStack(spacing: 0) { Text("Group name").modifier(ListRowModifier(isLast: false)) Text("Group hashtags").modifier(ListRowModifier(isLast: false)) Text("Group description").modifier(ListRowModifier(isLast: true)) } .modifier(ListStyleModifier())
两种方案都适配深色模式,支持自定义子项高度,可直接在修饰器中统一调整圆角、边距等全局样式。
内容的提问来源于stack exchange,提问作者Liviu
相关产品推荐
相关产品推荐

