I want to fake a titlebar (bigger and with a different color), so my way until now is the following:
I added a NSView directly below the titlebar and then I set the titlebar to transparent with this code:
self.window.titlebarAppearsTransparent = true
self.window.styleMask |= NSFullSizeContentViewWindowMask
The next step is, that I subclassed the NSView to add some drawing methods (background etc.) and especially the code, so that I can use the complete NSView for moving the window (therefore I use this code: https://stackoverflow.com/a/4564630/2062613)
This is the result:
Now the next thing I want to do is to vertically center the traffic light buttons in this new titlebar. I know, that I can access the buttons with self.window.standardWindowButton(NSWindowButton.CloseButton)
(for example). But changing the frame.origin
of one of the button doesn't have any effect.
How can I change the origin.y value of the buttons?
UPDATE
I discovered, that the window resizing re-arranges the buttons. Now I decided to add the buttons as subviews to my fake titlebar, because moving the origin in the titlebar cuts off the buttons (it's obviously limited to the titlebar rect).
This works, but strangely the mouseover effect of the buttons still remains in the titlebar. Look at this screen:
This is actually my code:
func moveButtons() {
self.moveButtonDownFirst(self.window.standardWindowButton(NSWindowButton.CloseButton)!)
self.moveButtonDownFirst(self.window.standardWindowButton(NSWindowButton.MiniaturizeButton)!)
self.moveButtonDownFirst(self.window.standardWindowButton(NSWindowButton.ZoomButton)!)
}
func moveButtonDownFirst(button: NSView) {
button.setFrameOrigin(NSMakePoint(button.frame.origin.x, button.frame.origin.y+10.0))
self.fakeTitleBar.addSubview(button)
}
You need to add toolbar and change window property titleVisibility
. Here more details NSWindow Style Showcase.
let customToolbar = NSToolbar()
window?.titleVisibility = .hidden
window?.toolbar = customToolbar
Swift 4.2 version (without Toolbar).
Idea behind:
Normal screen.
Full screen mode.
Real application
File FullContentWindow.swift
public class FullContentWindow: Window {
private var buttons: [NSButton] = []
public let titleBarAccessoryViewController = TitlebarAccessoryViewController()
private lazy var titleBarHeight = calculatedTitleBarHeight
private let titleBarLeadingOffset: CGFloat?
private var originalLeadingOffsets: [CGFloat] = []
public init(contentRect: NSRect, titleBarHeight: CGFloat, titleBarLeadingOffset: CGFloat? = nil) {
self.titleBarLeadingOffset = titleBarLeadingOffset
let styleMask: NSWindow.StyleMask = [.closable, .titled, .miniaturizable, .resizable, .fullSizeContentView]
super.init(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: true)
titleVisibility = .hidden
titlebarAppearsTransparent = true
buttons = [NSWindow.ButtonType.closeButton, .miniaturizeButton, .zoomButton].compactMap {
standardWindowButton($0)
}
var accessoryViewHeight = titleBarHeight - calculatedTitleBarHeight
accessoryViewHeight = max(0, accessoryViewHeight)
titleBarAccessoryViewController.view.frame = CGRect(dimension: accessoryViewHeight) // Width not used.
if accessoryViewHeight > 0 {
addTitlebarAccessoryViewController(titleBarAccessoryViewController)
}
self.titleBarHeight = max(titleBarHeight, calculatedTitleBarHeight)
}
public override func layoutIfNeeded() {
super.layoutIfNeeded()
if originalLeadingOffsets.isEmpty {
let firstButtonOffset = buttons.first?.frame.origin.x ?? 0
originalLeadingOffsets = buttons.map { $0.frame.origin.x - firstButtonOffset }
}
if titleBarAccessoryViewController.view.frame.height > 0, !titleBarAccessoryViewController.isHidden {
setupButtons()
}
}
}
extension FullContentWindow {
public var standardWindowButtonsRect: CGRect {
var result = CGRect()
if let firstButton = buttons.first, let lastButton = buttons.last {
let leadingOffset = firstButton.frame.origin.x
let maxX = lastButton.frame.maxX
result = CGRect(x: leadingOffset, y: 0, width: maxX - leadingOffset, height: titleBarHeight)
if let titleBarLeadingOffset = titleBarLeadingOffset {
result = result.offsetBy(dx: titleBarLeadingOffset - leadingOffset, dy: 0)
}
}
return result
}
}
extension FullContentWindow {
private func setupButtons() {
let barHeight = calculatedTitleBarHeight
for (idx, button) in buttons.enumerated() {
let coordY = (barHeight - button.frame.size.height) * 0.5
var coordX = button.frame.origin.x
if let titleBarLeadingOffset = titleBarLeadingOffset {
coordX = titleBarLeadingOffset + originalLeadingOffsets[idx]
}
button.setFrameOrigin(CGPoint(x: coordX, y: coordY))
}
}
private var calculatedTitleBarHeight: CGFloat {
let result = contentRect(forFrameRect: frame).height - contentLayoutRect.height
return result
}
}
File FullContentWindowController.swift
open class FullContentWindowController: WindowController {
private let fullContentWindow: FullContentWindow
private let fullContentViewController = ViewController()
public private (set) lazy var titleBarContentContainer = View().autolayoutView()
public private (set) lazy var contentContainer = View().autolayoutView()
private lazy var titleOffsetConstraint =
titleBarContentContainer.leadingAnchor.constraint(equalTo: fullContentViewController.contentView.leadingAnchor)
public init(contentRect: CGRect, titleBarHeight: CGFloat, titleBarLeadingOffset: CGFloat? = nil) {
fullContentWindow = FullContentWindow(contentRect: contentRect, titleBarHeight: titleBarHeight,
titleBarLeadingOffset: titleBarLeadingOffset)
super.init(window: fullContentWindow, viewController: fullContentViewController)
contentWindow.delegate = self
fullContentViewController.contentView.addSubviews(titleBarContentContainer, contentContainer)
let standardWindowButtonsRect = fullContentWindow.standardWindowButtonsRect
LayoutConstraint.withFormat("V:|[*][*]|", titleBarContentContainer, contentContainer).activate()
LayoutConstraint.pin(to: .horizontally, contentContainer).activate()
LayoutConstraint.constrainHeight(constant: standardWindowButtonsRect.height, titleBarContentContainer).activate()
LayoutConstraint.withFormat("[*]|", titleBarContentContainer).activate()
titleOffsetConstraint.activate()
titleOffsetConstraint.constant = standardWindowButtonsRect.maxX
}
open override func prepareForInterfaceBuilder() {
titleBarContentContainer.backgroundColor = .green
contentContainer.backgroundColor = .yellow
fullContentViewController.contentView.backgroundColor = .blue
fullContentWindow.titleBarAccessoryViewController.contentView.backgroundColor = Color.red.withAlphaComponent(0.4)
}
public required init?(coder: NSCoder) {
fatalError()
}
}
extension FullContentWindowController {
public func embedTitleBarContent(_ viewController: NSViewController) {
fullContentViewController.embedChildViewController(viewController, container: titleBarContentContainer)
}
public func embedContent(_ viewController: NSViewController) {
fullContentViewController.embedChildViewController(viewController, container: contentContainer)
}
}
extension FullContentWindowController: NSWindowDelegate {
public func windowWillEnterFullScreen(_ notification: Notification) {
fullContentWindow.titleBarAccessoryViewController.isHidden = true
titleOffsetConstraint.constant = 0
}
public func windowWillExitFullScreen(_ notification: Notification) {
fullContentWindow.titleBarAccessoryViewController.isHidden = false
titleOffsetConstraint.constant = fullContentWindow.standardWindowButtonsRect.maxX
}
}
Usage
let windowController = FullContentWindowController(contentRect: CGRect(...),
titleBarHeight: 30,
titleBarLeadingOffset: 7)
windowController.embedContent(viewController) // Content "Yellow area"
windowController.embedTitleBarContent(titleBarController) // Titlebar "Green area"
windowController.showWindow(nil)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With