16

I want to be able to add a button to the title bar of all windows that open on a Mac.

The button will go on the right hand side, opposite the X - + buttons.

This is asked about windows of my app here:
How can I create Yosemite-style unified toolbar in Interface Builder?

But I want the button to appear on all windows, of any app, that are opened on the Mac. Obviously, this will only happen once the user has installed this program.

I understand that this is essentially "plugging into" the OS's UI, but I have seen other apps do this, which makes me feel that it is do-able.

Here is screenshot where I want the button:

Screenshot

pkamb
  • 33,281
  • 23
  • 160
  • 191
Timsk
  • 163
  • 1
  • 6
  • Just be careful where you add it- users with Mac OS X 10.7 and up will have a "Fullscreen" button on the right hand side. Make sure your button is moved to the left a bit. – Sam Spencer Mar 31 '12 at 14:48
  • A useful related question: http://stackoverflow.com/questions/3833241/adding-secondary-text-to-window-title-bar-in-cocoa – Graham Miln Mar 25 '14 at 13:03

5 Answers5

16

The officially-supported way to add a title bar button in OS X 10.10 (Yosemite) and later is by creating an NSTitlebarAccessoryViewController and adding it to your window using -[NSWindow addTitlebarAccessoryViewController].

For example, I have a window that has a title bar accessory view:

demo window title bar

