9

Porting a game to macOS Catalyst, but window is quite small. Is it possible to start in full screen instead?

Paulo Mattos
  • 16,310
  • 10
  • 64
  • 73
Lim Thye Chean
  • 6,652
  • 9
  • 40
  • 77

4 Answers4

9

Yes it is possible to start in full screen.

Method #1 (more generic way for using AppKit from Mac Catalyst app)

To switch to full screen you need to use AppKit and NSApplication class but currently that is not available in Mac Catalyst app directly. However you can access it from another plugin bundle. This is how you do that and switch to full screen on app start:

Step 1. You need to create a new mac bundle target in your app. Click File -> New -> Target -> macOS -> Bundle and then click button Next. Enter product name for example MacBundle and click button Finish.

Step 2. Select the newly created group MacBundle in your project and click File -> New -> macOS -> Cocoa Class and the click button Next. Enter class name for example MacApp that is a subclass of NSObject and set language to Objective-C. Click Next, make sure that MacBundle target is selected and click button Create.

Step 3. Edit MacApp.h like this:

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface MacApp : NSObject

+ (void)toggleFullScreen;

@end

NS_ASSUME_NONNULL_END

Step 4. Edit MacApp.m like this:

#import "MacApp.h"

@implementation MacApp

+ (void)toggleFullScreen {
    [[[[NSApplication sharedApplication] windows] firstObject] toggleFullScreen:nil];
}

@end

Step 5. Click on your project and in Targets section select your main app target (the same which is for iOS)

Step 6. In General tab scroll down to Frameworks, Libraries, and Embeeded Content section and click + button. In new popup for choosing framework select MacBundle.bundle and click button Add to embeed this bundle in your main app.

Step 7. Now you can call toggleFullScreen method from your MacApp class that is in MacBundle from your main iOS code. To make it work you can call it once from viewDidAppear from the first UIViewController that appears in your app. You can call it like below:

static var needsFullScreen = true

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    if Self.needsFullScreen {
        Bundle(path: Bundle.main.builtInPlugInsPath?.appending("/MacBundle.bundle") ?? "")?.load()
        let macApp = NSClassFromString("MacApp") as? NSObjectProtocol
        macApp?.perform(NSSelectorFromString("toggleFullScreen"))
        Self.needsFullScreen = false
    }
}

Alternatively you could create a protocol with that toggleFullScreen method.

After that when you launch the app it will switch to fullscreen automatically.

Method #2 (less generic but faster for this specific case)

If you do not plan to use other AppKit stuff then for this one toggleFullScreen call shown in previous method you can just call it without plugin bundle with runtime functions once from viewDidAppear from the first UIViewController that appears in your app like below:

static var needsFullScreen = true

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    if Self.needsFullScreen {
        (NSClassFromString("NSApplication")?.value(forKeyPath: "sharedApplication.windows") as? [AnyObject])?.first?.perform(Selector("toggleFullScreen:"))
        Self.needsFullScreen = false
    }
}
Community
  • 1
  • 1
Leszek Szary
  • 8,558
  • 1
  • 49
  • 46
  • If you are going to use runtime functions within the catalyst app to access `MacApp`, why use a framework at all? You can call `toggleFullScreen:` without a framework. –  Mar 02 '20 at 16:59
  • Yes you could do that also. If you only want to call a single method it would not be that hard but it would be very painful in case when you would also need to execute more complex stuff on NSApplication. So above solution shows a more generic solution that is easier to extend later in case you need to access other NSApplication stuff. – Leszek Szary Mar 02 '20 at 18:44
  • You can do that in one line using [Dynamic](https://github.com/mhdhejazi/Dynamic): `Dynamic.NSApplication.sharedApplication.windows.firstObject.toggleFullScreen(nil)` – Hejazi Apr 18 '20 at 22:41
3

There's no simple setting that says "start full screen". But you can set the window's frame on startup.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let _ = (scene as? UIWindowScene) else { return }

    #if targetEnvironment(macCatalyst)
    window?.frame = CGRect(origin: .zero, size: CGSize(width: 1600, height: 1000))
    #endif

Obviously that's not ideal because you don't want to hardcode a specific size.

You could get the screen's size as follows. But in my own tests the returned value is not accurate. This might be a bug in Mac Catalyst.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let winScene = (scene as? UIWindowScene) else { return }

    #if targetEnvironment(macCatalyst)
    let screen = winScene.screen
    let size = screen.nativeBounds.size
    window?.frame = CGRect(origin: .zero, size: size)
    #endif
}

This makes it bigger but it's not truly fullscreen because, at least in my tests, the returned screen size doesn't actually match the size of the screen.

But this should give you some ideas.

You can also set a minimum and maximum size on your screen:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let winScene = (scene as? UIWindowScene) else { return }

    #if targetEnvironment(macCatalyst)
    if let sizes = winScene.sizeRestrictions {
        let screen = winScene.screen
        let size = screen.nativeBounds.size
        sizes.minimumSize = size
        sizes.maximumSize = size
    }
    #endif
}

In this example, the screen won't be resizable because both the min and max are the same. Adjust to suit the needs of your app. If you give different values for the min and max you can also be combine this with setting the window frame if you want the initial size to be between the min and max setting.


Here is the same solution in Objective-C:

- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
    if (![scene isKindOfClass:[UIWindowScene class]]) { return; }

    UIWindowScene *winScene = (UIWindowScene *)scene;

#if TARGET_OS_MACCATALYST
    UISceneSizeRestrictions *sizes = winScene.sizeRestrictions;
    if (sizes) {
        UIScreen *screen = winScene.screen;
        CGSize size = screen.nativeBounds.size;
        sizes.minimumSize = size;
        sizes.maximumSize = size;
    }
#endif
rmaddy
  • 298,130
  • 40
  • 468
  • 517
  • In Catalina screen sizes are always reported as 1920x1080. The real screen sizes should now be reported to Catalyst apps in Big Sur. Ref. https://developer.apple.com/forums/thread/652276?answerId=617896022#617896022 – endavid Jul 04 '20 at 08:15
3

You can do it with a single line using Dynamic:

Dynamic.NSApplication.sharedApplication.windows.firstObject.toggleFullScreen(nil)
Hejazi
  • 14,917
  • 8
  • 42
  • 60
0

To get rid of the warning in Step 7:

    Bundle(path: Bundle.main.builtInPlugInsPath?.appending("/MacBundle.bundle") ?? "")?.load()
        
    let macClass: AnyClass? = NSClassFromString("MacApp")
        
    let macApp = macClass as AnyObject as? NSObjectProtocol
        
    macApp?.perform(NSSelectorFromString("toggleFullScreen"))
Tobias Timpe
  • 622
  • 12
  • 24