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

SwiftUI设备旋转后更新tapGesture响应区域的问题

解决方案:修复SwiftUI侧边菜单旋转后点击区域不更新问题

问题根源

  1. 静态宽度计算:sideBarWidth使用UIScreen.main.bounds初始化,属于静态值,设备旋转后不会自动更新,导致侧边栏宽度与背景点击区域错位。
  2. 无效的背景视图布局:嵌套的GeometryReader { _ in EmptyView() }未绑定当前容器的动态尺寸,旋转后手势响应区域仍停留在旧尺寸范围。
  3. 手势区域未跟随布局更新:背景视图尺寸未随父容器旋转实时调整,导致onTapGesture触发区域无法同步适配新界面。

修改步骤与代码实现

1. 替换静态宽度为动态计算

将sideBarWidth改为从GeometryReader的proxy中实时获取当前视图宽度的70%,确保旋转时自动更新。

2. 简化背景视图布局

移除嵌套的GeometryReader,直接使用Color作为背景——Color默认会填充父容器全部空间,旋转时自动适配新尺寸。

3. 优化视图依赖关系

让视图依赖于GeometryReader的proxy尺寸,旋转时proxy变化会自动触发视图重绘,无需手动更新手势区域。

修改后的完整代码:

struct ContentView: View {
    var viewModel = SettingsSideMenuViewModel()
    var body: some View {
        VStack {
            SideMenu(viewModel: viewModel)
                
            Button("Present menu") {
                viewModel.isShown.toggle()
            }
            Spacer()
        }
        .padding()
    }
}

final class SettingsSideMenuViewModel: ObservableObject {
    @Published var isShown = false
    
    func dismissHostingController() {
        guard !isShown else { return }
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
            debugPrint("viewShoudBeDismissedHere")
        }
    }
}

struct SideMenu: View {
    
    @ObservedObject var viewModel: SettingsSideMenuViewModel
    
    var body: some View {
        GeometryReader { proxy in
            let sideBarWidth = proxy.size.width * 0.7 // 动态计算侧边栏宽度
            ZStack {
                // 简化背景视图,直接用Color填充整个容器
                Color.black.opacity(0.6)
                    .opacity(viewModel.isShown ? 1 : 0)
                    .animation(.easeInOut.delay(0.2), value: viewModel.isShown)
                    .onTapGesture {
                        viewModel.isShown.toggle()
                        viewModel.dismissHostingController()
                    }
                
                content(sideBarWidth: sideBarWidth)
            }
            .edgesIgnoringSafeArea(.all)
        }
    }
    
    // 将侧边栏宽度作为参数传入,确保实时更新
    func content(sideBarWidth: CGFloat) -> some View {
        HStack(alignment: .top) {
            ZStack(alignment: .top) {
                Color.white
                
                Text("SOME VIEW HERE")
                
                VStack(alignment: .leading, spacing: 20) {
                    Text("SOME VIEW HERE")
                    Divider()
                    Text("SOME VIEW HERE")
                    Divider()
                    Text("SOME VIEW HERE")
                }
                .padding(.top, 80)
                .padding(.horizontal, 40)
            }
            .frame(width: sideBarWidth)
            .offset(x: viewModel.isShown ? 0 : -sideBarWidth)
            .animation(.default, value: viewModel.isShown)
            
            Spacer()
        }
    }
}

struct DeviceRotationViewModifier: ViewModifier {
    let action: (UIDeviceOrientation) -> Void
    
    func body(content: Content) -> some View {
        content
            .onAppear()
            .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
                action(UIDevice.current.orientation)
            }
    }
}

extension View {
    func onRotate(perform action: @escaping (UIDeviceOrientation) -> Void) -> some View {
        self.modifier(DeviceRotationViewModifier(action: action))
    }
}

struct SideMenu_Previews: PreviewProvider {
    static var viewModel = SettingsSideMenuViewModel()
    static var previews: some View {
        SideMenu(viewModel: viewModel)
    }
}

额外说明

  • 若仍需监听设备旋转状态,可保留onRotate修饰符,但上述修改已通过GeometryReader的动态尺寸实现手势区域自动更新,无需依赖orientation变量。
  • 移除了无用的@State private var orientation变量,简化代码结构。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 13:05:27