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

如何为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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 16:06:02