12

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?

Boris Y.
  • 4,387
  • 2
  • 32
  • 50
devilhunter
  • 321
  • 1
  • 4
  • 13

8 Answers8

21
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].

Wekwa
  • 226
  • 1
  • 2
  • Looking at the example xcode project, there is no 'window' definition. (window is undeclared). even if i declare 'window' to the programmatically created 'RoundWindow', the Titlebar shows again because it's now a normal window class. – devilhunter Apr 16 '11 at 12:11
  • 2
    Keep in mind that in order to be correct for multiple displays, you also need to do `xPos += [[window screen] frame].origin.x; yPos += [[window screen] frame].origin.y;` – Glyph Jul 14 '12 at 07:55
  • 1
    @Glyph the gist of what you're saying is correct, but the math is wrong. Depending on the display arrangements, you need to do different calculations. For example if the second screen is to the left of the main screen, you have to -= the xPos. I'm still figuring out how to do the calculation properly for all screen arrangements. – Ben Baron Oct 27 '17 at 14:12
  • 1
    @BenBaron Please do add a link when you figure out that math :) – Glyph Dec 08 '17 at 20:56
  • 1
    @Glyph we finally did figure it out. Our app is open source so take a look at the updateWindowFrame and updateWindowOrigin methods here: https://github.com/balancemymoney/balance-open/blob/master/Balance/macOS/Frameworks/CCNStatusItem/CCNStatusItemWindowController.m – Ben Baron Dec 12 '17 at 16:17
  • Note that our use case is a bit different since our app is a status bar app, but we had the same positioning problem. Sorry it's not commented, but it should give you an idea of what we needed to do to get it working properly. – Ben Baron Dec 12 '17 at 16:18
  • Actually not sure how relevant it will be for you because a lot of our solution involves using our status item button. – Ben Baron Dec 12 '17 at 16:21
  • Also it's been a while and another dev ended up fixing it, so sorry I can't help much more than pointing your to our code. – Ben Baron Dec 12 '17 at 16:21
  • @RamarajT: `[window center]` works only for Titled window. For title-less window we have to do as this answer suggested. – Anoop Vaidya Aug 07 '20 at 09:36
  • @AnoopVaidya Okay – Ramaraj T Aug 10 '20 at 06:30
10
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))
        }
    }
}
Klaas
  • 22,394
  • 11
  • 96
  • 107
4

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.

CRD
  • 52,522
  • 5
  • 70
  • 86
  • Taking the top code from the 'Drawing a custom window on Mac OS X' link, where and how should NSScreen be used there? – devilhunter Apr 15 '11 at 22:09
  • @devilhunter: see @Wekwa - `[window screen]` returns an `NSScreen`, the one the window is currently on. If you want to place the window on a particular screen `NSScreen` will give you a list of all of them, the main one, etc. – CRD Apr 16 '11 at 00:29
3

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];
}
dengST30
  • 3,643
  • 24
  • 25
2

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)
}
JDev
  • 5,168
  • 6
  • 40
  • 61
1

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

Yuval Tal
  • 645
  • 7
  • 12
0

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)
        }
}
xySVerma
  • 941
  • 9
  • 12
0

NSWindow has a property screen, which one has a property visibleFrame. This property works like safeArea (a screen frame excluding menu bar and dock). There is Swift code for NSWindow exension.

extension NSWindow {
    func centerOnScreen() {
        guard let screen else {
            return
        }
        
        let screenFrame = screen.visibleFrame
        let windowFrame = frame
        
        let xPosition = screenFrame.width / 2 - windowFrame.width / 2
        let yPosition = screenFrame.height / 2 - windowFrame.height / 2
        let origin = NSPoint(x: xPosition, y: yPosition)
        setFrameOrigin(origin)
    }
}
Ace Rodstin
  • 132
  • 1
  • 6