在SwiftUI中,为了让我的视图宽度适配多种设置,我从GeometryReader中获取屏幕的宽度,并设置到对应的VStack中。
GeometryReader { geo in
let width = geo.frame(in: .global).width * 0.95
let height = geo.frame(in: .global).height
VStack {
// 省略内容
}
.frame(width: width,height:110)
}

从Gif动画中可以看到,主视图顶部的黑色区域在Sheet关闭时,黑色区域的宽度会直接放大,缺失了动画缩放的效果。当然Gif动画因为本身的帧数限制,可能看的并不是很清楚。
这一问题的原因主要在于当Sheet关闭后,VStack的width就需要根据 frame(in: .global) 计算出的宽度变宽,这这也就会导致在动画过程中,VStack的宽度瞬间变大。
解决方案:使用 .frame(in: .local) 或者更安全的 UIScreen.main.bounds.width:
GeometryReader { geo in
let width = geo.frame(in: .local).width * 0.95 // 改为 .local
let height = geo.frame(in: .local).height
VStack {
// 省略内容
}
.frame(width: width,height:110)
}

使用 .frame(in: .local)后,问题得到解决。
扩展知识
.frame(in: .local) 和 .frame(in: .global)的区别
GeometryProxy.frame(in:) 是 SwiftUI 中一个方法,用来获取当前视图在某个坐标空间(CoordinateSpace)中的位置和大小,返回的是一个 CGRect。
.local:当前视图坐标系自身,自身左上角是 (0,0),宽高是自己大小。
.global,整个屏幕,用于测量一个视图在屏幕上的绝对位置。
为什么 .global 会导致 sheet 关闭时宽度突变?
1、.global
当视图处于动画状态(例如正在从 .sheet 消失),SwiftUI 会把它从原布局结构中移出,暂时放在一个系统管理的动画容器里。
这时候 .global 坐标系就无法正确反映原来看到的视图大小,它可能会返回整个屏幕的宽度,甚至一个奇怪的大值。
所以当拿 .global.width 来设置 .frame(width:),就等于“用错了参考线”。
2、.local
.local 总是从视图自己的坐标系来出发,左上角是 (0,0),大小是视图当前真实的布局大小。
即使在动画中,只要 GeometryReader 本身还在,它的 .local 尺寸是稳定的。