登陆

SwiftUI - 3D卡片

admin 2022-11-25 6人围观 ,发现0个评论

3D卡片

Simulator Screen Recording - iPhone 14 Pro - 2022-.gif

想要实现这个效果,需要处理以下几点:

  1. 手势处理

  2. 手势转换为3D角度

  3. 角度影响视图

1. 手势处理

这种3D卡片本身没有什么技术难度,也就是多层的图像堆积,想要实现这种跟随手势的3D效果,需要获取手势移动的距离,同时需要注意在手势终止之后需要将视图恢复到初始状态。

  1. 手势监听 手势监听只需要将手势的移动距离保存起来,在SwiftUI中使用一个@State变量保存即可

  2. 手势终止后将视图恢复到初始状态 手势终止后将移动距离变量恢复为0即可

具体代码如下:

.gesture(     DragGesture()         .onChanged { value in             offset = value.translation         }         .onEnded { _ in             withAnimation(.interactiveSpring(response: 0.6, dampingFraction: 0.32, blendDuration: 0.32)) {                 offset = .zero             }         } ) 复制代码

2. 将手势转换为3D角度

当手指在页面上向下拖动时,卡片对应的向下方旋转(对应旋转轴是x);当手指在视图上左右拖动时,卡片对应左右旋转(对应旋转轴是y);

至于拖动的距离与旋转角度的关系就随你定义。这里取 10/屏幕长宽。

func offset2Angle(_ isVertical: Bool = false) -> Angle {   let progress = (isVertical ? -offset.height : offset.width) / (isVertical ? screenSize.height : screenSize.width)   return Angle(degress: progress * 10)   } 复制代码

这里因为需要计算两个维度的角度,然后通过组合来实现立体的旋转,所以增加一个条件以区别两个角度。

这里还有一个辅助函数用于获取屏幕尺寸,就不展开说了。

var screenSize: CGSize = {     guard let window = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return .zero }     return window.screen.bounds.size }() 复制代码

3. 角度影响视图

最后就是最简单的一个步骤:给视图添加3D旋转

.rotation3DEffect(offset2Angle(true), axis: (x: 1, y: 0, z: 0)) // 旋转轴为x .rotation3DEffect(offset2Angle(), axis: (x: 0, y: 1, z: 0)) // 旋转轴为y 复制代码

为了使得3D效果更明显,还可以在前景视图上添加offset位移,让卡片旋转时带动前景移动

.offset(x: offset2Angle().degress * 5, y: -offset2Angle(true).degress * 5) 复制代码

总结

这种3D效果看起来很厉害,实际上运用到的知识却很简单,关键在于空间理解能力。

完整代码

struct ContentView: View {     @State var offset: CGSize = .zero     var body: some View {         GeometryReader { proxy in             let size = proxy.size             let imageSize = size.width * 0.7                          VStack {                 Image("img")                     .resizable()                     .aspectRatio(contentMode: .fit)                     .frame(width: imageSize)                     .zIndex(1)                     .offset(x: offset2Angle().degrees * 5, y: -offset2Angle(true).degrees * 5)             }             .padding(.top, 65)             .padding(.horizontal, 15)             .padding(.bottom, 30)             .frame(width: imageSize)             .background {                 ZStack {                     Rectangle()                         .fill(.blue)                 }                 .clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous))             }             .rotation3DEffect(offset2Angle(true), axis: (x: 1, y: 0, z: 0))             .rotation3DEffect(offset2Angle(), axis: (x: 0, y: 1, z: 0))             .scaleEffect(0.9)             .frame(maxWidth: .infinity, maxHeight: .infinity)             .contentShape(Rectangle())             .gesture(                 DragGesture()                     .onChanged { value in                         offset = value.translation                     }                     .onEnded { _ in                         withAnimation(.interactiveSpring(response: 0.6, dampingFraction: 0.32, blendDuration: 0.32)) {                             offset = .zero                         }                     }             )         }     }          func offset2Angle(_ isVertical: Bool = false) -> Angle {         let progress = (isVertical ? -offset.height : offset.width) / (isVertical ? screenSize.height : screenSize.width)         return .init(degrees: progress * 10)     }          var screenSize: CGSize = {         guard let window = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return .zero }         return window.screen.bounds.size     }() }  复制代码
请发表您的评论
请关注微信公众号
微信二维码
不容错过
Powered By Z-BlogPHP