Equivalent Storyboard function for Nib - objective-c

I'm trying to convert a project on macOS that uses Storyboards to instantiate a ViewController through a delegate, although I'm running into some difficulty trying to convert it to use a Nib instead.
Currently the storyboard version of the code uses an App Delegate which is associated with two View Controllers. When a button is clicked the front window animates and flips over revealing another (back) window. The code that instantiates the View Controller is:
mainWindow = [NSApplication sharedApplication].windows[0];
secondaryWindow = [[NSWindow alloc]init];
[secondaryWindow setFrame:mainWindow.frame display:false];
// the below is what I'm not sure of - how to reference nib instead of storyboard?
NSStoryboard *mainStoryboard = [NSStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]];
NSViewController *vc = [mainStoryboard instantiateControllerWithIdentifier:#"BackViewController"];
[secondaryWindow setContentViewController:vc];
I'm not sure the proper way to reference a nib instead of a storyboard in the example shown above. The project I'm trying to convert is located here. I'm really hoping someone can help, thank you!

This is pretty easy to do. Just make an NSViewController subclass (or an NSWindowController subclass if you want it to control a whole window) for each of the two views. For each view, override -init and have it call super's implementation of -initWithNibName:bundle: with the name of the view's nib file:
#implementation MyViewController
- (instancetype)init {
self = [super initWithNibName:#"MyViewController" bundle:nil];
if (self == nil) {
return nil;
}
return self;
}
Note that if you can require a sufficiently recent version of macOS (I think it's 10.11 and higher off the top of my head, but it's possible that I could be off by a version or so), you don't even have to do this much, because NSViewController will automatically look for a nib file with the same name as the class.
Anyway, now you should be able to instantiate a MyViewController and insert its view into your view hierarchy, and manipulate it the same way you'd manipulate any other view:
MyViewController *vc = [MyViewController new];
[someSuperview addSubview:vc.view];
If you want to do windows instead, you can make an NSWindowController subclass instead of NSViewController. NSWindowController is slightly more annoying to use, since its initializers that take nib names are all convenience initializers, whereas the designated initializer just takes an NSWindow. So if you're using, say, Swift, you can't do it the way I did it above with NSViewController. Objective-C, of course, generally lets you do whatever you want, so you actually can get away with just calling super's -initWithWindowNibName:owner:, and I won't tell anyone, wink wink, nudge nudge. However, to be stylistically correct, you probably should just call -initWithWindow: passing nil, and then override windowNibName and owner:
#implementation MyWindowController
- (instancetype)init {
self = [super initWithWindow:nil];
if (self == nil) {
return nil;
}
return self;
}
- (NSNibName)windowNibName {
return #"MyWindowController";
}
- (id)owner {
return self;
}
This should get you a window controller that you can just initialize with +new (or +alloc and -init if you prefer), then call its -window property and manipulate the window as normal.

Related

initWtihFrame: method for custom textfield not being called when created in a TableCellView

So I'm trying to create a custom NSTableView by subclassing NSTableCellView and a NSTextField inside of the cellview. I'm trying to write some init code in the initWithFrame: method for the NSTextField subclass (TableTextField), but it looks like the initWithFrame: method isn't being called at all, even when I create new rows for the NSTableView, which each contain an instance of the TableTextField. Here's the code in the TableTextField.m file:
#import "TableTextField.h"
#implementation TableTextField
- (id)initWithFrame:(NSRect)frame//This isn't even being called.
{
self = [super initWithFrame:frame];
if (self) {
self.ad = [[NSApplication sharedApplication] delegate];
}
return self;
}
- (void)mouseDown:(NSEvent*) theEvent{
NSLog(#"A");//TEST
self.ad = [[NSApplication sharedApplication] delegate];//This works. Why isn't it happening in init?
[self.ad.classViewController addHomeworkItem];
}
#end
I've been able to solve the problem by just putting the code i need in the mouseDown: method, which is really the only thing I need, but I feel like it's bad practice to redeclare self.ad every time i need to use it, rather than just declaring it once and accessing it for each use, and I can't seem to figure out why initWithFrame: isn't being called. I'm assuming it has to do with the way objects inside NSTableCellViews are initialized, but I haven't found a real explanation. Any suggestions on how to solve this, or explanations as to why it's happening would be welcome. Thanks!
Here's an image of how the NSTableView is set up (with 3 rows):
If you are using XIB files to layout the views, the initialiser method being called is initWithCoder:

Xcode: Document-based app window is not loading during attempt to subclass NSWindowController

I really haven't done much and i'm already stuck.
So far i've done:
added NSWindowController subclass (MikesWindowController.h & .m)
removed windowNibName from MikesDocument.m (since i'm implementing my own
WindowController subclass.)
I tried:
Tested if NSLog would come back at init, windowControllerDidLoadNib, applicationDidFinishLaunching. Only the NSLog at init printed.
And, tested the Main Menu -> File -> New after after compiling my Document app.
Am I implementing this right? Thanks. Any suggestions would be great! Under MikesDocument.m
-(void)makeWindowControllers{
MikesController *controller = [[MikesWindowController alloc]init];
[self addWindowController:controller];
}
After much deliberation I found that answer. Woohoo. Enjoy future.
removed initWithWindow from my NSWindowController subclass
implemented initWithWindowNibName to my NSWindowController subclass so now anytime I initialize I must specify the window nibName.
Below I implemented initWithWindowNibName in my NSWindowController subclass heres what it looks like:
MikesWindowController.m
-(id) initWithWindowNibName:(NSString *)windowNibName{
self = [super initWithWindowNibName:windowNibName];
return self;
}
(Below) Back again to the main document I corrected the makeWindowController method and instantiated my controller with "MikesDocument" (for MikesDocument.xib) and added it.
MikesDocument.m
-(void)makeWindowControllers{
MikesWindowController *controller =
// must tell controller which nib file to use.
[[MikesWindowController alloc]initWithWindowNibName:#"MikesDocument"];
[self addWindowController:controller];
}
Success! Don't even bother calling init or implementing init as it returns an error at any time.

UIViewController: [super init] calls [self initWithNibName bundle:]

I have two init functions in my UIViewController subclass:
- (id)init
{
self = [super init];
if (self)
{
// Custom stuff
return self;
}
return nil;
}
and
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName: nibNameOrNil
bundle: nibBundleOrNil];
if (self)
{
// Custom stuff
}
return self;
}
I put the init function in to avoid the call to the initWithNibName:bundle: method. I am trying to experiment with taking the xib file out. Unfortunately, calling this init [[Myclass alloc] init] calls initWithNibName:bundle: through the call to [super init].
First, where in the documentation should I be reading so that I would have expected the call to the parent init method to call my own initWithNibName:bundle: method?
Second, how is this a good design choice on Apple's part. I am not seeing why this is desirable behavior? (It may be that I am just not getting the big picture here so please feel free to clue me in.)
Third, how do I get around it best. Do I just take the initWithNibName:bundle: out of my code? Is there never a case where I would like the option of using either a xib or a manual instantiation of the class.
Usually I have to initialise my view controllers with managed object context. I implement simple -(id)initWithContext: method in which I call super's initWithNibName:bundle: method. This way I can define my own xib name.
Not sure about the first part of you question (the reading thing, that is), but Apple's class templates for VC's show that they have their own initWithNibName:bundle method which calls on super with same parameters as they are given. Hence from your situation I'd say that exactly this is designated initialiser and it's not "safe" to call simple init method on super as it will invoke initWithNibName:bundle. I believe UIViewController's init looks like this:
- (id)init
{
self = [self initWithNibName:nibNameDerivedFromClass bundle:probablyNilOrMainBundle];
if (!self) return nil;
// some extra initialization
return self;
}
Since the super class doesn't have initWithNibName:bundle it has to call method on itself making it the designated initialiser. Since you have overridden it, ObjC's runtime replaces self in that method with your class.
If you want to exclude Interface Builder from the creation of your UIViewController's GUI, you have to override loadView and create the view yourself. Don't implement initWithNibName:bundle:.
- (void)loadView {
// Init a view. The frame will be automatically set by the view controller.
UIView *view = [[UIView alloc] initWithFrame:CGRectZero];
// Add additional views (buttons, sliders etc.) to your view here.
// Set the view controller's view to the new view.
self.view = view.
}
First, where in the documentation should I be reading so that I would
have expected the call to the parent init class to call my own
initWithNibName:bundle: method?
You don't, it's a design thing. init is not the base initialization method for all classes
Second, how is this a good design choice on Apple's part. I am not
seeing why this is desirable behavior? (It may be that I am just not
getting the big picture here so please feel free to clue me in.)
When init calls that, it sends nil name and bundle, and it defaults to an empty xib file. There's always a xib file, yours or not.
Third, how do I get around it best. Do I just take the
initWithNibName:bundle: out of my code? Is there never a case where I
would like the option of using either a xib or a manual instantiation
of the class.
You don't. You don't really need to have that code there if you are just calling super, merely forwarding a method.
You can customize your view and add subviews in the viewDidLoad method. In this method you can check whether the class was created using init or using initWithNibName:bundle: by examining property nibName. When using init, nibName will be nil.
- (void)viewDidLoad
{
[super viewDidLoad];
if (!self.nibName) {
// View was not loaded from nib - setup view
}
}

How to present a view controller from another view controller

I am trying to open a ViewController from within another ViewController if certain conditions are met. The code seems to run without error but the view is never shown. I am new to xcode 4 /ios 5 so I must be missing something.
Here is the code responsible for opening the second viewcontroller:
CreateUserViewController *createUserController = [[CreateUserViewController alloc] initWithNibName:#"CreateUserView" bundle:[NSBundle mainBundle] keyWrapper:keyChainWrapper];
[self presentViewController:createUserController animated:YES completion:nil];
In my project I have a xib called, "CreateUserView". I have added a view controller to this xib and assigned it to, "CreateUserViewController".
Also I noticed in the apple documentation that is shows setting the delegate of the viewcontroller to be presented. But it seems that no property called, "delegate" is on the viewcontroller object. Is this documentation old? This is the document I am trying to use (section 9-1):
View Controller Programming
Can someone give me a hint? Thanks..
edit Adding Custom Constructor
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil keyWrapper:(KeychainItemWrapper *)keyWrapper
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if(self){
[self setKeyChainWrapper:keyWrapper];
}
return self;
}
Regarding CreateUserView.xib: you don't want to put a CreateUserViewController object in the nib. You want to set the custom class of the File's Owner placeholder to CreateUserViewController. Then you need to connect the view outlet of File's Owner to the top-level view in the nib.
Regarding the delegate property: The UIViewController class doesn't have its own delegate property. The idea is that you add a delegate property to your subclass of UIViewController. The delegate provides a way for your presented view controller to pass custom information back to the presenting view controller.
Why would you want to do that? Let's consider the code you posted. I'll assume you have a UserListViewController that shows a list of User objects, and has a "Create new user" button. When the user touches the "Create new user" button, you create a CreateUserViewController and present it.
The user interacts with the CreateUserViewController to set the attributes of the new User object - name, rank, hairstyle, etc. Then he touches a "Done" button. Your CreateUserViewController creates the new User object and puts it in the database. Then it needs to dismiss itself, so the UserListViewController's list of User objects will appear again.
But you want the User list to include the newly created User object and you want to scroll the list so that the new User is on the screen. So you need a way to have your CreateUserViewController tell the UserListViewController about the newly created User object. This is where the delegate comes in.
You define a protocol like this:
#protocol CreateUserViewControllerDelegate
- (void)didCreateUser:(User *)user;
#end
and you give your CreateUserViewController a delegate property:
#interface CreateUserViewController
#property (weak, nonatomic) id<CreateUserViewControllerDelegate> delegate;
// ...
When your CreateUserViewController's "Done" button is touched, you notify your delegate of the new User:
- (IBAction)doneButtonWasTouched:(id)sender {
User *user = [self createUser];
[self.delegate didCreateUser:user];
[self dismissViewControllerAnimated:YES completion:nil];
}
In your UserListViewController, you adopt and implement the protocol:
#interface UserListViewController <CreateUserViewControllerDelegate, UITableViewDelegate, UITableViewDataSource>
// ...
#end
#implementation UserListViewController
- (void)didCreateUser:(User *)user {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[self.users count] inSection:0];
[self.users addObject:user];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation: UITableViewRowAnimationAutomatic];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition: UITableViewScrollPositionNone animated:YES];
}
and when you need to present a CreateUserViewController, you set the new controller's delegate to the UserListViewController:
- (IBAction)createUserButtonWasTouched:(id)sender {
CreateUserViewController *createUserController = [[CreateUserViewController alloc] initWithNibName:#"CreateUserView" bundle:[NSBundle mainBundle] keyWrapper:keyChainWrapper];
createUserController.delegate = self;
[self presentViewController:createUserController animated:YES completion:nil];
}
In iOS5 the method for pushing new view controllers was really changed around quite a bit from iOS4 and Xcode 3. In summary, storyboards are now used to create your application view controller flow. Even though you may use standalone .xib files to build an application it is much less common in iOS5.
Anyway, the main method for pushing new view controllers onto the screen is done using segues. Check out this tutorial for an introduction: http://www.raywenderlich.com/5138/beginning-storyboards-in-ios-5-part-1
It does a good job on explaining how to create a storyboard and use segues. You can still present view controllers in code "the old way" but it is much much less common now with the introduction of these new technologies. There are also some absolutely awesome tutorials on iTunes U - search for CS193P. It's the Stanford Introductory class to Objective-C and programming for iOS. This should get you started and maybe help you think of a way to push your createUserController in a way more up to speed with iOS5.
UPDATE
I just wanted to add. If you configure your program to use storyboards and segues you can use the method performSegueWithIdentifier:sender: to perform the segue to your createUserController view if the proper conditions are met. See the Apple API for UIViewController for information on how to use this method.

Why is this iPhone program not calling -loadView?

I am trying to work my way through basic iPhone programming and I have a good basic understanding of how Interface Builder works, so I decided to try my hand at doing the views programmatically. I have gone through the ViewController Apple guide and searched everywhere and I cannot seem to find a solution to my problem. This leads me to believe it is a very simple solution, but I am just really banging my head against the wall. Basically all I am trying to do is create a view that gets main window as a subview. I know that if self.view is not defined then the loadView method is supposed to be called, and everything is supposed to be set up there. Here is the current state of my code:
The delegate:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
StartMenuViewController *aViewController = [[StartMenuViewController alloc] init];
self.myViewController = aViewController;
[aViewController release];
UIView *controllersView = [myViewController view];
window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[window setBackgroundColor:[UIColor redColor]];
[window addSubview:controllersView];
[window makeKeyAndVisible];
}
The view controller:
- (id)init {
if (self = [super init]) {
self.title = #"Start Menu";
}
return self;
}
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
UIView *startView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
[startView setAutoresizingMask:UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth];
[startView setBackgroundColor:[UIColor greenColor]];
self.view = startView;
[startView release];
}
Thanks for the help in advance!
Are you sure that you're inheriting from UIViewController and not overriding the implementation of - (UIView*)view?
EDIT: More info:
UIViewController has a special implementation of the "-(UIView*) view" message so that when it's called, the loadView method is called if the view member variable is not set. So, if you provide an implementation of "- (id)view" in your subclass, (or a property named view) it will break the auto-calling of "- loadView".
Just to document a "loadView is not called" case:
I wrote a 2 UITableViewController(s) to handle detail data for a master ViewController. Since the devil was in #2, I made a simple UITableViewController for #1, and referenced it in the XIB for the "master" ViewController.
When I was done with #2, I could simply copy the code to #1, remove the complicated code, and go on with life.
But to my dismay and several days work, no matter what I did, viewLoad was not being called for my simple #1 UITableViewController.
Today I finally realised that I was referencing the UITableViewController in the XIB to the master ViewController program. - and of course, loadView was never being called.
Just to help some other dork that makes the same mistake....
Best Regards,
Charles
viewDidLoad only if the view is unarchived from a nib, method is invoked after view is set.
loadView only invoked when the view proberty is nil. use when creating views programmatically. default: create a UIView object with no subviews.
(void)loadView {
UIView *view = [[UIView alloc] initWithFrame:[UIScreen
mainScreen].applicationFrame];
[view setBackgroundColor:_color];
self.view = view;
[view release];
}
By implementing the loadView method, you hook into the default memory management behavior. If memory is low, a view controller may receive the didReceiveMemoryWarning message. The default implementation checks to see if the view is in use. If its view is not in the view hierarchy and the view controller implements the loadView method, its view is released. Later when the view is needed, the loadView method is invoked
again to create the view.
I would strongly recommend you use interface builder for at least your initial Window/View.
If you create a new project in XCode you should be able to select from one of many pre-defined iPhone templates that come with everything setup.
Unless I am reading this wrong, you did not associate any view with the the controller's view property like this
myViewController.view = controllersView;
So as far as Cocoa is concerned the view you are setting in the window has no controller to call loadView on. loadView is a View controller, not view, method. The view you assign to the window is not associated with any view controller. So your view controller loadView method is never called. Get it? The view you are trying to display, has no view controller associated with it.
When you use interface builder to create views you can link the UIView object you created in IB to the view property in the controller in IB which the framework automatically
But if not done in IB you have to set it