SplitView in Storyboard, can't connect delegate - delegates

I have an iPad interface defined in a storyboard, with a SplitViewController. I want to set the delegate of the SplitViewController to point to the detail controller, but IB won't allow that connection to be made.
Putting an "object" object and changing the class to that of my detail controller makes the delegate point to a different instance of the detail controller than the one displayed.
My attempts to connect it manually (in didFinishLaunching) in the app delegate were fruitless, the delegate was still nil.
Changing the delegate to the AppDelegate and implementing the protocol there worked, but passing messages to the detail controller didn't work (even though I verified that I was referencing the same instance).
Nothing works. I can set self.splitViewController.delegate = self when detail view loads, and then it becomes the delegate with no problem. But I miss any delegate calls that happen before that point.
For context, I need to get the splitviewcontroller's delegate calls so that I can show a button in the detailview to show a popover, as is standard practice. Right now, if the app is launched in portrait, it has to be rotated to landscape and then back for the button to show.
I've done this with "normal" xibs with no issue. It seems to be a special case of the more general problem of not being allowed to connect outlets across scenes in a storyboard.

I have no idea why this worked this time and not the first 80 times, but I added this to my AppDelegate:
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
splitViewController.delegate = (id)navigationController.topViewController;

Related

NSToolbar in Xcode 7 using Storyboards (NSWindowController -> NSSplitViewController)

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;
}

Swift class to interact with Interface Builder via IBOutlet

Good evening all,
I'm slowly working through my first OS X app. I have been having a hard time getting my Swift class to interact with an NSPopUpButton. Just to make sure I was doing it right, I created a new project and successfully erased all entries and entered text into the NSPopUpButton via AppDelegate. However, as soon as I try to move the same functionality to my own class, I can't even get the IBOutlet connection across to the new class.
Is a particular subclass type required of a new class to work properly with interface builder?
Here is a screenshot of the class I have created, as well as AppDelegate where I am trying to call the function belonging to this class.
Finally, here is the IB element in question, should I be able to select my own class under the 'Custom Class' inspector?
I am an iOS developer, but I would imagine the same principles would apply to your situation.
A ViewController class and an interface created in interface builder are two seperate things. While it may appear that they are connected via an iboutlet, they are actually independent and one can be instantiated without the other.
Currently, you are only creating an instance of your ViewController class in your App Delegate - and that's all. The system has no idea that your xib even exists! The outlets will only be connected once your app connects your xib to your ViewController class.
How do we do this? It's actually quite simple. Instead of instantiating our view controller like this:
let viewcontroller = ViewController()
We would connect our view controller to our xib in the instantiation:
let viewcontroller = ViewController(nibName: "MainWindow", bundle: NSBundle().mainBundle)
The nibName is telling the system the file name of your xib, and the NSBundle().mainBundle is telling the system where to look for the xib.
This will all only work if your xib has been assigned a custom class, like you mentioned. In your xib in interface builder, select the entire view controller. Then, in the custom class inspector type in the name of your ViewController class (in your case: ViewController - it should autocomplete). This will make sure your outlets are connected.
And you should be set up!! Let me know if you have any more problems come up.
Good luck!
EDIT:
This replaces the first part of my answer, however the part about hooking things up in Storyboard remains true. Upon reconsidering, I've believe I've realized that we are only creating the view controller, and not adding it to our view. Despite this, I believe we can take a short cut solution by adding one method to your view controller subclass (the one we set in the Storyboard). Start typing in viewDidLoad, and it should autocomplete. Type in super.viewDidLoad() at the beginning of the method. After that, type self.listUpdate(). This should work if the classes are hooked up correctly in Storyboard. This means you can delete the variables you created in the App Delegate.
Reference: You might also find Apple's documentation on creating a view controller handy (it's in Objective C online, but can be easily converted to Swift - it's the concept that counts): NSViewController Class Reference

NSWindow tracking

