I'm trying to make a macOS app without using Interface Builder. My project builds and runs, but my primary view controller doesn't seem to be loading its view. That is, the viewDidLoad()
method is not invoked. I'm using Xcode-beta 8.0 beta 6 (8S201h).
The Swift 3 documentation for NSViewController
says this about the view
property:
If this property’s value is not already set when you access it, the view controller invokes the
loadView()
method. That method, in turn, sets the view from the nib file identified by the view controller’snibName
andnibBundle
properties.If you want to set a view controller’s view directly, set this property’s value immediately after creating the view controller.
And for viewDidLoad
:
For a view controller created programmatically, this method is called immediately after the
loadView()
method completes.
Finally, for isViewLoaded
:
A view controller employs lazy loading of its view: Immediately after a view controller is loaded into memory, the value of its
isViewLoaded
property isfalse
. The value changes totrue
after theloadView()
method returns and just before the system calls theviewDidLoad()
method.
So the documentation leads me to believe it is possible.
My project looks like this:
.
├── main.swift
└── Classes
├── AppDelegate.swift
└── Controllers
└── PrimaryController.swift
main.swift
import Cocoa
let delegate = AppDelegate()
NSApplication.shared().delegate = delegate
NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
AppDelegate.swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let pc = PrimaryController(nibName: nil, bundle: nil)!
print(pc.isViewLoaded)
pc.view = NSView()
print(pc.isViewLoaded)
}
}
PrimaryController.swift
import Cocoa
class PrimaryController: NSViewController {
override func loadView() {
print("PrimaryController.loadView")
}
override func viewDidLoad() {
super.viewDidLoad()
print("PrimaryController.viewDidLoad")
}
}
Building and running a project with the above yields this console output:
false
true
That is, the view loads for the controller, but the loadView
and viewDidLoad
methods are never invoked.
What am I missing?
Minimal setup to make ViewController from code (Swift 4.1)
class WelcomeViewController: NSViewController {
private lazy var contentView = MyCustomClassOrJustNSView()
override func loadView() {
view = contentView
}
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError()
}
}
A careful reading of the documentation is enlightening. Specifically:
If this property’s value is not already set when you access it, the view controller invokes the
loadView()
method.
Meaning, loadView()
is only invoked when an attempt is made to read the view
property before the property has a value. Assigning a value to the property directly will bypass the method call.
So if AppDelegate.applicationDidFinishLaunching(_:)
is written like this:
let pc = PrimaryController(nibName: nil, bundle: nil)!
print(pc.isViewLoaded)
let x = pc.view // accessing the view property before it has a value
print(pc.isViewLoaded)
...then loadView
will be invoked by the system to attempt to load the view:
false
PrimaryController.loadView
false
Note that viewDidLoad()
is still not invoked, since no view can be automatically associated with the controller (ie, the nib is nil
and PrimaryController.xib
doesn't exist). The correct way to complete the process is to manually load the view in PrimaryController.loadView()
:
print("PrimaryController.loadView")
view = NSView() // instantiate and bind a view to the controller
Now the program gives the desired result:
false
PrimaryController.loadView
PrimaryController.viewDidLoad
true
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