iosswiftuiscreen-rotationswiftui-ontapgesture

Updating tapGesture area(frame) after device is rotated SwiftUI


I have an issue with updating the area(frame) of .onTapGesture after a device is rotated. Basically, even after changing @State var orientation the area where .onTapGesture works remain the same as on the previous orientation. Would appreciate having any advice on how to reset that tap gesture to the new area after rotation. Thanks in advance!

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
    
    @State private var orientation = UIDeviceOrientation.unknown
    
    var sideBarWidth = UIScreen.main.bounds.size.width * 0.7
    
    var body: some View {
        
        GeometryReader { proxy in
            ZStack {
                GeometryReader { _ in
                    EmptyView()
                }
                .background(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
            }
            .edgesIgnoringSafeArea(.all)
            .frame(width: proxy.size.width,
                   height: proxy.size.height)
            .onRotate { newOrientation in
                orientation = newOrientation
            }
        }
    }
    
    var content: 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)
    }
}

In this example is just slideoutMenu with a blurred area. By opening that menu in portrait and taping on the blurred area this menu should close. The issue is when the menu is opened in portrait and then rotated to landscape - the tapGesture area stays the same as it was in portrait, hence if tapped in the landscape - nothing happens. This works in the same direction too. Thus the question is how to reset the tapGesture area on rotation?


Solution

  • This view is presented in UIHostingController. slideOutView?.modalPresentationStyle = .custom the issue is there. But if slideOutView?.modalPresentationStyle = .fullScreen (or whatever) - everything works okay.