Skip to content

Instantly share code, notes, and snippets.

@mminer
Last active May 19, 2024 05:06
Show Gist options
  • Save mminer/caec00d2165362ff65e9f1f728cecae2 to your computer and use it in GitHub Desktop.
Save mminer/caec00d2165362ff65e9f1f728cecae2 to your computer and use it in GitHub Desktop.
NSTabViewController for preferences window that resizes itself to fit activated tab view.
import AppKit
class PreferencesViewController: NSTabViewController {
private lazy var tabViewSizes: [NSTabViewItem: NSSize] = [:]
override func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {
super.tabView(tabView, didSelect: tabViewItem)
if let tabViewItem = tabViewItem {
view.window?.title = tabViewItem.label
resizeWindowToFit(tabViewItem: tabViewItem)
}
}
override func tabView(_ tabView: NSTabView, willSelect tabViewItem: NSTabViewItem?) {
super.tabView(tabView, willSelect: tabViewItem)
// Cache the size of the tab view.
if let tabViewItem = tabViewItem, let size = tabViewItem.view?.frame.size {
tabViewSizes[tabViewItem] = size
}
}
/// Resizes the window so that it fits the content of the tab.
private func resizeWindowToFit(tabViewItem: NSTabViewItem) {
guard let size = tabViewSizes[tabViewItem], let window = view.window else {
return
}
let contentRect = NSRect(x: 0, y: 0, width: size.width, height: size.height)
let contentFrame = window.frameRect(forContentRect: contentRect)
let toolbarHeight = window.frame.size.height - contentFrame.size.height
let newOrigin = NSPoint(x: window.frame.origin.x, y: window.frame.origin.y + toolbarHeight)
let newFrame = NSRect(origin: newOrigin, size: contentFrame.size)
window.setFrame(newFrame, display: false, animate: true)
}
}
@mminer
Copy link
Author

mminer commented Feb 5, 2020

Hmm, I'm not sure; it's been a while since I've touched AppKit code.

@caraffa
Copy link

caraffa commented Mar 6, 2020

@svoida

this would do the trick (and would also be enough to resize the tabviews. Only problem, it doesn't animate!)

override func tabView(_ tabView: NSTabView, willSelect tabViewItem: NSTabViewItem?) {
    super.tabView(tabView, willSelect: tabViewItem)

    preferredContentSize = (tabViewItem?.view?.frame.size)!
 }

This is how I managed to fix the initial size issue

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    //var settingsWindow: SettingsWindow!
    var settingsWindowController: NSWindowController!

    // MARK: - App Delegate
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Instanciate Preferences Window
        settingsWindowController = NSStoryboard(name: "Settings", bundle: nil).instantiateController(identifier: "SettingsWindowController")
        
        // Set the initial size and title
        if let settingsViewController: SettingsViewController = settingsWindowController.contentViewController as? SettingsViewController {
            settingsWindowController.window?.setContentSize(settingsViewController.tabViewSizes.first?.value ?? NSSize(width: 500.0, height: 500.0))
            settingsWindowController.window?.title = settingsViewController.tabViewItems[0].label
        }
    }
.
.
.
class SettingsViewController: NSTabViewController {
    
    public lazy var tabViewSizes: [NSTabViewItem: NSSize] = [:]

    override func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {
        super.tabView(tabView, didSelect: tabViewItem)

        if let tabViewItem = tabViewItem {
            view.window?.title = tabViewItem.label
            resizeWindowToFit(tabViewItem: tabViewItem)
        }
    }

    override func tabView(_ tabView: NSTabView, willSelect tabViewItem: NSTabViewItem?) {
        super.tabView(tabView, willSelect: tabViewItem)
        
        // Cache the size of the tab view.
        if let tabViewItem = tabViewItem, let size = tabViewItem.view?.frame.size, !tabViewSizes.keys.contains(tabViewItem) {
            tabViewSizes[tabViewItem] = size
        }

        if tabViewItem?.identifier as! String != "NSTabViewDatabaseItem" {
            self.view.window?.styleMask.remove( [ .resizable ] )
        } else {
            self.view.window?.styleMask.insert( [ .resizable ] )
        }
    }
.
.
.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment