11
votes

I've come across an issue where I cannot access my app's main window, as it returns nil.

let window = NSApplication.sharedApplication().mainWindow

I've found similar questions:

How to get Main Window (App Delegate) from other class (subclass of NSViewController)?

But doing:

let window = (NSApplication.sharedApplication() as! NSArray).objectAtIndex(0)

Doesn't seem to work either.

Do I have to mess around in Storyboard?

Thanks in advance.

Update:

I'm actually trying to port something from Objective-C.

    NSWindow *mainWindow = [[[NSApplication sharedApplication] windows] objectAtIndex:0];
    NSLog(@"%@", mainWindow.contentViewController);

This returns a proper value, when put in the viewDidLoad() block in a NSViewController, so I'm guessing there is something wrong with NSApplication.sharedApplication().

6

6 Answers

9
votes

It also can return nil if the main app window is not active on macOS. For instance I ran into this when I was making a drag and drop file uploader. If the window was not in the front (on the operating system) it would return nil. The following line of code will activate your app (bring to front)

NSApp.activateIgnoringOtherApps(true)

I also needed a timer to delay my call of mainWindow in my case.

2
votes

You could do this instead:

   func applicationDidBecomeActive(notification: NSNotification) {

        NSApplication.sharedApplication().mainWindow?.movable = false
    }
1
votes

From the docs:

The value in this property is nil when the app’s storyboard or nib file has not yet finished loading. It might also be nil when the app is inactive or hidden.

So, this is a perfectly normal thing to have happen, depending on the circumstances.

1
votes

If your app only contains one window, or always starts with the same window without even removing it from memory, you can use this code:

if let window = NSApp.windows.first {
    window.makeKeyAndOrderFront(self) // Or do something else
}

If you need the window during startup of your app, you should load this code async, like this:

DispatchQueue.main.async {
    if let window = NSApp.windows.first {
        window.makeKeyAndOrderFront(self)
    }
}
0
votes

Make sure you have a Window in your storyboard as Initial Controller

0
votes

For me the accepted answer did not work. The documentation for activate(ignoringOtherApps:) says:

...you shouldn’t assume the app will be active immediately after sending this message.

For this reason, mainWindow will most likely remain nil. So for me the following works:

extension NSApplication {
  /// Activates the app so main window / key window are no longer `nil`. Pauses a bit between activations and keeps trying.
  /// - Parameter completion: completion called with the key / main window
  @objc public static func withKeyOrMainWindow(mainOnly: Bool = false, completion: @escaping (_ window: NSWindow)->Void) {
    DispatchQueue.main.async {
      let window = mainOnly ? NSApplication.shared.mainWindow : NSApplication.shared.keyWindow ?? NSApplication.shared.mainWindow
      if let window = window {
        completion(window)
      }
      else {
        // Activate the app
        NSApp.activate(ignoringOtherApps: true)
        
        // Short delay
        DispatchQueue.main.asyncAfter(deadline: .now()+0.2) {
          NSApplication.withKeyOrMainWindow(completion: completion)
        }
      }
    }
  }
  
  /// Activates the app so main window is no longer `nil`. Pauses a bit between activations and keeps trying.
  /// - Parameter completion: completion called with the main window
  @objc public static func withMainWindow(completion: @escaping (_ window: NSWindow)->Void) {
    return withKeyOrMainWindow(mainOnly: true, completion: completion)
  }
}

Usage:

NSApplication.withKeyOrMainWindow { window in
  alert.beginSheetModal(for: window) { (response: NSApplication.ModalResponse) in
        handler?(response)
  }
}