我正在构建一个应用程序,其中有关一系列参数的摘要信息和历史细节显示在一个列表中。
用户可以通过单击“公开”按钮查看历史指标,DisclosureGroup可以非常灵活地处理这一问题,但我想添加一个按钮,该按钮可以切换控件的显示,使用户能够输入新数据,而无需使用模式显示或从摘要屏幕导航。
尽管您可以嵌套DisclosureGroups,但这并不是很好的UX (用户需要公开两次才能访问他们想要的信息),所以我想通过有条件地向层次结构中添加视图来复制DisclosureGroup动画行为。
外部列表或表单,这是很容易做到的-确保操作切换您的标志显示是包装在withAnimation和SwiftUI照顾其余。不幸的是,这些容器视图的布局方式导致了一些相当奇怪的行为,当一个新视图被添加到一个单元格中时,这种行为会导致预先存在的视图(不包括文本视图)“弹跳”。这种情况不会发生在DisclosureGroup (不管您是使用文本还是其他视图子类型)中,它在添加到列表或窗体时完全不受影响。
在模拟器中减慢动画速度可以明显地显示父视图(即单元格)在添加新子视图之前更改其大小,但这会导致现有视图的位置也发生变化(试图将其锚定在单元格视图的顶部,方法是在包围的HStack上使用对齐修饰符或直接在视图的框架上使用对齐修饰符,这并不能阻止这种情况发生)。
此外,当视图从单元格的层次结构中移除时,该单元格在视图被移除之前收缩,使离开的视图在相邻单元格上盘旋一段时间,这看起来很混乱。不用说,这在DisclosureGroup中不会发生,所以我认为我可以合理地假设我想要的是完全可能的。
我尝试(但失败)通过向每个视图抛出对齐、HStack和VStack容器以及.matchedGeometryEffect修饰符来解决这个问题(后者似乎是最有希望的,因为这似乎最终是视图转换的协调不良)。这要么不起作用,要么导致了一些非常奇怪的行为。
放慢动画速度很明显,DisclosureGroup可能正在使用ZStack和/或自定义转换,这种转换以渐进的方式抵消子视图。然而,我试图复制这一点的尝试有been...interesting。
一张图片值一千字(所以一段视频值一万?)请参阅所附的行为图解及相关守则。

使用文本视图的:
struct DisclosureWithTextView: View {
@Namespace private var nspace
@State private var willDisplayControl = false
var body: some View {
DisclosureGroup(
content: {
ForEach(0..<2) { index in
Text("Item")
.padding(6)
.background {
Rectangle()
.foregroundColor(.yellow)
}
}
},
label: {
VStack {
HStack {
Button(action: {
willDisplayControl.toggle()
})
{
Image(systemName: "plus.circle")
}
.buttonStyle(PlainButtonStyle())
Text("SUMMARY")
.padding(8)
.background {
Rectangle()
.foregroundColor(.red)
}
}
if willDisplayControl {
Text("CONTROL")
.padding(8)
.background {
Rectangle()
.foregroundColor(.green)
}
}
}
})
}}
使用矩形视图的:
struct DisclosureWithGraphics: View {
@Namespace private var nspace
@State private var willDisplayControl = false
var body: some View {
DisclosureGroup(
content: {
ForEach(0..<2) { index in
Rectangle()
.frame(width:80, height: 30)
.foregroundColor(.yellow)
}
},
label: {
VStack {
HStack {
Button(action: {
withAnimation { willDisplayControl.toggle() }
})
{
Image(systemName: "plus.circle")
}
.buttonStyle(PlainButtonStyle())
Rectangle()
.frame(width: 110, height: 40)
.foregroundColor(.red)
}
if willDisplayControl {
Rectangle()
.frame(width: 100, height: 40)
.foregroundColor(.green)
}
}
})
}}
发布于 2022-01-10 20:31:29
具有讽刺意味的是,我意识到阿斯佩里把我提到了他的解决方案,因为我在18世纪前提出了一个几乎相同的问题!现在感觉有点傻,但这解释了我以前遇到过这个问题的模糊感觉!
然而,我发现有一个重要的区别。如果在列表或表单中使用“香草”堆栈容器视图,那么调整对齐是您需要做的所有工作,以实现一个视图被另一个视图披露的平滑动画。动画容器视图的框架非常好,但这是不必要的。但是,当您在DisclosureGroup中尝试相同的操作时,形成标签的视图就会开始跳来跳去,而不管您是否动画化了对齐向导、框架等,并且您必须显式地动画父视图的高度。
因此,在一个简单的列表或表单单元格中滑动视图很容易(使用AlignmentGuide),但是当您在一个更专业的视图容器中尝试相同的事情时,您需要为单元格高度添加AnimatableValue。如果我猜的话,我怀疑这是因为与苹果自己的信息披露实现有一些冲突。
所以,感谢阿斯佩里提醒我该怎么做:-)
实现以下效果的两种方法的代码示例(我更喜欢对齐指南动画的外观)
调整待披露视图的框架:
struct DisclosureWithFrameAnimationReveal: View {
@Namespace private var nspace
@State private var willDisplayControl = false
var body: some View {
DisclosureGroup(
content: {
ForEach(0..<2) { index in
Text("Item")
.padding(6)
.background {
Rectangle()
.foregroundColor(.yellow)
}
}
},
label: {
VStack {
HStack {
Button(action: {
withAnimation(.easeInOut) { willDisplayControl.toggle() }
})
{
Image(systemName: "plus.circle")
}
.buttonStyle(PlainButtonStyle())
Color(.red)
.frame(width: 100, height: 40)
Spacer()
}
.padding(.top, 4)
.background()
HStack {
Color(.blue)
.frame(width: 100, height: willDisplayControl ? 40 : 0)
Spacer()
}
.opacity(willDisplayControl ? 1 : 0)
}
.modifier(AnimatableCellHeight(height: willDisplayControl ? 88 : 44))
}
)
}
}对齐指南动画:
struct DisclosureWithAlignmentReveal: View {
@Namespace private var nspace
@State private var willDisplayControl = false
var body: some View {
DisclosureGroup(
content: {
ForEach(0..<2) { index in
Text("Item")
.padding(6)
.background {
Rectangle()
.foregroundColor(.yellow)
}
}
},
label: {
ZStack(alignment: .top) {
HStack {
Button(action: {
withAnimation(.easeInOut) { willDisplayControl.toggle() }
})
{
Image(systemName: "plus.circle")
}
.buttonStyle(PlainButtonStyle())
Color(.red)
.frame(width: 100, height: 40)
Spacer()
}
.zIndex(1)
.padding(.top, 4)
.background()
HStack {
Color(.blue)
.frame(width: 100, height: 40)
Spacer()
}
.alignmentGuide(.top, computeValue: { d in d[.top] - (willDisplayControl ? 46 : 0) })
.opacity(willDisplayControl ? 1 : 0)
.zIndex(0)
}
.modifier(AnimatableCellHeight(height: willDisplayControl ? 88 : 44))
}
)
}
},最后是AnimatableValue实现:
struct AnimatableCellHeight: AnimatableModifier {
var height: CGFloat = 0
var animatableData: CGFloat {
get { height }
set { height = newValue }
}
func body(content: Content) -> some View {
content.frame(height: height)
}
}

https://stackoverflow.com/questions/70623529
复制相似问题