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

如何在SwiftUI的ListView中实现自定义圆角滑动操作?

实现iOS 15天气App风格圆角滑动操作方案

方案选型建议

优先选择纯SwiftUI自定义手势+布局实现,相比UIViewRepresentable封装UITableView有这些优势:

  • 不需要处理UIKit和SwiftUI的状态同步问题,维护成本更低
  • 样式自定义灵活度更高,可自由调整圆角、间距、动画曲线
  • 代码量更少,适配SwiftUI后续版本迭代更方便
    仅当你需要兼容iOS 14及更低版本系统时,再考虑使用UITableView桥接方案。

纯SwiftUI实现步骤

  1. 首先禁用List的默认分割线、列表行边距,自己实现列表行的圆角样式
  2. 移除原生swipeActions修饰符,给列表行添加拖动手势,追踪横向偏移量
  3. 根据偏移量展示操作按钮,给按钮添加统一圆角样式
  4. 增加滑动锚点和回弹动画,模拟原生滑动操作的交互手感

示例代码

struct SwipeActionRow<Content: View>: View {
    let content: Content
    let actionButtons: [SwipeButton]
    @State private var offsetX: CGFloat = 0
    @State private var isExpanded = false
    
    init(@ViewBuilder content: () -> Content, actionButtons: [SwipeButton]) {
        self.content = content()
        self.actionButtons = actionButtons
    }
    
    var body: some View {
        ZStack(alignment: .trailing) {
            // 滑动操作按钮层
            HStack(spacing: 12) {
                ForEach(actionButtons) { button in
                    Button {
                        button.action()
                        // 点击后自动收回
                        withAnimation { offsetX = 0 }
                    } label: {
                        Text(button.title)
                            .foregroundColor(.white)
                            .padding(.horizontal, 16)
                    }
                    .frame(height: .infinity)
                    .background(button.tintColor)
                    .cornerRadius(12) // 这里调整圆角大小
                }
            }
            .padding(.horizontal, 16)
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)
            
            // 列表行内容层
            content
                .padding(.horizontal, 16)
                .padding(.vertical, 12)
                .background(.white)
                .cornerRadius(12)
                .offset(x: offsetX)
                .gesture(
                    DragGesture(minimumDistance: 10)
                        .onChanged { value in
                            // 只响应左滑
                            guard value.translation.width < 0 else { return }
                            let dragOffset = value.translation.width
                            // 最大滑动距离为按钮总宽度+边距
                            let maxOffset = -CGFloat(actionButtons.count) * 80 - 24
                            offsetX = isExpanded ? maxOffset + dragOffset : min(0, dragOffset)
                        }
                        .onEnded { value in
                            withAnimation(.easeOut(duration: 0.2)) {
                                let threshold: CGFloat = -60
                                if value.translation.width < threshold || isExpanded {
                                    // 展开滑动操作
                                    let maxOffset = -CGFloat(actionButtons.count) * 80 - 24
                                    offsetX = maxOffset
                                    isExpanded = true
                                } else {
                                    // 收回
                                    offsetX = 0
                                    isExpanded = false
                                }
                            }
                        }
                )
                // 点击行内容自动收回
                .onTapGesture {
                    withAnimation {
                        offsetX = 0
                        isExpanded = false
                    }
                }
        }
        .listRowSeparator(.hidden)
        .listRowBackground(Color.clear)
        .listRowInsets(EdgeInsets(top: 6, leading: 16, bottom: 6, trailing: 16))
    }
}

// 滑动按钮模型
struct SwipeButton: Identifiable {
    let id = UUID()
    let title: String
    let tintColor: Color
    let action: () -> Void
}

// 调用示例
struct ContentView: View {
    var body: some View {
        List {
            SwipeActionRow(
                content: { Text("Pepperoni pizza") },
                actionButtons: [
                    SwipeButton(title: "Order", tintColor: .green, action: {
                        print("Awesome!")
                    })
                ]
            )
            
            SwipeActionRow(
                content: { Text("Pepperoni with pineapple") },
                actionButtons: [
                    SwipeButton(title: "Burn", tintColor: .red, action: {
                        print("Right on!")
                    })
                ]
            )
        }
        .listStyle(.plain)
        .background(.gray.opacity(0.1))
    }
}

注意事项

  • 可根据需求调整圆角数值、按钮间距、滑动阈值,匹配你想要的效果
  • 如果需要支持左右两侧都添加滑动操作,只需要在ZStack的leading侧也添加一组按钮,同时处理右滑的偏移逻辑即可
  • 该方案兼容iOS 15及以上版本,如果需要更低版本兼容,可将listRowSeparator等修饰符替换为对应版本的实现

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 23:24:04