GeometryEffect
GeometryEffect 是一个遵循 Animatable 和 ViewModifier 协议的协议,它可以对视图的坐标进行变化,并且不会影响到该视图的父视图和子视图的布局。
func effectValue(size: CGSize) -> ProjectionTransform


它返回的是变化效果的当前值,比如示例所示的,我们需要实现一个翻转效果:
struct FlipEffect: GeometryEffect {
/// 是否已翻转
@Binding var flipped: Bool
/// 翻转角度
var angle: CGFloat
var animatableData: CGFloat {
get { angle }
set { angle = newValue }
}
func effectValue(size: CGSize) -> ProjectionTransform {
DispatchQueue.main.async {
// 获取当前变化的状态
// 通过绑定值将翻转状态回传给视图
flipped = angle >= .pi * 0.5
}
// 返回的视图变化效果,这里是3D翻转效果
var transform3d = CATransform3DMakeRotation(angle, 0, 1, 0)
transform3d = CATransform3DTranslate(transform3d, -size.width * 0.5, 0, 0)
return ProjectionTransform(transform3d)
}
}
然后我们通过 modifier 调用:
@State private var angle: CGFloat = 0
@State private var flipped = false
private let circleSize: CGFloat = 80
var randomString: String {
Bool.random() ? "❤️" : "😭"
}
VStack {
HStack(spacing: 20) {
circle
circle
circle
}
HStack(spacing: 20) {
Button("翻转") {
withAnimation(.spring(response: 0.5,
dampingFraction: 0.5,
blendDuration: 0.5) {
angle += flipped ? -.pi : .pi
}
}
.padding()
Button("慢放") {
withAnimation(.easeInOut(duration: 3)) {
angle += flipped ? -.pi : .pi
}
}
.padding()
}
}
var circle: some View {
ZStack {
Circle()
.fill(Color.blue)
.frame(width: circleSize, height: circleSize)
Text(flipped ? randomString : "?")
.foregroundColor(.white)
.font(.largeTitle)
}
.modifier(FlipEffect(flipped: $flipped, angle: angle))
.offset(x: circleSize * 0.5, y: 0)
}
另外,GeometryEffect 还有一个可选的方法:
func ignoredByLayout() -> _IgnoredByLayoutEffect<Self>
一般来讲,当我们对视图做变换的时候,它的布局信息(坐标、尺寸等)会跟着变化,如果我们不希望如此,就可以使用这个方法,它不会改变视图的布局信息,但是动画效果并不受影响。
matchedGeometryEffect
matchedGeometryEffect 是 iOS 14 新增的 modifier,使用简单,功能强大。我们直接结合示例和代码来看:
@State private var changed = false
@Namespace private var ns
VStack {
if changed {
VStack {
Image("icon")
.cornerRadius(10)
.matchedGeometryEffect(id: "icon", in: ns)
HStack {
Text("如果你喜欢 Eul,请评价/分享")
.font(.subheadline)
Spacer()
Link(destination: URL(string:
"https://apps.apple.com/cn/app/eul/id154199
Label("", systemImage: "square.and.arrow.up
}
}
.matchedGeometryEffect(id: "content", in: ns)
}
} else {
HStack {
Image("icon")
.cornerRadius(10)
.matchedGeometryEffect(id: "icon", in: ns)
HStack {
Text("如果喜欢 Eul,请评价/分享")
.font(.subheadline)
Spacer()
Link(destination: URL(string:
"https://apps.apple.com/cn/app/eul/id154199
Label("", systemImage: "square.and.arrow.up
}
}
.matchedGeometryEffect(id: "content", in: ns)
}
}
Button("点我") {
withAnimation(.spring()) {
changed.toggle()
}
}
.padding()
}
通过代码,我们可以看到,动画前后,视图的内容并没有变化,只是布局发生了变化,针对这样的场景,我们可以通过 matchedGeometryEffect 给它添加平滑的动画效果。
matchedGeometryEffect 需要标识符 id 来同步视图的布局,id 是定义在命名空间 namespace 中的,通常我们只需要指定这两个参数就足够了。
matchedGeometryEffect 方法还提供以下参数,作了解:
- properties : 从 source view 复制的属性,默认是 frame,还可使用 position、size
- anchor : 锚点,默认是 center
- isSource : 是否作为源视图,默认是 true