Earlier this year I wrote a short article about creating NSWindow without Storyboard with code only. While collecting information, I read several opinions why it was a tedious job, and it could be done better...
Maybe the easiest way to mix Storyboard and coding approach. As I pointed out, recreation of NSMenu is a real pain/mess. So, what about keeping Main.storyboard for NSMenu only and code the rest of the components?
Step 1.
Create a new macOS app.
Step 2.
Delete:
- Window Controller Scene
- View Controller Scene
This way we can keep NSMenu and need to recreate NSWindow and NSView with their Controllers.
Step 3.
Add a new Cocoa (NSWindow) class with the following code:
class MainWindow: NSWindow { | |
override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) { | |
super.init(contentRect: contentRect, styleMask: [.miniaturizable, .closable, .resizable, .titled], backing: .buffered, defer: true) | |
isMovableByWindowBackground = true | |
} | |
} |
Probably, the most important part of NSWindow init method is the StyleMask.
I added these four constants:
- titled: The window displays a title bar
- closable: The window displays a close button
- miniaturizable: The window displays a minimize button
- resizable: The window can be resized by the user
It's worth checking the documentation to find out more about NSWindow StyleMask.
Step 4.
Add a new Cocoa class, an NSWindowController.
In the Controller file we need to define the size and position of our new window. Beside these we need to prepare the window to hold a view too as content.
class MainWindowController: NSWindowController { | |
convenience init() { | |
self.init(windowNibName: "") | |
} | |
override func loadWindow() { | |
// MARK: Create window | |
let windowSize = NSSize(width: 600, height: 300) | |
let screenSize = NSScreen.main?.frame.size ?? .zero | |
let rect = NSMakeRect(screenSize.width/2 - windowSize.width/2, screenSize.height/2 - windowSize.height/2, windowSize.width, windowSize.height) | |
window = MainWindow(contentRect: rect, styleMask: [], backing: .buffered, defer: true) | |
self.window?.title = "SemiStoryboard Window" | |
self.window?.titlebarAppearsTransparent = true | |
self.window?.styleMask.insert(.fullSizeContentView) | |
self.window?.contentViewController = MyViewController() | |
} | |
} |
Note: the convinience init method is required as NSWindowController cannot load the relevant xib file, unless NSViewController. Since we don't want to use any xib files, so we set empty string there.
Step 5.
Add a new Cocoa class, an NSViewController.
As I mentioned above, NSViewController will try to use xib file with the same name, we override this to set it to nil.
class MyViewController: NSViewController { | |
init() { | |
super.init(nibName: nil, bundle: nil) | |
} | |
required init?(coder: NSCoder) { | |
fatalError() | |
} | |
override func viewDidLoad() { | |
} | |
override func loadView() { | |
super.viewDidLoad() | |
self.view = NSView() | |
view.frame.size = CGSize(width: 600, height: 300) | |
view.wantsLayer = true | |
view.layer?.backgroundColor = NSColor.white.cgColor | |
} | |
} |
It's important not to forget that NSViewController is not enough alone. We need an NSView to show the content, which size is the same as defined for NSWindow.
Run the application. Everything seems to be fine but... we have no error and no window. Why? We have never told our application that it has an NSWindow.
Step 6.
Go to AppDelegate.swift and define our newly created NSWindow. Once we have it as variable, we need to show in applicationDidFinishLaunching method.
class AppDelegate: NSObject, NSApplicationDelegate { | |
lazy var mainWindowController = MainWindowController() | |
func applicationDidFinishLaunching(_ aNotification: Notification) { | |
// Insert code here to initialize your application | |
mainWindowController.showWindow(nil) | |
} | |
func applicationWillTerminate(_ aNotification: Notification) { | |
// Insert code here to tear down your application | |
} | |
} |
Run the application now, and you'll see the window.
Step 7.
Add 'Hello, World' label as NSTextField to the View using Constraints.
let label = NSTextField() | |
label.frame = CGRect(x: 0, y: 0, width: 100, height: 44) | |
label.stringValue = "Hello, World!" | |
label.backgroundColor = .white | |
label.isBezeled = false | |
label.isEditable = false | |
label.sizeToFit() | |
view.addSubview(label) | |
label.translatesAutoresizingMaskIntoConstraints = false | |
let labelConstraint = [ | |
label.centerXAnchor.constraint(equalTo: view.centerXAnchor), | |
label.centerYAnchor.constraint(equalTo: view.centerYAnchor), | |
] | |
NSLayoutConstraint.activate(labelConstraint) |
Note: We'll align the label to the center of the View, not important to give other x and y coordinates than 0. You use sizeToFit() method, so width and height of the NSTextfield are irrelevant as well.
Source code: Github
You can find more macOS programming tutorial on my blog.
Top comments (0)