如何在SwiftUI的ListView中实现自定义圆角滑动操作?
实现iOS 15天气App风格圆角滑动操作方案
方案选型建议
优先选择纯SwiftUI自定义手势+布局实现,相比UIViewRepresentable封装UITableView有这些优势:
- 不需要处理UIKit和SwiftUI的状态同步问题,维护成本更低
- 样式自定义灵活度更高,可自由调整圆角、间距、动画曲线
- 代码量更少,适配SwiftUI后续版本迭代更方便
仅当你需要兼容iOS 14及更低版本系统时,再考虑使用UITableView桥接方案。
纯SwiftUI实现步骤
- 首先禁用List的默认分割线、列表行边距,自己实现列表行的圆角样式
- 移除原生
swipeActions修饰符,给列表行添加拖动手势,追踪横向偏移量 - 根据偏移量展示操作按钮,给按钮添加统一圆角样式
- 增加滑动锚点和回弹动画,模拟原生滑动操作的交互手感
示例代码
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
相关产品推荐
相关产品推荐

