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

如何让SwiftUI视图旋转后完全适配屏幕且不超出边界?

问题描述

我有一个固定尺寸的View——RectangleView,用于展示外部来源的任意宽高内容,因此不希望通过.frame修饰符直接调整其框架。当用户点击按钮旋转该视图时,视图可正常旋转,但我希望横向旋转后的RectangleView能适配屏幕(同时保持宽高比)且不超出显示边界。

原代码如下:

// Playground
  
import SwiftUI
import PlaygroundSupport

struct RectangleView: View {
    @Binding var rotation: Double

    var body: some View {
        Rectangle()
            .fill(Color.red)
            .frame(width: 100, height: 200) // fixed
            .rotationEffect(.degrees(rotation))
    }
}

struct ContentView: View {
    @State public var rotation: Double = 0

    var body: some View {
        VStack {
            RectangleView(rotation: $rotation)

            Button(action: {
                withAnimation {
                    rotation += 90
                }
            }) {
                Text("Rotate")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
    }
}

PlaygroundPage.current.setLiveView(ContentView())

尝试过使用.aspectRatio(contentMode: .fit)或GeometryReader来让矩形在90度或270度旋转时适配屏幕,但均无效。该如何解决此问题?


解决思路与代码实现

问题核心是旋转操作在固定frame之后执行,导致旋转后的视图脱离了父容器的布局计算,aspectRatio无法对旋转后的尺寸生效。我们需要调整布局逻辑:先基于旋转状态计算适配尺寸,再通过缩放保持原始frame不变,同时让视图适配屏幕。

具体实现步骤

  • 用GeometryReader获取父容器的可用空间
  • 根据当前旋转角度,判断是否需要交换原始宽高(90/270度旋转时,宽高逻辑上互换)
  • 计算适配父容器的最大缩放比例,保持原始宽高比
  • 先应用缩放适配,再执行旋转操作

修改后的完整代码:

import SwiftUI
import PlaygroundSupport

struct RectangleView: View {
    @Binding var rotation: Double
    // 原始固定尺寸,可替换为外部传入的参数
    private let originalSize = CGSize(width: 100, height: 200)
    
    var body: some View {
        GeometryReader { proxy in
            let containerSize = proxy.size
            // 判断是否处于横向旋转状态(90或270度,取模360处理循环旋转)
            let rotationMod = Int(rotation.truncatingRemainder(dividingBy: 360))
            let isLandscapeRotation = rotationMod == 90 || rotationMod == 270
            
            // 根据旋转状态调整原始尺寸的宽高顺序
            let adjustedOriginalSize = isLandscapeRotation 
                ? CGSize(width: originalSize.height, height: originalSize.width) 
                : originalSize
            
            // 计算适配容器的最大缩放比例
            let widthScale = containerSize.width / adjustedOriginalSize.width
            let heightScale = containerSize.height / adjustedOriginalSize.height
            let scale = min(widthScale, heightScale)
            
            Rectangle()
                .fill(Color.red)
                .frame(width: originalSize.width, height: originalSize.height) // 保持原始固定frame
                .scaleEffect(scale)
                .rotationEffect(.degrees(rotation))
        }
    }
}

struct ContentView: View {
    @State public var rotation: Double = 0

    var body: some View {
        VStack(spacing: 20) {
            // 让RectangleView占据屏幕除按钮外的所有可用空间
            RectangleView(rotation: $rotation)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            
            Button(action: {
                withAnimation {
                    rotation += 90
                }
            }) {
                Text("Rotate")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
            .padding(.bottom)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

PlaygroundPage.current.setLiveView(ContentView())

关键说明

  1. 保留原始frame:通过scaleEffect实现适配,而非直接修改.frame,满足“不调整原始框架”的需求
  2. 旋转状态适配:90/270度旋转时,视图的视觉宽高会互换,因此计算适配尺寸时需要先交换原始宽高
  3. 利用GeometryReader:让视图能感知父容器的可用空间,确保适配逻辑基于实际屏幕尺寸
  4. 父容器布局设置:给ContentView和RectangleView设置maxWidth/maxHeight: .infinity,确保视图能利用屏幕的全部可用空间

内容的提问来源于stack exchange,提问作者swiftenjoyer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:33:22