Since UIViewController navigationItem outlets are deprecated (yes, I'm a bit behind), what is the correct idiom specifying the elements of a UINavigationItem?
I've seen several approaches suggested, but none are entirely clear to me:
"Embed the navigation item in the view controller" in the view controller's XIB.
Initialize the navigation item's properties in code, with from-scratch values.
Create an outlet for the navigation controller in the view controller, wire up a navigation item in the view controller's XIB, and use its properties to initialize the (actual) navigation item's properties in code.
It isn't clear to me how to "embed" the navigation item (simply adding it as a child of the view controller in IB has no effect); and it isn't clear to me which of these approaches is better, or, for that matter, where (in what method) to do 2 or 3.
1) If controller is created in XIB you can drop UINavigationItem on it and tweak this item - it will work. For example when you're defining UINavigationControler in XIB you can put some controller inside as root view controller. So you can have UINavigationItem for this controller in XIB.
2) If controller loads it's view from XIB (it was created by alloc and then init or initWithNibName:bundle:) it's presented in XIB only as File's Owner which doesn't support UINavigationItem in it. In this case you should configure navigation item in code (I do it in viewDidLoad usually). There is no need to create it, it's already there in navigtionItem property of your controller
self.navigationItem.title = #"Price List";
Maybe it's possible to make outlet for navigation item but I wouldn't recommend this. Apple declared such outlet obsolete for a reason. I remember discussing this with co-worker one day, but I forgot what it was (it was obvious then).
On our project we have a requirement to make UI be as customizable from IB as possible.
So, I add UINavigationItem to xib, configure it, link it as outlet to my custom UIViewController subclass, and then copy all properties at runtime with method added to UIViewController using category:
- (void)setNavigationItemPropertiesFromOtherItem:(UINavigationItem *)navItem
{
// WORKAROUND: we can't link UINavigationItem to UIViewController from IB, and navigationItem property in UIViewController is readonly
self.navigationItem.title = navItem.title;
self.navigationItem.prompt = navItem.prompt;
self.navigationItem.hidesBackButton = navItem.hidesBackButton;
if (navItem.backBarButtonItem != nil)
{
self.navigationItem.backBarButtonItem = navItem.backBarButtonItem;
}
if (navItem.leftBarButtonItem != nil)
{
self.navigationItem.leftBarButtonItem = navItem.leftBarButtonItem;
}
if (navItem.rightBarButtonItem != nil)
{
self.navigationItem.rightBarButtonItem = navItem.rightBarButtonItem;
}
if (navItem.titleView != nil)
{
self.navigationItem.titleView = navItem.titleView;
}
}
This workaround also allows to link bar button items to UINavigationItem using IB.
Related
I am trying to add an outlet into my viewcontroller for a toolbar item in my window controller. I have tried playing around with first responder and bindings but have not been able to find any solutions.
A similar question that was answered provided some insight but no one has mentioned anything about IBOutlets other than still asking how to add them in the comments. The answer has been accepted so i am assuming no one will add to it.
How to use NSToolBar in Xcode 6 and Storyboard?
Incase my question is unclear at all, i would like to be able to add this to my storyboard program
#IBOutlet weak var Mytoolbar: NSToolbarItem!
func enabletoolbar()
{
Mytoolbar.action = "FunctionIn.ViewController.swift"
Mytoolbar.enabled = true
}
I found a decent workaround by adding IBOutlets to my custom NSWindow class and using the storyboard to connect my views to the IBOutlets. Then, I accessed these views from my NSViewController class by getting them from the custom NSWindow.
Basically you need to set the action and other properties to the toolbaritem but not in the toolbar. So try the same.
i ended up doing this in my view controller which seems to work
override func viewDidLayout() {
var x = self.view.window?.toolbar?.items[1].label
println(x)
if(self.view.window?.toolbar?.items[0].label! != "Check")
{
toobarediting()
}
println("didlay")
}
func toobarediting() {
self.view.window?.toolbar?.insertItemWithItemIdentifier("Check", atIndex: 0)
}
func toolbarcheck(functiontoset: Selector) {
var y = self.view.window?.toolbar?.items[0] as NSToolbarItem
y.action = functiontoset
if(functiontoset != nil)
{
y.enabled = true
}
}
It seems to allow me to make the tool bar button clickable/unclickable when ever i require it to change it just seems so much more bulky and error prone than
myitem.enable = fale
myitem.action = nil
is this really the best way for a storyboard based application in osx?
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;
}
I'm using an NSCollectionView to display various objects. The whole things works rather well, except for one annoying thing. I cannot figure out how to access the various controls on the view used to represent each object in the collection.
Here's the setup:
I have dragged an NSCollectionView into my view in IB.
I made a custom subclass of NSCollectionViewItem. Mapped my class in IB.
I made a custom subclass of NSBox to act as the view for each object in the collection. Also mapped this class in IB and connected it to the view property of my NSCollectionViewItem subclass.
I made all the bindings in IB to display the correct information for each object.
The view:
The resulting collection view:
Reasoning that that my subclass of NSCollectionViewItem is basically a controller for each view in the collection, I made referencing outlets of the various controls in the view in my controller subclass:
#interface SourceCollectionViewItem : NSCollectionViewItem
#property (weak) IBOutlet NSTextField *nameTextField;
#property (weak) IBOutlet NSTextField *typeTextField;
#property (weak) IBOutlet RSLabelView *labelView;
#property (weak) IBOutlet NSButton *viewButton;
#end
When I inspect any instance of SourceCollectionViewItem in the debugger, all the properties show up as nil despite the fact that I can actually see them on my screen and that everything is displayed as it should be.
My setup was inspired by Apple's sample app IconCollection.
I am obviously missing something. What?
EDIT: I found various posts hinting at a similar issue:
CocoaBuilder.com and this question on SO.
EDIT: Just to be complete: this post deals with the subject as well and delivers a solution based on a combination of the options mentioned in the accepted answer.
Outlets are set during nib loading, and only the prototype item is loaded from nib and has its outlets assigned. All other ViewItems and their Views are cloned from the prototype, in that case outlets are just instance variables that are never initialized.
Here are the options I could come up with:
Override newItemForRepresentedObject: of collection view and reload nib instead of cloning the prototype. But this will probably hurt the performance greatly.
Override copyWithZone of collection view item and assign outlets manually using viewWithTag: to find them.
Give up and try to provide data via bindings only.
I found that overriding NSCollectionViewItem's -setRepresentedObject: could also be a good choice, as it is called on the new Item when all IBOutlet seem to be ready. After the call to super you can do whatever is needed:
- (void)setRepresentedObject:(id)representedObject
{
if (representedObject) {
[super setRepresentedObject:representedObject];
[self.anOutlet bind:#"property" toObject:self.representedObject withKeyPath:#"representeProperty" options:nil];
}
}
I used this method to bind a custom property of an interface object. The check is there to avoid useless calls, when the representedObject is not yet ready. The project uses a separate xib for the ViewItem, as explained in the links in the original edits.
Great question. Like #hamstergene suggests, you can use copyWithZone, it will be much more efficient compared to newItemForRepresentedObject. However viewWithTag is not always an option, first, because not everything can be tagged (easily), and, second, using tag for this purpose is a little wrong. Here's a cool approach with performance in mind, in Swift.
import AppKit
class MyViewController: NSCollectionItemView
{
// Here you are cloning the original item loaded from the storyboard, which has
// outlets available, but as you've seen the default implementation doesn't take
// care of them. Each view has a unique identifiers, which you can use to find it
// in sublayers. What's really cool about this, is that you don't need to assign
// any tags or do anything else while having advantage of better performance using
// cached nib object.
override func copyWithZone(zone: NSZone) -> AnyObject {
let copy: NSCollectionItemView = super.copyWithZone(zone) as! NSCollectionItemView
let oldView: RecordingView = self.view as! MyView
let newView: RecordingView = copy.view as! MyView
newView.foo = newView.viewWithIdentifier(oldView.foo.identifier!) as! NSTextfield
newView.bar = newView.viewWithIdentifier(oldView.bar.identifier!) as! NSImageView
return copy
}
}
#IBDesignable class MyView: View
{
// Custom collection view item view. Lets assume inside of it you have two subviews which you want
// to access in your code.
#IBOutlet weak var foo: NSTextfield!
#IBOutlet weak var bar: NSImageView!
}
extension NSView
{
// Similar to viewWithTag, finds views with the given identifier.
func viewWithIdentifier(identifier: String) -> NSView? {
for subview in self.subviews {
if subview.identifier == identifier {
return subview
} else if subview.subviews.count > 0, let subview: NSView = subview.viewWithIdentifier(identifier) {
return subview
}
}
return nil
}
}
I am looking at rebuilding the settings section of my app using the new functionality provided by storyboards. Not wanting to touch the rest of my app at this point, so my main NIB will be staying.
Now when going from my NIB'ed tabBar to another NIB I just add a viewController to the tabBar in IB and then set the NIB Name property to the NIB which I want to load when that tab is pressed.
But there is no 'storyboard name' property that I can see, so how is this done?
There is no "official" way to do it at the moment, but you can do it using some tricks.
1) add your view controller to your tabbar in nib in the usual way. Leave the nib field empty.
2) create your storyboard and add your viewcontroller. Set the class and set a storyboard ID (I'll use "theID" for this example)
3) add a static bool var to your .m file, outside implementation or interface
static BOOL aFlag = NO;
4) in your viewcontroller class override this method:
- (id) awakeAfterUsingCoder:(NSCoder *)aDecoder
{
if (!aFlag){
aFlag = YES;
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
return [storyboard instantiateViewControllerWithIdentifier:#"theID"];
} else {
return self;
}
}
essentially:
when you load the object from the tab bar nib, a first call to "initWithCoder" is made, and the object loads without nib
after initWithCoder, awakeAfterUsingCoder is called and there you substitute the object with another loaded from storyboard. Object of the same class but archived in the storyboard
when you load the object from the storyboard, another call to both initWithCoder and awakeAfterUsingCoder. You use the flag to avoid a loop and return self (at the second call, the object is loaded from storyboard so returning self is ok)
I tried and it works good ;-)
If you want here is an example project: http://www.lombax.it/files/testTabNib.zip
I have a UIViewController with an xib and using Interface Builder I've added a child UIView.
Within the child UIView, when I click on an object within that view, I want to be able to alter the title of the whole window.
Now I'd normally do that setting
self.title = #"hi";
on the parent UIViewController. But is there any way I can access the parent title from within the child?
I've tried
self.superview.title = #"i";
self.parentViewController.title = #"hi";
but neither work.
Any help much appreciated
thanks
self.superview.title = #"i"; evaluates to an object of type UIView, and UIView has no title property. UIViewControllers have a parentViewController property but UIViews don't.
So the fundamental problem is that you're not properly separating your controller and your view classes. What you'd normally do is make the view you want to catch taps on a subclass of UIControl (which things like UIButton already are, but if it's a custom UIView subclass then you can just change it into a UIControl subclass since UIControl is itself a subclass of UIView), then in your controller add something like:
- (void)viewDidLoad
{
[super viewDidLoad];
// we'll want to know if the view we care about is tapped;
// we've probably set up an IBOutlet to it but any way of
// getting to it is fine
[interestingView
addTarget:self
action:#selector(viewTapped:)
forControlEvents:UIControlEventTouchDown];
// UIButtons use UIControlEventTouchUpInside rather than
// touch down if wired up in the interface builder. Pick
// one based on the sort of interaction you want
}
// so now this is exactly like an IBAction
- (void)viewTapped:(id)sender
{
self.title = #"My new title";
}
So you explicitly don't invest the view with any knowledge about its position within the view hierarchy or how your view controllers intend to act. You just tell it to give you a shout out if it receives a user interaction.
I have a Document based core data app. The main document window has a number of views, each controlled by its own custom NSViewController which are switched in as necessary. I want each of these view controllers to be able to drop down a custom modal sheet from the document window. However because the views are separate and not in the MyDocument nib I cannot link the view to the document window in IB. This means that when I call
[NSApp beginSheet: sheetWindow modalForWindow: mainWindow modalDelegate: self didEndSelector: #selector(didEndSheet:returnCode:contextInfo:) contextInfo: nil];
I’m supplying nil for mainWindow and the sheet therefore appears detached.
Any suggestions?
Many Thanks
You can use [[self view] window]
Indeed, it's self.view.window (Swift).
This may be nil in viewDidLoad() and viewWillAppear(), but is set properly by the time you get to viewDidAppear().
One issue with the other answers (i.e., just looking at self.view.window) is that they don't take into account the case that when a view is hidden, its window property will be nil. A view might be hidden for a lot of reasons (for example, it might be in one of the unselected views in a tab view).
The following (swift) extension will provide the windowController for a NSViewController by ascending the view controller hierarchy, from which the window property may then be examined:
public extension NSViewController {
/// Returns the window controller associated with this view controller
var windowController: NSWindowController? {
return ((self.isViewLoaded == false ? nil : self.view)?.window?.windowController)
?? self.parent?.windowController // fallback to the parent; hidden views like those in NSTabView don't have a window
}
}
If your controller can get access to the NSDocument subclass, you can use -windowForSheet
more about Tim Closs answer :
-(void)viewDidAppear
{
self.view.window.title = #"title-viewDidAppear"; //this only works when and after viewDidAppeer is called
}
-(void)viewWillDisappear
{
self.view.window.title = #"title-viewWillDisappear"; //this only works before and when viewWillDisappear is called
}