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

SwiftUI自定义子视图对齐问题:适配横竖屏相机预览

SwiftUI相机预览适配与对齐问题解决方案

需求说明

  • 横屏模式下显示16:9比例的相机预览(用Color.blue模拟),竖屏模式下显示9:16比例
  • 相机预览需与安全区前边缘对齐(可微调偏移)
  • 底部按钮覆盖层需与相机预览的边缘严格对齐

原代码问题

顶层Color.clear通过onGeometryChange获取的尺寸在横竖屏切换时出现转置错误,导致手动计算的偏移量失效,同时覆盖层未与相机预览绑定对齐。

修正后的代码

struct CameraUI: View {
    @Environment(\.verticalSizeClass) var verticalSizeClass
    @State private var safeAreaInsets: EdgeInsets = .zero
    
    private var previewAspectRatio: CGFloat {
        verticalSizeClass == .regular ? 9/16 : 16/9
    }
    
    var body: some View {
        GeometryReader { fullScreenProxy in
            ZStack(alignment: .leading) {
                // 相机预览层
                Color.blue
                    .aspectRatio(previewAspectRatio, contentMode: .fit)
                    .frame(width: fullScreenProxy.size.width)
                    .padding(.leading, safeAreaInsets.leading)
                    .ignoresSafeArea(edges: [.top, .bottom, .trailing])
                
                // 对齐的按钮覆盖层
                VStack {
                    Spacer()
                    HStack {
                        Button("Button1") {}
                        Spacer()
                        Button("Button2") {}
                    }
                    .padding(.horizontal, safeAreaInsets.leading + 16) // 可微调内边距
                    .padding(.bottom, safeAreaInsets.bottom + 16)
                }
                .frame(width: fullScreenProxy.size.width)
            }
        }
        .persistentSystemOverlays(.hidden)
        .onAppear {
            // 获取安全区边距
            if let window = UIApplication.shared.connectedScenes
                .compactMap({ $0 as? UIWindowScene })
                .first?.windows.first {
                safeAreaInsets = window.safeAreaInsets
            }
        }
        .onRotate { newOrientation in
            // 横竖屏切换时重新获取安全区
            if let window = UIApplication.shared.connectedScenes
                .compactMap({ $0 as? UIWindowScene })
                .first?.windows.first {
                safeAreaInsets = window.safeAreaInsets
            }
        }
    }
}

// 用于监听屏幕旋转的扩展
extension View {
    func onRotate(perform action: @escaping (UIDeviceOrientation) -> Void) -> some View {
        self.modifier(RotationModifier(action: action))
    }
}

struct RotationModifier: ViewModifier {
    let action: (UIDeviceOrientation) -> Void
    @State private var orientation = UIDevice.current.orientation
    
    func body(content: Content) -> some View {
        content
            .onAppear {
                UIDevice.current.beginGeneratingDeviceOrientationNotifications()
                NotificationCenter.default.addObserver(
                    forName: UIDevice.orientationDidChangeNotification,
                    object: nil,
                    queue: .main
                ) { _ in
                    orientation = UIDevice.current.orientation
                    action(orientation)
                }
            }
            .onDisappear {
                UIDevice.current.endGeneratingDeviceOrientationNotifications()
            }
    }
}

#Preview {
    CameraUI()
}

关键改动说明

  1. 用GeometryReader替代onGeometryChange:直接获取全屏尺寸,避免横竖屏切换时的尺寸转置问题,确保比例计算准确。
  2. 安全区对齐处理:通过onAppear和onRotate监听安全区边距,用padding(.leading)让相机预览与安全区前边缘对齐,可根据需求微调数值。
  3. 覆盖层绑定对齐:将按钮覆盖层与相机预览放在同一个ZStack内,使用相同的frame和安全区边距计算,确保两者边缘完全匹配。
  4. 屏幕旋转监听:添加onRotate扩展,横竖屏切换时重新获取安全区边距,保证适配实时生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 23:44:57