Hi I've seen this question asked a few times already but with no definite answer yet so I created it for xcode 7 and swift2 (which may have changed things a bit anyway).
I created a project using Xcode 7 and Cocoa OSX Story boards + swift2, so my project started with a NSWindowController that Connects to a NSViewController (as expected!). I added a NSToolbar to my window controller and added a NSButton to the toolbar. I changed my NSViewController to be one of the new NSSplitViewController that links to three NSViewControllers and displays their views horizontally - with vertical dividers - (similar to the layout you see in the photo app or pages in Yosemite +). My final goal will be that the button in My toolbar shows and hides the first split.
It is my understanding is, and I would expect that to achieve this I should create an action in the NSSplitViewController that changes the auto layout constrains more or less in the way they are working it out here: How to do collapse and expand view in mac application?.
And then somehow link this action to the NSButton that is in the Toolbar... which happens to be in the NSWindowController (far up and isolated in the hierarchy)...
I have already gone through other questions about NSToolbar and storyboards and failed to accomplish my goal:
The YouTube video: Cocoa Programming L17 - NSToolbar which is the closest I found to solve the problem, but his method does not work for storyboards, only creating your own xib file.
In this question: How to use NSToolBar in Xcode 6 and Storyboard? One person proposes to make the link using the first reponder and expecting everything to hook up at run-time (which looks a bit dodgy and not the way apple would implement it I think...). A second person suggested to create a view controller variable in the NSWindowController and manipulate its properties from there... but again, a bit dodgy too.
One latest comment I saw in that question which seems the best way to tackle the problem (but still not as good as I guess it could be) is to add a NSObjectController to the dock of each scene and when the scene loads, set the values of the objects to the other secene's controller. Is this really the best way to go ahead? If so, how could I achieve this one?
Apple did mention (again) in WWDC15 that they created storyboards for osx and the split-view controller that owns view-controllers so that you can move your logic and work to the specific view-controller, so I would be expecting to do everything from inside my split-view controller as this is the target that needs to change.
Does anyone know how to achieve this from the view controller itself? I really haven't been able to find a way to connect my ToolBarItem to it.
OK, I've created this question quite a few days ago and no answer so far so I've answer with what I recently did to overcome the problem.
After I created my Xcode project I did this:
Created a subclass MySplitViewController for the NSSplitViewController
Added an IBOutlet for each NSSplitViewItem. For example:
#IBOutlet weak var mySplitViewItem: NSSplitViewItem!
Created a subclass WindowController for the NSWindowController
Added an IBAction in the WindowController class that links to the NSToolbarItem (my button)
Added a property that gets the Window Controller's content as MySplitViewController
var mySplitViewController: MySplitViewController {
return self.window?.contentViewController as! MySplitViewController
}
Now I can access the split view controller's property from the Window Controller in the action I created:
mySplitViewController. mySplitViewItem.collapsed = true
I created some sample code that does this (but using a view controller and changing the text for a label here, just in case someone wants to see a working project with this behaviour. And a blog post about it too :)
One person proposes to make the link using the first reponder and expecting everything to hook up at run-time (which looks a bit dodgy and not the way apple would implement it I think...).
I think this first responder method is actually the proper way.
As an example:
Add something similar to the following, in whichever view controller makes sense.
#IBAction func doSomething(_ sender: AnyObject?) {
print("Do something.")
}
This will magically show up in the first responder:
In your storyboard, right-click the orange "first responder" icon above your window controller, and you should see doSomething in the very long list. You just need to connect that up to your toolbar button.
In the following screen capture, you can see my "Toggle Sidebar" button is connected to the toggleSidebar action in my first responder.
I didn't even have to write this method — it's provided by NSSplitViewController:
#IBAction open func toggleSidebar(_ sender: Any?)
So, I was working this same issue and finding no solution as you experienced. I read your post and was trying to figure how I would implement your solution when it occurred to me to use a notification. In about 30 seconds, I had a perfectly fine working solution:
In your windowController add an IBAction to post a notification like so
-(IBAction)toggleMasterViewClicked:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"TestNotification" object:nil];
}
Hook up that action to your NSToolbarItem, then in the viewController add self as an observer for that notification like so
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(toggleMasterView:) name:#"TestNotification" object:nil];
In your case, selector would be updateMyLabelText
I don't really see any downside here. No reference to other objects needed, no dependancies. Works flawlessly for me
While connectiong IBActions works by using either the First Responder or by adding an "Object" to the scene, then changing its class to the window's view controller class, this doesn't help with IBOutlets and delegates that you'd like to point to the view controller.
Here's a work-around for that:
Add the Toolbar to the View Controller, not to its Window. That way, you can make all the IBOutlet connections in the View Controller Scene easily. I've done that for years and found no issues with it, even when using Tabs.
You'll have to assign the window's toolbar in code, then. E.g. like this:
#interface ViewController ()
#property (weak) IBOutlet NSToolbar *toolbar; // connect this in your storyboard to the Toolbar that you moved to the View Controller Scene
#end
- (void)viewWillAppear {
[super viewWillAppear];
self.view.window.toolbar = self.toolbar;
}
Using Swift 1.1 and Xcode 6.2.
I have a UIStoryboard containing a singular, custom UIViewController subclass. On it, I have an #IBOutlet connection of type UIView from that controller to a UIView subclass on the storyboard. I also have similar outlets for subviews of that view. See figure A.
But at run time, these properties are nil (Figure B). Even though I have assured I've connected the outlets in Interface Builder.
Thoughts:
Is it possible that because I am using a subclass of a subclass something messes up with the initialization? I am not overriding any initializers
awakeFromNib: is not getting called for some reason
Maybe it doesn't connecting to subviews on subviews
Things I have tried:
Matching #IBOutlet and storyboard item types exactly (instead of UIView)
Deleting property and outlet and re-added them
Figure A*
Figure B
*The obscured code in Figure A is:
#IBOutlet private var annotationOptionsView: UIView!
#IBOutlet private var arrivingLeavingSwitch: UISegmentedControl!
Thank you.
Typically this happens because your view controller hasn't loaded its view hierarchy yet. A view controller only loads its view hierarchy when something sends it the view message. The system does this when it is time to actually put the view hierarchy on the screen, which happens after things like prepareForSegue:sender: and viewWillAppear: have returned.
Since your VC hasn't loaded its view hierarchy yet, your outlets are still nil.
You could force the VC to load its view hierarchy by saying _ = self.view.
Did you instantiate your view controller from a Storyboard or NIB, or did you instantiate it directly via an initializer?
If you instantiated your class directly with the initializer, the outlets won't be connected. Interface Builder creates customized instances of your classes and encodes those instances into NIBs and Storyboards for repeated decoding, it doesn't define the classes themselves. If this was your problem, you just need to change the code where you create your controller to instead use the methods on UIStoryboard, or UINib.
Have you tried running Product > Clean. Solved a very similar problem for me.
The storyboard wasn't recognizing any further UI things I added to it. At run time all the references were nil. So I cleared my derived data folder and then those connections worked again.
This happened for me because I was accidentally instantiating my view controller directly instead of instantiating it through the storyboard. If you instantiate directly via MyViewController() then the outlets won't be connected.
This was happening to me with my custom collection view cell. Turns out I had to replace my registerClassforReuseIdentifier method with registerNib. That fixed it for me.
In my case, it happened because I overriden the loadView method in my ViewController subclass, but forgot to add [super loadView]
-(void)loadView {
// blank
}
When you override the loadView method, the it is your responsibility to init your subviews. Since you override it, the views from interface builder do not get the chance to convert to cocoa objects and thus outlets remain nil.
If you implement loadView in your view controller subclass, then it becomes your responsibility load the UI elements from from storyboard/xib into code.
Or just call
[super loadView];
So that the superclass gets the chance to load storyboard/xib into code.
If you instantiate view controller through programmatically. Then
try creating it like below
let initialVC = self.storyboard?.instantiateViewController(withIdentifier: "InitialVC") as! InitialVC
instead of directly
let initialVC = InitialVC()
This worked for me.
You can call controller.view to force to load the view to initialize the IBOutlets, then you will be able to assign the values.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "identifier") {
let controller = segue.destinationViewController as! YourController
let _ = controller.view //force to load the view to initialize the IBOutlets
controller.your_IBOutlet_property = xxx
...
controller.delegate = self
}
}
I encounter this problem recently! Here is my thought.
The problem is not about you storyboard or any link issue. It is about how you initiate your ViewController. Especially when you are using Swift.(There is barely nothing in the editor when you create a class file)
By simply using the init() from super class can not initiate anything you worked with story board. So what you need to do is changing the initialisation of the ViewController. Replace
let XXViewController = XXViewController()
by
let XXViewController = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("XXViewController") as! XXViewController
This tells the program to go to the storyboard find XXViewController and initiates all IBOutlet in your storyboard.
Hope this help~ GL
For me, this occurred when I accidentally declared my view controller's class as
class XYZViewController: UINavigationController {
}
(ie as a UINavigationController not a UIViewController).
Xcode doesn't pick up on this mistake, the class seems to build fine, and override functions such as viewDidLoad, viewWillAppear, etc. all work correctly. But none of the IBOutlets get connected.
Changing the declaration to
class XYZViewController: UIViewController {
}
fixed it completely.
2019, ONE POSSIBILITY FOR THIS HORRIBLE PROBLEM:
Say you have perhaps a container view that shows some sort of clock. So you have
class Clock: UIViewController
You actually use it in a number of places in the app.
On the main screen, on the details screen, on the edit screen.
You have a complicated snapchat-like modern app.
In fact, Clock may actually be loaded more than once at the same time somewhere on the same screen. (Maybe it's hidden in some cases.)
You start working on one instance of Clock on one of your many storyboards.
On that storyboard you add a label, NewLabel.
Naturally you add the outlet in code. Everything should work. All the other outlets work perfectly.
You have definitely linked the outlet.
But the app crashes with NewLabel as nil.
Xcode clearly tells you "you forgot to connect the outlet".
The reason is this .......... you have "NewLabel" on only one of the storyboard uses of Clock!
The crash is actually from >>> an other place <<<< you are using Clock!!!!
Xcode does not tell you the crash is from another place altogether, not from where you are working!
The crash is actually not from the place you are working - it's from another storyboard, where there is no "NewLabel" item on that storyboard!!!
Frustrating.
For Swift 3.
func configureView() {
let _ = self.view
}
In my case, the app started crashing all of a sudden.
Debugging it revealed that all outlets were still nil at the time of viewDidLoad().
My app still uses nibs (not storyboards) for most view controllers. Everything was in place, all outlets wired properly. I double-checked.
We typically instantiate our view controllers as
let newVC = MYCustomViewController()
...which for some reason seems to work as long as the .xib is named the same as the view controller class (not sure how that works, though. We are not calling init(nibName:bundle:) with nil arguments, or overriding init() to do so on self like it is typically suggested...).
So I tried to explicitly call
let newVC = MYCustomViewController(nibName: "MYCustomViewController", bundle: .main)
...only to be greeted with the runtime exception error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not load NIB in bundle: 'NSBundle </Users/nicolasmiari/Library/Developer/CoreSimulator/Devices/3DA3CF21-108D-498F-9649-C4FC9E3C1A8D/data/Containers/Bundle/Application/C543DDC1-AE86-4D29-988C-9CCE89E23543/MyApp.app> (loaded)' with name 'MYCustomViewController''
And then, I saw it:
The "Target Membership" checkbox of the .xib file was unchecked.
Must have happened when resolving one of the frequent merge conflicts regarding the Xcode project file.
Apple definitely needs to come up with a project file format that is more SCM-friendly.
You need to load the view hierarchy first in order to instantiate the outlets in the storyboard. For this, you can manually call the loadView or loadViewIfNeeded methods.
100% Working solution for creating ViewControllers from XIB without StoryBoards
Create class CustomViewController : UIViewController
Create view CustomViewControllerView.xib
In CustomViewControllerView.xib in Interface Builder select Placeholders -> File's Owner
In "Attributes inspector" set Class to CustomViewController
In "Connections inspector" connect "view" to top-level view of xib (ensure top-level view's Class is not pointing to CustomViewController)
In "Connections inspector" connect other outlets (if needed/exist)
Create an instance of CustomViewController in parent view controller/App delegate
7.1.
// creating instance
let controller = CustomViewController()
7.2.
// connecting view/xib with controller instance
let bundle = Bundle(for: type(of: controller))
bundle.loadNibNamed("CustomViewControllerView", owner: controller, options: nil)
7.3.
// get/set outlets
controller.labelOutlet.text = "title"
controller.imageOutlet.image = UIImage(named: "image1")
Check your IBOutlet connection if it connected to the File owner or the view.
There could be mistakes.
Other case:
Your outlets won't get set until the view controller's view is actually instantiated, which in your case is probably happening shortly after initWithNibName:bundle:—at which point they'll still be nil. Any setup you do that involves those outlets should be happening in your view controller's -viewDidLoad method.
For me, I had same error on a localized storyboard, an element was added in some locale and not in the other, so I had null reference for that element when switched to the missing element locale, I had to remove (redundant) localization for that storyboard using https://stackoverflow.com/a/42256341/1356559.
For me, this was crashing because containerView was nil.
Here is my code with Crash.
#IBOutlet private var containerView: UIView! // Connected to Storyboard
override open func loadView() {
containerView.addSubview(anotherView)
}
The missing thing was calling the super.loadView(). So adding it solved problem for me.
Fixed Code:
#IBOutlet private var containerView: UIView!
override open func loadView() {
super.loadView()
containerView.addSubview(anotherView)
}
I had a similar issue when I had previously added register(_:forCellReuseIdentifier:) for the custom cell after I had already defined the identifier in the storyboard. Had this code in the viewDidLoad() function. Once I removed it, it worked fine.
Yet another case I just ran into. I changed the name of my class for the UIViewController, but I forgot to change the name of the .xib file where the interface was built.
Once I caught this and made the file names reflect the class name, it was all good!
I hope that helps someone.
Got one more ...
If you have a custom class for a UITableViewCell but forget to specify Custom in the Style of the cell.
Check to see if you have any missing or disconnected outlets.
You can validate if the is view is loaded.
if isViewLoaded && view.window != nil {
//self.annotationOptionsView.
}
select both .h and .m view controller files
remove the reference of those files
re-add the files to your project tree
open the storyboard, eventually re-build the project
Accidently I subclassed my view controller with AVPlayerViewController instead of UIViewController. By replaying it to UIViewController things back normal. This should help.
No build cleaning (normal&full), removing derived data folders and quitting Xcode worked for me.
I had the same problem after copying a class (linked to a xib) to reuse it with another viewcontroller class (linked to a storyboard).
I forgot to remove
override var nibName
and
override var nibBundle
methods.
After removing them, my outlets started to work.
I see you use ViewController!? in ViewController class you must use -viewDidLoad, not -awakeFromNib, -awakeFromNib use for UIView class
If you have two main.storyboards and you are making changes to the wrong one this can happen. This can happen anytime you connect an outlet from an uninstantiated storyboard.
Xcode 6.1, OSX not ios, allows me to Control-drag from a button in MainMenu.xib to AppDelegate only. This is unfortunate for me because my IBAction must include view methods like [self setNeedsDisplay:YES] . I need to Control-drag it into my MyView file, which will tolerate them. This also makes more sense.
Control-dragging from the button to any file other than AppDelegate does nothing.
Identity Inspector > Class is set to MYView.
How can I make this work, and how does the fix work?
Also, why is it now restricted to AppDelegate? Perhaps a timing issue?
Thanks ahead,
Nick
Try dragging an empty object from the object library to the area where you see 'AppDelegate' then selecting it and setting its' class to 'MyView' then secondary dragging to that object to create the IBAction.
The upcoming OSX 10.10 ("Yosemite") offers a new type of view, NSVisualEffectView, which supports through-the-window or within-the-window translucency. I'm mostly interested in through-the-window translucency, so I'm going to focus on that in this question, but it applies to within-the-window translucency as well.
Using through-the-window translucency in 10.10 is trivial. You just place an NSVisualEffectView somewhere in your view hierarchy and set it's blendingMode to NSVisualEffectBlendingModeBehindWindow. That's all it takes.
Under 10.10 you can define NSVisualEffectViews in IB, set their blending mode property, and you're off and running.
However, if you want to be backwards-compatible with earlier OSX versions, you can't do that. If you try to include an NSVisualEffectView in your XIB, you'll crash as soon as you try to load the XIB.
I want a "set it and forget it" solution that will offer translucency when run under 10.10 and simply degrade to an opaque view when run on earlier OS versions.
What I've done so far is to make the view in question a normal NSView in the XIB, and then add code (called by awakeFromNib) that checks for [NSVisualEffectView class] != nil, and when it's the class is defined, I create an instance of the NSVisualEffectView, move all my current view's subviews to the new view, and install it in place. This works, but it's custom code that I have to write every time I want a translucent view.
I'm thinking this might be possible using an NSProxy object. Here's what I'm thinking:
Define a custom subclass of NSView (let's call it MyTranslucentView). In all the init methods (initWithFrame and initWithCoder) I would throw away the newly created object and instead create a subclass of NSProxy that has a private instance variable (myActualView). At init time it would decide to create the myActualView object as an NSVisualEffectView if OS>=10.10, and a normal NSView under OS<10.10.
The proxy would forward ALL messages to it's myActualView.
This would be a fair amount of fussy, low-level code, but I think it should work.
Has anybody done something like this? If so, can you point me in the right direction or give me any pointers?
Apple is MUCH more open with the Beta agreement with Yosemite a than with previous Betas. I don't think I'm violating my Beta NDA by talking about this in general terms, but actual code using NSVisualEffectView would probably need to be shared under NDA...
There is a really simple, but somewhat hacky solution: Just dynamically create a class named NSVisualEffectView when your app starts. Then you can load nibs containing the class, with graceful fallback on OS X 10.9 and earlier.
Here's an extract of my app delegate to illustrate the idea:
AppDelegate.m
#import "AppDelegate.h"
#import <objc/runtime.h>
#implementation PGEApplicationDelegate
-(void)applicationWillFinishLaunching:(NSNotification *)notification {
if (![NSVisualEffectView class]) {
Class NSVisualEffectViewClass = objc_allocateClassPair([NSView class], "NSVisualEffectView", 0);
objc_registerClassPair(NSVisualEffectViewClass);
}
}
#end
You have to compile this against the OS X 10.10 SDK.
How does it work?
When your app runs on 10.9 and earlier, [NSVisualEffectView class] will be NULL. In that case, the following two lines create a subclass of NSView with no methods and no ivars, with the name NSVisualEffectView.
So when AppKit now unarchives a NSVisualEffectView from a nib file, it will use your newly created class. That subclass will behave identically to an NSView.
But why doesn't everything go up in flames?
When the view is unarchived from the nib file, it uses NSKeyedArchiver. The nice thing about it is that it simply ignores additional keys that correspond to properties / ivars of NSVisualEffectView.
Anything else I need to be careful about?
Before you access any properties of NSVisualEffectView in code (eg material), make sure that the class responds to the selector ([view respondsToSelector:#selector(setMaterial:)])
[[NSVisualEffectView alloc] initWithFrame:] still wont work because the class name is resolved at compile time. Either use [[NSClassFromString(#"NSVisualEffectView") alloc] initWithFrame:], or just allocate an NSView if [NSVisualEffectView class] is NULL.
I just use this category on my top-level view.
If NSVisualEffects view is available, then it inserts a vibrancy view at the back and everything just works.
The only thing to watch out for is that you have an extra subview, so if you're changing views around later, you'll have to take that into account.
#implementation NSView (HS)
-(instancetype)insertVibrancyViewBlendingMode:(NSVisualEffectBlendingMode)mode
{
Class vibrantClass=NSClassFromString(#"NSVisualEffectView");
if (vibrantClass)
{
NSVisualEffectView *vibrant=[[vibrantClass alloc] initWithFrame:self.bounds];
[vibrant setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
[vibrant setBlendingMode:mode];
[self addSubview:vibrant positioned:NSWindowBelow relativeTo:nil];
return vibrant;
}
return nil;
}
#end
I wound up with a variation of #Confused Vorlon's, but moving the child views to the visual effect view, like so:
#implementation NSView (Vibrancy)
- (instancetype) insertVibrancyView
{
Class vibrantClass = NSClassFromString( #"NSVisualEffectView" );
if( vibrantClass ) {
NSVisualEffectView* vibrant = [[vibrantClass alloc] initWithFrame:self.bounds];
[vibrant setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
NSArray* mySubviews = [self.subviews copy];
for( NSView* aView in mySubviews ) {
[aView removeFromSuperview];
[vibrant addSubview:aView];
}
[self addSubview:vibrant];
return vibrant;
}
return nil;
}
#end
I'm using an NSViewController class with a single view in it to display a progress indicator bar and some text fields. I'm trying to use progressIndicator setMaxValue:and theTextField setStringValue: but neither of these are doing anything.
I've done this before and I've checked and rechecked, it's fairly straightforward, the fact that it's not working makes me think that it has to do with the fact that the class is NSViewController. Which is why I tried
Timers *aTimer = [[Timers alloc] init];
[aTimer.timerNameLabel setStringValue:#"name"];
[aTimer.progressIndicator setMaxValue:x];
in the app delegate which is an NSObject class, but that didn't work either.
I've tried looking around the NSViewController documentation but I can't find anything that says it can't set those values so I don't know what's happening. What am I doing wrong?
You probably want to use -initWithNibName:bundle: instead of a regular init to initialize your custom nib.
EDIT: It seemed the problem was due to the view not being queried before getting other objects. By calling [myController view] you actually load the nib, which isn't done automatically when you initialize the view controller. So before you can use any element of the view, you need to call [myController view]