Search code examples
iosswiftswiftuiwkwebview

How do you make an app's orientation dependent on a URL using requestGeometryUpdate?


I am new to Swift and iOS development. I am trying to wrap a web app where the orientation is dependent on the URL. I have the code working with Stack Overflow as an example where "https://stackoverflow.com" displays in portrait and all other pages change to landscape after being loaded. I have a URL observer that triggers when the URL changes and calls requestGeometryUpdate. I'm running into the following problem:

When changing the orientation with requestGeometryUpdate, the orientation changes, but if the device is physically rotated after the change, the orientation changes again. I would like to make the orientation change locked and permanent until a new page is loaded.

Any help would be much appreciated. I have attached my code below:

import SwiftUI
import WebKit

struct TestView: View {
    private let urlString: String = "https://stackoverflow.com/"
    var body: some View {
        TestWebView(url: URL(string: urlString)!)
            .background(Color.black)
            .scrollIndicators(.hidden)
            .ignoresSafeArea([.all])//stretchs webview over notch on iphone
            .defersSystemGestures(on:.bottom)//deprioritizes multitasking indicator
            .statusBar(hidden: true)//hides time and battery
    }
}

class TestController: UIViewController {
    var webview: WKWebView!
    var webViewURLObserver: NSKeyValueObservation?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let winScene = UIApplication.shared.connectedScenes.first
        let windowScene = winScene as! UIWindowScene
        
        webview = WKWebView(frame: self.view.frame)
        webview.autoresizingMask = [.flexibleWidth,.flexibleHeight]//makes webview fit screen in portrait and landscape

        self.view.addSubview(self.webview)

        webViewURLObserver = self.webview.observe(\.url, options: .new) { webview, change in
            let url=change.newValue!!;//! converts from optional to string
            print(url)
            let arr = url.absoluteString.split(separator: "stackoverflow.com").map(String.init)
            var portrait=false
            if(arr.count>1){
                let path = arr[1];
                if path=="/"{
                    portrait=true
                }
            }
            
            if portrait==true {
                windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) { error in print(error)}
            }
            else{
                windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .landscape)) { error in print(error)}
            }
            self.setNeedsUpdateOfSupportedInterfaceOrientations()
        }
    }
}

// WebView Struct
struct TestWebView: UIViewControllerRepresentable {
    let url: URL

    func makeUIViewController(context: Context) -> TestController {
        let webviewController = TestController()
        return webviewController
    }

    func updateUIViewController(_ webviewController: TestController, context: Context) {
        let request = URLRequest(url: url)
        webviewController.webview.scrollView.contentInsetAdjustmentBehavior = .never
        webviewController.webview.load(request)
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView()
    }
}

Solution

  • There is an answer here that may help you with this: SwiftUI: How do I lock a particular View in Portrait mode whilst allowing others to change orientation?

    Basically what your want to do is add this AppDelegate variable/class to your App Main. See Below:

    struct WebApp: App {
        @UIApplicationDelegateAdaptor(AppDelegate.self) var app_delegate
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    class AppDelegate: NSObject, UIApplicationDelegate {
        static var allowed_orientations = UIInterfaceOrientationMask.portrait
        func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
            return AppDelegate.allowed_orientations
        }
    }
    

    Then in your ViewDidLoad you will add the orientation lock where you are setting the portrait variable.

    if portrait==true {
        windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) { error in print(error)}
    }
    else{
        windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .landscape)) { error in print(error)}
    }
    AppDelegate.allowed_orientations = portrait ? .portrait : .landscape
    self.setNeedsUpdateOfSupportedInterfaceOrientations()