自定义DisclosureGroup中ForEach内容的条件显隐动画实现求助
自定义DisclosureGroup中ForEach内容的条件显隐动画实现求助
嘿,我完全懂你现在的困扰——自己折腾了个自定义DisclosureGroup,视觉样式和交互逻辑都调得很满意,结果卡在内容展开收起的动画上,明明功能正常,就是没有丝滑的过渡效果,对吧?我之前做类似自定义组件的时候也踩过这个坑,给你分享几个亲测有效的解决方案,你可以照着试试:
方案一:用Transition配合状态动画
这个方案是给内容区域指定显隐时的过渡效果,同时在触发状态切换时包裹动画上下文,让SwiftUI自动处理动画流程。你可以把原代码里的内容部分做如下修改:
struct SectionView: View { let title: String let data: [String] @State private var isExpanded: Bool = true var body: some View { VStack(spacing: 12) { // 加个间距让布局更舒服 HStack { Text(title) .font(.title2) .fontWeight(.semibold) Text("\(data.count)") .font(.body) .fontWeight(.light) .foregroundStyle(.secondary) Spacer() Button { // 用withAnimation包裹状态切换,让所有依赖该状态的视图触发动画 withAnimation(.easeInOut(duration: 0.3)) { isExpanded.toggle() } } label: { Image(systemName: isExpanded ? "chevron.down" : "chevron.right") .foregroundColor(.secondary) // 给箭头也加个旋转动画,和内容动画呼应 .rotationEffect(.degrees(isExpanded ? 0 : -90)) } } // 内容区域:用if控制显隐,同时指定过渡动画 if isExpanded { VStack(alignment: .leading, spacing: 8) { ForEach(data, id: \.self) { item in Text(item) .font(.body) .padding(.vertical, 4) .padding(.horizontal, 8) } } // 自定义过渡效果:滑入+淡入,消失时反向执行 .transition(.opacity.combined(with: .slide)) // 绑定状态变化,确保动画精准触发 .animation(.easeInOut(duration: 0.3), value: isExpanded) } } .padding(.horizontal) } }
方案二:高度渐变动画(更贴近原生DisclosureGroup效果)
原生DisclosureGroup的展开收起其实是高度从0到自动高度的渐变动画,你可以用frame的height属性来模拟这个效果,过渡会更自然:
struct SectionView: View { let title: String let data: [String] @State private var isExpanded: Bool = true var body: some View { VStack(spacing: 12) { HStack { // 这里的HStack内容和方案一完全一致 Text(title) .font(.title2) .fontWeight(.semibold) Text("\(data.count)") .font(.body) .fontWeight(.light) .foregroundStyle(.secondary) Spacer() Button { withAnimation(.easeInOut(duration: 0.3)) { isExpanded.toggle() } } label: { Image(systemName: isExpanded ? "chevron.down" : "chevron.right") .foregroundColor(.secondary) .rotationEffect(.degrees(isExpanded ? 0 : -90)) } } // 内容区域:用frame高度控制显隐+裁剪溢出 VStack(alignment: .leading, spacing: 8) { ForEach(data, id: \.self) { item in Text(item) .font(.body) .padding(.vertical, 4) .padding(.horizontal, 8) } } // 隐藏时高度设为0,展开时自动适应内容高度 .frame(height: isExpanded ? .infinity : 0, alignment: .top) .clipped() // 裁剪高度为0时的内容溢出,避免出现残影 .animation(.easeInOut(duration: 0.3), value: isExpanded) } .padding(.horizontal) } }
小提醒
- 不要把动画修饰符加在根VStack上,不然整个组件(包括标题栏)都会跟着动,显得很奇怪,精准加在需要动画的内容区域就好;
- 如果你的内容是动态加载的,比如数据会变化,记得把ForEach的id设置成稳定的唯一标识(如果用
\.self的话要确保data里的字符串不会重复); - 可以调整
.easeInOut(duration: 0.3)里的时长参数,找到最贴合你UI风格的动画速度。
你可以根据自己想要的视觉效果选其中一种方案,或者把两种结合起来调整,应该就能解决动画的问题啦!
备注:内容来源于stack exchange,提问作者Johnathon Sullinger
相关产品推荐
相关产品推荐

