12
votes

I've been using the example from here to create a custom titlebar-less window:

Drawing a custom window on Mac OS X

I've found this is the only way i can create a titlebar-less window in Leopard, Snow Leopard and Lion, other methods don't work right either on Leopard or Lion. (If i try to invoke a titlebar-less window via normal NSWindow and IB, it won't start up in Leopard anymore)

So far this custom titlebar-less window works great everywhere, but i can't center it, only a hard fixed position in Interface Builder.

It's fairly easy to center a normal NSWindow *window implementation with [window center], but i've found nothing that works on this custom window subclass, a window that isn't created from nib via Interface Builder.

I've tried a few things from NSWindow, but nothing seems to work.

Any Ideas?

7

7 Answers

21
votes
CGFloat xPos = NSWidth([[window screen] frame])/2 - NSWidth([window frame])/2;
CGFloat yPos = NSHeight([[window screen] frame])/2 - NSHeight([window frame])/2;
[window setFrame:NSMakeRect(xPos, yPos, NSWidth([window frame]), NSHeight([window frame])) display:YES];

This puts it at the literal center of the screen, not taking into account the space occupied by the dock and menu bar. If you want to do that, change [[window screen] frame] to [[window screen] visibleFrame].

10
votes
extension NSWindow {
    public func setFrameOriginToPositionWindowInCenterOfScreen() {
        if let screenSize = screen?.frame.size {
            self.setFrameOrigin(NSPoint(x: (screenSize.width-frame.size.width)/2, y: (screenSize.height-frame.size.height)/2))
        }
    }
}
4
votes

The question should probably be why [window center] does not work; but assuming that is the case use NSScreen to get the screen coordinates, do the math, and center the window directly.

2
votes

Objective - C in macOS Catalina , Version 10.15.3

more readable @Wekwa's answer

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    NSWindow * window = NSApplication.sharedApplication.windows[0];
    CGFloat xPos = NSWidth(window.screen.frame)/2 - NSWidth(window.frame)/2;
    CGFloat yPos = NSHeight(window.screen.frame)/2 - NSHeight(window.frame)/2;
    [window setFrame:NSMakeRect(xPos, yPos, NSWidth(window.frame), NSHeight(window.frame)) display:YES];
}
1
votes

I stumbled on the same issue. What worked for me is to set isRestorable to false on the NSWindow object before calling center().

0
votes

Swift version: One can write a simple static utility function for the same

static func positionWindowAtCenter(sender: NSWindow?){
        if let window = sender {
            let xPos = NSWidth((window.screen?.frame)!)/2 - NSWidth(window.frame)/2
            let yPos = NSHeight((window.screen?.frame)!)/2 - NSHeight(window.frame)/2
            let frame = NSMakeRect(xPos, yPos, NSWidth(window.frame), NSHeight(window.frame))
            window.setFrame(frame, display: true)
        }
}
0
votes

Updated for Swift 5

I always found that centering the window with screen.frame resulted in a window that feels as if it were too close to the bottom of the screen. This is due to the Dock size being nearly 3x as large as the Status/Menu Bar (roughly 70-80px vs. 25px, respectively).

This causes the window to appear as if it were positioned lower to the bottom of the screen, as our eyes do not automatically adjust the insets of the Status Bar (1x size) and the Dock (~3x size).

For this reason, I always choose to go to with screen.visibleFrame, as it definitely feels more centered. visibleFrame takes both the Status Bar and Dock sizes into consideration, and calculates the central points for the frame between those two objects.

extension NSWindow {
    
    /// Positions the `NSWindow` at the horizontal-vertical center of the `visibleFrame` (takes Status Bar and Dock sizes into account)
    public func positionCenter() {
        if let screenSize = screen?.visibleFrame.size {
            self.setFrameOrigin(NSPoint(x: (screenSize.width-frame.size.width)/2, y: (screenSize.height-frame.size.height)/2))
        }
    }
    /// Centers the window within the `visibleFrame`, and sizes it with the width-by-height dimensions provided.
    public func setCenterFrame(width: Int, height: Int) {
        if let screenSize = screen?.visibleFrame.size {
            let x = (screenSize.width-frame.size.width)/2
            let y = (screenSize.height-frame.size.height)/2
            self.setFrame(NSRect(x: x, y: y, width: CGFloat(width), height: CGFloat(height)), display: true)
        }
    }
    /// Returns the center x-point of the `screen.visibleFrame` (the frame between the Status Bar and Dock).
    /// Falls back on `screen.frame` when `.visibleFrame` is unavailable (includes Status Bar and Dock).
    public func xCenter() -> CGFloat {
        if let screenSize = screen?.visibleFrame.size { return (screenSize.width-frame.size.width)/2 }
        if let screenSize = screen?.frame.size { return (screenSize.width-frame.size.width)/2 }
        return CGFloat(0)
    }
    /// Returns the center y-point of the `screen.visibleFrame` (the frame between the Status Bar and Dock).
    /// Falls back on `screen.frame` when `.visibleFrame` is unavailable (includes Status Bar and Dock).
    public func yCenter() -> CGFloat {
        if let screenSize = screen?.visibleFrame.size { return (screenSize.height-frame.size.height)/2 }
        if let screenSize = screen?.frame.size { return (screenSize.height-frame.size.height)/2 }
        return CGFloat(0)
    }

}

Usage

NSWindow

Positions the existing window to the center of visibleFrame.

window!.positionCenter()

Sets a new window frame, at the center of visibleFrame, with dimensions

window!.setCenterFrame(width: 900, height: 600)

NSView

Using xCenter() and yCenter() to get the central x-y points of the visibleFrame.

let x = self.view.window?.xCenter() ?? CGFloat(0)
let y = self.view.window?.yCenter() ?? CGFloat(0)
self.view.window?.setFrame(NSRect(x: x, y: y, width: CGFloat(900), height: CGFloat(600)), display: true)

Function Example

override func viewDidLoad() {
    super.viewDidLoad()
    initWindowSize(width: 900, height: 600)
}

func initWindowSize(width: Int, height: Int) {
    let x = self.view.window?.xCenter() ?? CGFloat(0)
    let y = self.view.window?.yCenter() ?? CGFloat(0)
    self.view.window?.setFrame(NSRect(x: x, y: y, width: CGFloat(width), height: CGFloat(height)), display: true)
}