I would like to track each time a certain window appears (becomes visible to the user) in a OS X app. Where would be the most adequate place to call the tracker?
windowWillLoad, maybe?
I expected to find something like windowWillAppear but it seems I'm thinking too much iOS.
How about getting notification such as NSWindowDidBecomeMainNotification, By main I guess the one which is top most on screen directly visible by user.
see : Apple Documentation
Yes, one would expect that a window would notify its delegate or its controller with a windowWillAppear or windowDidAppear message, or post a documented notification like NSWindowDidAppearNotification. But alas, none of those exist. I filed a bug report with Apple and was given the advice to use a storyboard and a view controller instead. This is unhelpful in legacy apps that already use a bunch of window controllers and xibs.
You could subclass NSWindow and override orderWindow:relativeTo: to send a notification. Most, but not quite all, of the messages that make a window show itself ultimately go through this method, including orderBack:, orderFront:, makeKeyAndOrderFront:, and -[NSWindowController showWindow:]. But orderFrontRegardless does not go through orderWindow:relativeTo:, so you would also want to override that for completeness.
Another way to be notified is to make a subclass of NSViewController that controls some view that's always visible in the window. The view controller will receive viewWillAppear and viewDidAppear.
If you're subclassing NSWindow or NSViewController already for some other reason, either of these is a reasonable solution.
If you're not subclassing NSWindow already, and don't have an NSViewController subclass for a view that's always visible in the window, then another way is to use Cocoa bindings to connect the window's visible binding to a property one of your objects. For example, I have a custom NSWindowController subclass. I gave it a windowIsVisible property:
#interface MyWindowController ()
#property (nonatomic) BOOL windowIsVisible;
#end
and I implemented the accessors like this:
- (BOOL)windowIsVisible { return self.window.visible; }
- (void)setWindowIsVisible:(BOOL)windowIsVisible {
NSLog(#"window %# became %s", self.window, windowIsVisible ? "visible" : "hidden");
}
and in awakeFromNib, I bind the window's visible binding to the property like this:
- (void)awakeFromNib {
[super awakeFromNib];
[self.window bind:NSVisibleBinding toObject:self withKeyPath:NSStringFromSelector(#selector(windowIsVisible)) options:nil];
}
When the window becomes visible, the setWindowIsVisible: setter is called with an argument of YES. Note that if the whole app is hidden and reappears, the setter is called again, even though it wasn't called with argument NO when the app was hidden. So be careful not to assume the window was previously hidden.
Also, the binding might create a retain cycle, so you should probably unbind it when the window is closed, unless you want to keep the window and controller around. Note that the window does post NSWindowWillCloseNotification when it's closing, so you don't need any special magic to detect that.

Keep leftBarButtonItems on new UIViewcontroller when pushed

I have a UIViewController embedded in a UINavigationController. The rootViewController now contains already some buttons as leftBarButtonItems.
Now when I push a new UIViewController on top of the UINavigationController I want the new UIViewController to keep the existing leftBarButtonItems extended with the Back-Button.
Right now the situations is as follows: When I push the new UIViewController then the existing leftBarButtonItems disappear and only the Back-Button is visible.
Each UIViewController has it's own "navigationItem" property, which acts as the navigation bar representation for that viewcontroller. When you add buttons to the navigationItem of a particular UIViewController they are limited in scope to the viewcontroller to which they were added, and they don't persist into other viewcontrollers.
Basically, you'll have to add the buttons to the navigationItem of each viewcontroller as it loads. You can make this simpler by writing adding a method to do this work to a class other than your UIViewControllers. What happens when you touch each button might be viewcontroller specific though, so you'll have to think through how touch actions will be fed back to the relevant viewcontroller. Perhaps introduce some kind of NavigationBarDelegate protocol or something?
I found what seems like a hacky way to get around this when pushing multiple instances of the same view controller on to a detail view controller which I assume would work similarly. Before pushing the new view controller I used this: (browser is my new view controller)
self.browser.navigationItem setLeftBarButtonItem:self.detailViewController.navigationItem.leftBarButtonItem animated:YES]; // Sets popover view controller button.
[self.detailViewController.navigationController pushViewController:self.browser animated:YES];
This probably isn't a good way to do it but it seems to work in my situation.

View is null in viewDidLoad

I created UITabBarController with each tab contain UINavigationController and set rootviewcontroller in this UINavigationController, all of this is done in interface builder. In viewDidLoad I try to get frame size from view, but when I reference view it return null. Have anyone experienced this before.
All IBoutlet are properly connected.
viewDidLoad returning a nil view if the view is connected in your .xib could mean:
For programmatic initialisation (custom controllers):
You forgot to call initWithNibName:bundle: on the view controller class altogether (you may have called init or something instead).
You've overridden the loadView method in your view controller class, but it doesn't set the view.
For all controllers:
An outlet connection hasn't been made correctly.
You have accidentally released the view or view controller before showing it.
The nibName parameter was not properly specified when initialising the view (the nib could not be found or one without a view was found.. though this should also throw an exception).
There wasn't enough memory to allocate the view (the app would likely have been terminated by that point though).
I'd recommend you try doing frame size calculations in viewWillAppear: instead and see if the view is still nil at that point.

Resources