SwiftUI中用GeometryEffect实现旋转视图平滑动画的问题
设备旋转时SwiftUI视图尺寸平滑动画修复方案
问题描述
尝试在设备旋转时为Rectangle视图的尺寸变化添加平滑动画,但当前代码出现异常:内部黄色半透明视图会先快速适配新尺寸,随后再次执行缩放动画,导致出现两次尺寸变化的效果。
问题根源
你的代码存在双重尺寸变更逻辑:
- 父视图(红色边框Rectangle)随设备旋转调整尺寸时,内部的
GeometryReader会自动让黄色Rectangle跟着布局变化调整尺寸; - 同时你又监听
innerGeo.size的变化,通过自定义ScaleEffect对黄色Rectangle做缩放变换,相当于在布局自动调整后又叠加了一次缩放动画,导致视觉上的双重变化。
解决方案
方案1:直接让布局变化带动画(最简单)
不需要自定义GeometryEffect,直接给父视图添加动画修饰符,让布局尺寸变化本身平滑过渡:
import SwiftUI @main struct QuestionApp: App { var body: some Scene { WindowGroup { PortraitLandscapeRotation() } } } struct PortraitLandscapeRotation: View { var body: some View { GeometryReader { geo in Rectangle() .aspectRatio(1, contentMode: .fit) .frame(maxWidth: geo.size.width * 0.5) .border(Color.red, width: 2) .overlay( Rectangle() .fill(Color.yellow.opacity(0.2)) ) .padding() } // 给整个布局添加动画,旋转时尺寸变化自动平滑过渡 .animation(.easeOut(duration: 2.0), value: UIScreen.main.bounds.size) } }
方案2:固定内部视图尺寸,仅通过缩放适配(保留自定义缩放逻辑)
如果需要保留内部视图的独立缩放控制,要固定内部视图的初始尺寸,避免它跟着父视图布局自动变化:
import SwiftUI @main struct QuestionApp: App { var body: some Scene { WindowGroup { PortraitLandscapeRotation() } } } struct PortraitLandscapeRotation: View { @State private var baseSize: CGSize = .zero @State private var scale: CGFloat = 1.0 var body: some View { GeometryReader { geo in let targetWidth = geo.size.width * 0.5 // 父视图正常响应布局 Rectangle() .aspectRatio(1, contentMode: .fit) .frame(maxWidth: targetWidth) .border(Color.red, width: 2) .overlay( // 内部视图固定初始尺寸,仅通过缩放适配 Rectangle() .fill(Color.yellow.opacity(0.2)) .frame(width: baseSize.width, height: baseSize.height) .scaleEffect(scale) ) .padding() .onAppear { // 记录初始基准尺寸 baseSize = CGSize(width: targetWidth, height: targetWidth) } .onChange(of: targetWidth) { newWidth in // 计算缩放比例并执行动画 withAnimation(.easeOut(duration: 2.0)) { scale = newWidth / baseSize.width } } } } }
关于MatchedGeometryEffect的疑问
MatchedGeometryEffect的核心作用是跨布局容器的视图身份匹配过渡(比如列表项点击后放大到详情页的转场),并不适合当前场景——你只是同一视图在同一个布局容器内的尺寸变化,用普通动画修饰符或上述方案更简洁高效,没必要用MatchedGeometryEffect。
内容的提问来源于stack exchange,提问作者Linda
相关产品推荐
相关产品推荐

