通过 GeometryEffect 协议实现自定义的 Modifier,通过实现并拦截协议中的 animatableData 变量(绑定为翻转角度),根据翻转角度绑定卡片翻转状态 flipped,以此来实现显示不同内容的效果:
struct FlipEffect: GeometryEffect {
// SwiftUI 的动画就是通过时间函数操作 animatableData 变量,这里通过 getter setter 传递给给 angle 属性
var animatableData: Double {
get { angle }
set { angle = newValue }
}
@Binding var flipped: Bool // 绑定卡片当前的状态
var angle: Double // 翻转角度变量
func effectValue(size: CGSize) -> ProjectionTransform {
DispatchQueue.main.async {
self.flipped = self.angle >= 90 && self.angle < 270 // 根据当前的角度改变 flipped 的值
}
let a = CGFloat(Angle.degrees(angle).radians)
var transform3d = CATransform3DIdentity // 对角单位矩阵
transform3d = CATransform3DRotate(transform3d, a, 0, 1, 0) // 以 (0, 1, 0) 为轴, (0, 0, 0) 为锚点旋转 a 度
transform3d = CATransform3DTranslate(transform3d, -size.width/2.0, -size.height/2.0, 0) // 左移 1/2 width
let affineTransform = ProjectionTransform(CGAffineTransform(translationX: size.width/2.0, y: size.height / 2.0)) // 定义整体的右移 1/2 width 的效果,这样等效于中轴线旋转
return ProjectionTransform(transform3d).concatenating(affineTransform) // 组合应用上面的效果
}
}
使用 Demo:
import SwiftUI
struct FlipEffectView: View {
@State var flipped: Bool = false
@State var trigger: Bool = false
var body: some View {
VStack {
Text(flipped ? "反面" : "正面")
.rotation3DEffect(.degrees(flipped ? 180 : 0), axis: (x: 0, y: 1, z: 0))
.padding(24)
}
.background()
.cornerRadius(16)
.shadow(color: Color(.displayP3, red: 0, green: 0, blue: 0, opacity: 0.2), radius: 16, x: 0, y: 16)
.modifier(FlipEffect(flipped: $flipped, angle: trigger ? 180: 0))
.onTapGesture {
withAnimation(.spring(response: 0.5, dampingFraction: 0.5, blendDuration: 0.5)) {
self.trigger.toggle()
}
}
}
}
struct FlipEffectView_Previews: PreviewProvider {
static var previews: some View {
FlipEffectView()
}
}
效果: