You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 20:15:17