Dylan McDonald
Dylan McDonald

Reputation: 109

How to set a default/preferred window size for a Mac Catalyst app?

I have an iOS app that I have enabled Catalyst for. One function in the app opens a new window. By default, this window opens very large but I need a way to make it smaller by default. I know you can set windowScene.sizeRestrictions?.minimumSize and .maximumSize, but that then limits the window to my preferred size. I'd like to make it so the window opens a certain size, say 500x800 by default, but can be expanded by the user to whatever they want.

I have tried window?.frame = CGRect(origin: .zero, size: CGSize(width: 500, height: 800)) in the SceneDelegate, but it has no effect.

Visual example: Image showing my goal window size versus the default window size

Upvotes: 3

Views: 2432

Answers (3)

FPP
FPP

Reputation: 348

If you want to restore the window size and position from a previous run this might be the right solution for you:

First of all you have to implement a SceneDelegate for your app. That’s straightforward for UIKit but a little more complicated for SwiftUI. Take a look at this post for that: https://www.fivestars.blog/articles/app-delegate-scene-delegate-swiftui/

It’s a good idea to remember the corresponding window scene in the delegate:

class MySceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject {
    var scene: UIWindowScene?                           // remember our window scene
…

And then you implement this delegate function:

    …
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
#if targetEnvironment(macCatalyst)                      // only for running on a Mac
        guard let windowScene = scene as? UIWindowScene else { return }
        self.scene = windowScene                        // remember our window scene
        var preferredRect: CGRect? {                    // is a size preference present?
            var rect: CGRect?                           // by default its not
            let width = UserDefaults.standard.double(forKey: MyMessages.windowSizeWidth)
            let height = UserDefaults.standard.double(forKey: MyMessages.windowSizeHeight)
            let x = UserDefaults.standard.double(forKey: MyMessages.windowOriginX)
            let y = UserDefaults.standard.double(forKey: MyMessages.windowOriginY)
            if width > 0 && height > 0 {                // we do have a valid size
                rect = CGRect(x: x, y: y, width: width, height: height)
            }
            return rect                                 // return the rect (or nothing)
        }
        if let rect = preferredRect {                   // we do have a rect
            let geoPrefs = UIWindowScene.GeometryPreferences.Mac(systemFrame: rect)
            windowScene.requestGeometryUpdate(geoPrefs) // apply it to the current scene
        }
#endif
    }
}

To store the current geometry of your window you can use any View in your app with a GeometryReader:

struct MyView: View {
    @EnvironmentObject var sceneDelegate: MySceneDelegate
…
    var body: some View {
        GeometryReader { geo in
…
            .onChange(of: geo.size) {               // size changed
#if targetEnvironment(macCatalyst)                  // are we running on a Mac?
                if let rect = sceneDelegate.scene?.effectiveGeometry.systemFrame {  // get the system frame from the scene delegate
                    UserDefaults.standard.set(rect.width, forKey: MyMessages.windowSizeWidth)       // and set the user defaults
                    UserDefaults.standard.set(rect.height, forKey: MyMessages.windowSizeHeight)
                    UserDefaults.standard.set(rect.origin.x, forKey: MyMessages.windowOriginX)
                    UserDefaults.standard.set(rect.origin.y, forKey: MyMessages.windowOriginY)
                }
#endif
            }
        }
    }
}

Upvotes: 0

Fattie
Fattie

Reputation: 12582

Following Dylan's great tip,

func tidyCatalystWindow() {
    #if targetEnvironment(macCatalyst)
    UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
     .forEach { ws in
        ws.sizeRestrictions?.minimumSize = CGSize(width: 500, height:800)
        ws.sizeRestrictions?.maximumSize = CGSize(width: 500, height: 800)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            ws.sizeRestrictions?.maximumSize = CGSize(width: 9000, height: 9000)
        }
    }
    #endif
}

It's that easy.

Upvotes: 3

Dylan McDonald
Dylan McDonald

Reputation: 109

Figured it out, so I'll help anyone with this issue in the future. Set your .maximumSize as your preferred size. Then after setting the window, use DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { to set the .maximumSize again, but this time what you want to be the actual maximum window size.

My full code:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        
        #if targetEnvironment(macCatalyst)
        let toolbarDelegate = NewSchoolworkToolbar()
        let toolbar = NSToolbar(identifier: "main")
        windowScene.title = "New Schoolwork"
        if let titlebar = windowScene.titlebar {
            titlebar.toolbar = toolbar
            titlebar.toolbarStyle = .unified
            titlebar.separatorStyle = .shadow
        }
        #endif
        
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            let newClassworkView = NewClassworkTableViewController()
            
            
            windowScene.sizeRestrictions?.minimumSize = CGSize(width: 400, height: 500)
            // This will be your "preferred size"
            windowScene.sizeRestrictions?.maximumSize = CGSize(width: 500, height: 800)
            
            window.rootViewController = newClassworkView
            self.window = window
            window.makeKeyAndVisible()
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            // This will be your actual size.
            windowScene.sizeRestrictions?.maximumSize = CGSize(width: 9000, height: 9000)
        }
    }

Upvotes: 6

Related Questions