Re-Visiting NSViewController.loadView's behaviour in 2024 and under macOS 15...

This is a post down memory lane for you AppKit developers and Apple engineers...

TL;DR:

  • When did the default implementation of NSViewController.loadView start making an NSView when there's no matching nib file? (I'm sure that used to return nil at some point way back when...)
  • If you override NSViewController.loadView and call [super loadView] to have that default NSView created, is it safe to then call self.view within loadView?

I'm refactoring some old Objective-C code that makes extensive use of NSViewController without any use of nibs. It overrides loadView, instantiates all properties that are views, then assigns a view to the view controller's view property. This seems inline with the documentation and related commentary in the header. I also (vaguely) recall this being a necessary pattern when not using nibs:

@interface MyViewController: NSViewController 
  // No nibs
  // No nibName
@end

@implementation MyViewController

- (void)loadView { 
  NSView *hostView = [[NSView alloc] initWithFrame:NSZeroRect];

  self.button = [NSButton alloc...];
  self.slider = [NSSlider alloc...];

  [hostView addSubview:self.button];
  [hostView addSubview:self.slider];

  self.view = hostView;
}

@end

While refactoring, I was surprised to find that if you don't override loadView and do all of the setup in viewDidLoad instead, then self.view on a view controller is non-nil, even though there was no nib file that could have provided the view. Clearly NSViewController has realized that:

  1. There's no nib file that matches nibName.
  2. loadView is not overridden.
  3. Created an empty NSView and assigned it to self.view anyways.

Has this always been the behaviour or did it change at some point? I could have sworn that if there as no matching nib file and you didn't override loadView, then self.view would be nil.

I realize some of this behaviour changed in 10.10, as noted in the header, but there's no mention of a default NSView being created.

Because there are some warnings in the header and documentation around being careful when overriding methods related to view loading, I'm curious if the following pattern is considered "safe" in macOS 15:

- (void)loadView {
  // Have NSViewController create a default view. 
  [super loadView]; 

  self.button = [NSButton...];
  self.slider = [NSSlider...];

  // Is it safe to call self.view within this method? 
  [self.view addSubview:self.button];
  [self.view addSubview:self.slider];
}

Finally, if I can rely on NSViewController always creating an NSView for me, even when a nib is not present, then is there any recommendation on whether one should continue using loadView or instead move code the above into viewDidLoad?

- (void)viewDidLoad { 
  self.button = [NSButton...];
  self.slider = [NSSlider...];

  // Since self.view always seems to be non-nil, then what 
 // does loadView offer over just using viewDidLoad?
  [self.view addSubview:self.button];
  [self.view addSubview:self.slider];
}

This application will have macOS 15 as a minimum requirement.

Re-Visiting NSViewController.loadView's behaviour in 2024 and under macOS 15...
 
 
Q