To set this up, I started by adding a standalone view to the window controller scene in my storyboard. (You don't have to use a storyboard but I did in this project.)

accessory view

My accessory view is an NSView with an NSButton subview. The button title uses Font Awesome to display the pushpin.

I connected the accessory view to an outlet (cleverly named accessoryView) in my NSWindowController subclass:

outlet connection

Then, in my window controller's windowDidLoad, I create the NSTitlebarAccessoryViewController, set its properties, and add it to the window:

@IBOutlet var accessoryView: NSView!
var accessoryViewController: NSTitlebarAccessoryViewController?

override func windowDidLoad() {
    super.windowDidLoad()
    createAccessoryViewControllerIfNeeded()
}

fileprivate func createAccessoryViewControllerIfNeeded() {
    guard self.accessoryViewController == nil else { return }
    let accessoryViewController = NSTitlebarAccessoryViewController()
    self.accessoryViewController = accessoryViewController
    accessoryViewController.view = accessoryView
    accessoryViewController.layoutAttribute = .right
    self.window?.addTitlebarAccessoryViewController(accessoryViewController)
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
13

This answer addresses the latest Xcode Version 9.3

  • Go to a storyboard.
  • Drag and drop a toolbar in the storyboard to a Window.

enter image description here

  • The toolbar will be visible below a title.

enter image description here

  • Locate the Window on the storyboard

enter image description here

  • Check "Hide Title" in properties of the Window.

enter image description here

  • The toolbar is a part of the title now.

enter image description here

frido
  • 789
  • 5
  • 14
9

This is really a two-part question. As for how to get a button up there, I’d suggest using -[NSWindow standardWindowButton:] to get an existing window button and its superview (i. e. the title bar):

NSButton *closeButton = [window standardWindowButton:NSWindowCloseButton]; // Get the existing close button of the window. Check documentation for the other window buttons.
NSView *titleBarView = closeButton.superview; // Get the view that encloses that standard window buttons.
NSButton *myButton = …; // Create custom button to be added to the title bar.
myButton.frame = …; // Set the appropriate frame for your button. Use titleBarView.bounds to determine the bounding rect of the view that encloses the standard window buttons.
[titleBarView addSubview:myButton]; // Add the custom button to the title bar.

The plugging-in is probably easiest to do as a SIMBL plug-in.

gcbrueckmann
  • 2,413
  • 18
  • 10
  • From what I can tell, using NSWindowCloseButton and similar is used to decide whether or not to show the standard buttons. The API isn't very helpful here, doesn't tell me what the method would return or anything. What I mean is, I'm not sure how I would use the standardWindowButton:] to place my own buttons, it seems to be for editing the existing ones. – Timsk Mar 31 '12 at 13:57
  • The API returns the requested buttons. You _can_ use it to hide them, but you needn’t. In the example, an existing window button is used to find the view that encloses the standard window buttons (i. e. the title bar view). I’ve expanded my example and added comments. I’m not quite sure what you mean by “doesn't tell me what the method would return or anything”. The NSWindow class reference [clearly does so](http://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSWindow_Class/Reference/Reference.html#//apple_ref/occ/instm/NSWindow/standardWindowButton:). – gcbrueckmann Mar 31 '12 at 14:11
  • Thanks for adding the comments, I shall give it a go. (Will look into that SIMBL plug-in now as never heard of it before). – Timsk Mar 31 '12 at 14:16
4

Swift 4 - In NSWindowController add the below code

if let window = window {
    let myButton = NSButton()
    myButton.title = "Help"
    myButton.bezelStyle = .rounded

    let titleBarView = window.standardWindowButton(.closeButton)!.superview!
    titleBarView.addSubview(myButton)
    myButton.translatesAutoresizingMaskIntoConstraints = false
    titleBarView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[myButton]-2-|", options: [], metrics: nil, views: ["myButton": myButton]))
    titleBarView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-1-[myButton]-3-|", options: [], metrics: nil, views: ["myButton": myButton]))
}

Hope this is helpful.

adiga
  • 34,372
  • 9
  • 61
  • 83
Iyyappan Ravi
  • 3,205
  • 2
  • 16
  • 30
0

enter image description here

All you need is to call createTitleBarButtons().

import Foundation
import SwiftUI

fileprivate var windowBtnsAdded = false

extension NSWindow {
    func createTitleBarBtns() {
        guard windowBtnsAdded == false else { return }
        
        createToggleBackground()
        
        createToggleFloating()
        
        windowBtnsAdded.toggle()
    }
    
    fileprivate func createToggleFloating() {
        let titleBarView = self.standardWindowButton(.closeButton)!.superview!
        
        let btn = NSButton()
        btn.setButtonType(.toggle)
        btn.isBordered = false
        btn.action = #selector(toggleWndFloating(_:))
        
        btn.title = ""
        btn.image = NSImage(systemSymbolName: "pin.slash.fill", accessibilityDescription: nil)
        btn.alternateImage = NSImage(systemSymbolName: "pin.fill", accessibilityDescription: nil)
        
        titleBarView.addSubview(btn)
        
        // remember, you ALWAYS need to turn of the auto resize mask!
        btn.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            btn.widthAnchor.constraint(equalToConstant: 15.0),
            btn.heightAnchor.constraint(equalToConstant: 15.0),
            btn.trailingAnchor.constraint(equalTo: titleBarView.trailingAnchor, constant: -10),
            btn.topAnchor.constraint(equalTo: titleBarView.topAnchor, constant: 7)
        ])
    }
    
    fileprivate func createToggleBackground() {
        let titleBarView = self.standardWindowButton(.closeButton)!.superview!
        
        let btn2 = NSButton()
        btn2.setButtonType(.toggle)
        btn2.isBordered = false
        btn2.action = #selector(toggleWndBg(_:))
        
        btn2.title = ""
        btn2.image = NSImage(systemSymbolName: "rectangle.portrait.fill", accessibilityDescription: nil)
        btn2.alternateImage = NSImage(systemSymbolName: "rectangle.portrait", accessibilityDescription: nil)
        
        titleBarView.addSubview(btn2)
        
        // remember, you ALWAYS need to turn of the auto resize mask!
        btn2.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            btn2.widthAnchor.constraint(equalToConstant: 15.0),
            btn2.heightAnchor.constraint(equalToConstant: 15.0),
            btn2.trailingAnchor.constraint(equalTo: titleBarView.trailingAnchor, constant: -30),
            btn2.topAnchor.constraint(equalTo: titleBarView.topAnchor, constant: 7)
        ])
        
    }
    
    @IBAction func toggleWndBg(_ sender: AnyObject) {
        AppModel.shared.toggleClearBg()
    }
    
    @IBAction func toggleWndFloating(_ sender: AnyObject) {
        AppModel.shared.toggleFloating()
    }
}

#endif

Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101