SwiftUI List中自定义DisclosureGroup展开动画失效如何修复?
修复List中自定义DisclosureGroup的展开动画失效问题
问题根源
在List中使用自定义DisclosureGroup时,动画效果异常的核心原因有三点:
- List的单元格存在复用机制,组件内部的
@State状态会因复用被意外重置或共享,导致动画逻辑混乱 - 直接通过条件渲染显示/隐藏内容,SwiftUI无法捕捉到完整的布局变化,无法生成平滑的过渡动画
- 系统默认
DisclosureGroup与List的布局系统深度整合,自定义组件缺少对应的适配逻辑
修复方案
以下是调整后的完整代码,解决动画失效问题并对齐系统默认组件的交互体验:
1. 重构自定义DisclosureGroup组件
将状态管理外置,添加过渡动画与视觉适配:
struct CustomDisclosureGroup<Label: View, Content: View>: View { @Binding var isExpanded: Bool private let label: Label private let content: Content init(isExpanded: Binding<Bool>, @ViewBuilder label: () -> Label, @ViewBuilder content: () -> Content) { self._isExpanded = isExpanded self.label = label() self.content = content() } var body: some View { VStack(spacing: 0) { Button { withAnimation(.default) { isExpanded.toggle() } } label: { HStack { label Spacer() // 模拟系统箭头的旋转动画 Image(systemName: isExpanded ? "chevron.down" : "chevron.right") .rotationEffect(.degrees(isExpanded ? 0 : -90)) .animation(.default, value: isExpanded) } .contentShape(Rectangle()) // 让整个按钮区域可点击 } // 添加过渡动画,确保平滑展开/收起 content .frame(maxWidth: .infinity, alignment: .leading) .transition(.disclosureTransition()) // iOS16+ 系统原生过渡效果 .opacity(isExpanded ? 1 : 0) .scaleEffect(y: isExpanded ? 1 : 0.95, anchor: .top) } .listRowInsets(EdgeInsets()) // 去掉默认行内边距,对齐系统样式 } }
2. 父视图管理展开状态
使用字典存储每个列表项的展开状态,避免List复用导致的状态混乱:
struct ContentView: View { @State var items: [Int] = [0, 1, 2, 3] @State private var expandedStates: [Int: Bool] = [:] var body: some View { List { ForEach(items, id: \.self) { item in CustomDisclosureGroup( isExpanded: Binding( get: { expandedStates[item, default: false] }, set: { expandedStates[item] = $0 } ), label: { Text(item.formatted()) }, content: { Text("Test text") Text("Test text") Text("Test text") } ) } } } }
关键修改点说明
- 状态外置:将展开状态从组件内部
@State改为外部绑定,由父视图通过字典独立管理每个列表项的状态,彻底避免List复用带来的状态问题 - 过渡动画:使用
.disclosureTransition()(iOS16+)或手动组合opacity、scaleEffect过渡,让内容的显示/隐藏有自然的动画效果 - 视觉适配:添加箭头图标并实现旋转动画,模拟系统
DisclosureGroup的交互反馈;设置.listRowInsets(EdgeInsets())去掉默认边距,对齐系统样式 - 动画一致性:明确指定
.default动画类型,确保点击触发的动画逻辑稳定
内容的提问来源于stack exchange,提问作者Nicolas Gimelli
相关产品推荐
相关产品推荐

