How to create multiple windows with the same nib file in xcode - iphone

I have an iphone app that uses table view as the interface. Each time that the user taps on one of the table cells, I want to show another window to the user. However the user interface of the window that I push into the navigation controller are extremely similar. Therefore I decided to make a "generic nib file" to be used across all the subclasses of the file owner of this generic nib file.
However what I'm confused (and what's not working) is that I can't seem to be able to customise this generic nib file. I have this code at the initialisation of the files:
In the .h file:
#import <UIKit/UIKit.h>
#import "primeViewController.h"
#interface subclass1 : primeViewController { //subclassing from a generic view controller that owns a generic nib file
}
In the .m file:
#import "subclass1.h"
#implementation subclass1
- (id) initWithLabelAndButton {
if(self = [super initWithNibName:#"primeViewController" bundle:nil]) {
self.label.text = #"Title of the first subclass";
}
return self;
}
But then when I instantiate the class in the table view:
//somewhere in rootviewcontroller.m:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.row) {
case 0:
{
checkPrime* checkPrimeViewController = [[checkPrime alloc] initWithLabelAndButton];
[self.navigationController pushViewController:checkPrimeViewController animated:YES];
[checkPrimeViewController release];
break;
}
default:
break;
}
}
So can anyone tell me what I'm doing wrong? Or am I wrong assuming that xcode allow me to use nib file multiple time across its subclasses? I don't see why I can't do it, but I can't figure out how...

When nib file is loaded, it creates view controller of exactly the same class that is written in .xib (UIViewController, or PrimeViewController, whatever else). What actually is saved in your .nib file?
You'll succeed if you will store UIView and all the corresponding objects in xib, and will load only them by using [[NSBundle mainBundle] loadNibNamed:#"YourNibFile" owner:class1ViewController options:nil], while view in nib is connected to the corresponding File owner outlet.

If you put all your views in one NIB, then when your application launches, it has to load the entire NIB into memory and construct all of the objects for all of the controls, and this takes a lot of time.
If you separate your views into separate NIBs, then your app will start up much faster, because only the NIB containing the initial view will be loaded. Then, when the view changes, you can load the NIB containing the new view. This will incur a minor hitch when opening a view for the first time.
Also if you're trying to optimize memory usage, you can unload the precious view when switching to a view

I'd suggest subclassing UITableViewController and add any methods, instance variables, and properties common to your different view here. Make your nib and have it have a reference to an instance of one of these objects.
Then, subclass your subclass to get customized functionality, like
GenericSubclassVC* checkPrimeViewController = [[SpecificSubclassVC alloc] initWithNibName:#"GenericNib" and Bundle:nil];
[self.navigationController pushViewController:checkPrimeViewController animated:YES];
You should stick to overriding the designated initializer. Put any custom initialization code into awakeFromNib or viewDidLoad methods, depending on if they need to modify UIView objects.
However, as others have mentioned, it's not terribly efficient or elegant. If all your ViewControllers are table view controllers and have the same data model to display, then there are other ways to get code reuse, like defining a datasource object.

Related

Calling a single view in different UIViewControllers

I have declared a UIView inside a UIViewController class. Can I call this UIView in another UIViewController class?
If its possible how can I call it?
Yes, you can use a single instance of a view in a number of views/viewControllers. Typically I do the same with Views that carry advertisements.
You pass them along as you would do with any other object.
If you do not create it in Interface Bulder (I suggest creating it programmatically) then you may want to define it within your application delegate rather than a view controller and pass it to the individual view controllers that make use of it.
Within the view controller just add it as sub view accordingly, as you would do it with any other view too.
There is one thing though. When you add this view to another super view for the second time or more then it will be removed from its prior super view. That means that you will have to add it as super view again, when its prior super view becomes visible again. A view can only be part of one view hierarchy at a time.
Sample code, thanks to Gordon:
/* Untested and simplified */
AppDelegate.h:
#property ( strong, nonatomic) ReuseableView reuseableView
;
AppDelegate.m
#synthesize reuseableView;
/* in didFinishLaunchingWithOptions ...*/
reuseableView = [[alloc] init]; // or init from nib, initwithframe, etc.
viewController.m
/* In each view controller that uses the view */
- (void) viewWillAppear:(BOOL)animated
{
[self.view addSubview:((AppDelegate*)[UIApplication sharedApplication].delegate).reuseableView];
}
- (void) viewWillDisappear:(BOOL)animated
{
[((AppDelegate*)[UIApplication sharedApplication].delegate).ReuseableView removeFromSuperview];
}
I am not quite sure whether this removeFromSuperview is really required. The next addSubview will remove it from its existing superview anyway and if addSubview is called on the same super view twice in a row then it does not do any harm. However, it is save using removeFromSuperview at this point.
Well, summarized that is basically it. You define, create and store your shared view (the reusableView in Gordon's example) at a common place. The application delegate is a good place for that. In each view's controller that uses the shared view you fetch it from the delegate and add it as subview to the current view.
I would subclass UIView and import it on the ViewControllers where I wanna use it
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:#"YourView" owner:self options:nil];
yourView = (YourView *)[nibArray objectAtIndex:0];
Then you set its frame and [self.view addSubview:yourView]

UITableViewController loading inside a UIViewController inside a UIViewController issue

I don't really know if what I'm doing is the right way to do it. Right now it seems to be working until it hits a certain point with a EXC_BAD_ACCESS message.
I'll describe what I'm doing as best and with the most relevant details I can tell:
I have a CalendarViewController that inherits UIViewController which is loading from a .xib file (CalendarViewController.xib). The class contains a UIView called contentView which I created and which I initialize with another nib file based on a class which is also inherited from UIViewController:
- (void)viewDidLoad {
[super viewDidLoad];
calendarView = [[CalendarView alloc] initWithNibName:#"CalendarView" bundle:nil];
[contentView addSubview:calendarView.view];
}
(calendarView is the class inheriting UIViewController and viewDidLoad is from CalendarViewController.
CalendarView.xib has a UITableViewController with it's respective UITableView. This Table View Controller is linked to a CalendarTableController to which I also generated a .xib file for it.
Everything is being created just right (apparently) but it is crashing somewhere very unexpected. CalendarTableController also implements a DateLoaderDelegate which loads information from an xml on an external url.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// When the data has all finished loading, we set a copy of the
// loaded data for us to access. This will allow us to not worry about
// whether a load is already in progress when accessing the data.
self.lastLoadedMatchXMLData = [self.matchXMLData copy];
// Make sure the _delegate object actually has the xmlDidFinishLoading
// method, and if it does, call it to notify the delegate that the
// data has finished loading.
if ([_delegate respondsToSelector:#selector(xmlDidFinishLoading)])
{
[_delegate xmlDidFinishLoading];
}
}
The application is getting to this point without any problem. _delegate is containing the correct object (a CalendarTableController which implements the DateLoaderDelegate). But when it arrives to the line:
if ([_delegate respondsToSelector:#selector(xmlDidFinishLoading)])
it crashes with the EXC_BAD_ACCESS, I don't really know the reason, if I look at the debugger, the crash is not occurring in any of my classes as any of them are appearing in the execution stack. URLConnectionClient seems to be generating it, but I don't really know why. The loading of the xml had worked earlier before I rearranged the ViewControllers to work as I have described now.
Any ideas?
It's weird. I fixed the problem but I had to dedicate the whole UIViewController to contain the UITableView. What I did was this:
Create an IBOutlet with the custom UITableViewController (CalendarTableViewController) in the custom UIViewController.
In the loaded .xib file I linked the IBOutlet to a created UITableViewController declared as a CalendarTableViewController.
This way I solved the problem of the UITableViewController being deallocated without reason. But now the image views I had placed in the intermediate UIViewController wouldn't appear. I had to set that UIViewController to contain solely the CalendarTableView and place the image views in the initial UIViewController. Weird, isn't it? I don't like much the hierarchy I created... but for now that will do =S
Check that you have defined properties for all of your subviews and that you are retaining everything that you need to be. Bad Access May mean that you're attempting to call respondsToSelector on an object that has been released.
Have you tried calling loadView before adding the nested controller's view to the parent's view?
Maybe viewDidLoad is not executing before you add the view and some variables were never initialized.

How do I get a view in Interface Builder to load a custom view in another nib?

I am making a custom widget that I would like to use in multiple nibs. So I make a new view nib Screen3, add some buttons, and now want my UIAwesomeSauce widget.
If I just add a view and then change the Class Identity, it doesn't get the subelements from the UIAwesomeSauce nib. Same thing if I go to the Library and switch to Classes. It seems only a UIViewController has the field for "Load from nib", which would be beautiful.
I know I can load the UIAwesomeSauce nib from code, get the top level objects, and place it by hand. But the point of IB is so you don't have to place things in code. Even better would be if I could get UIAwesomeSauce to show up in the Library list itself.
SOLVED - by Nimrod - READ ON FOR EXPLANATION AND CODE
Anyway, dood, that is great. I can make my own widget classes now for goofy Awesome stuff. Make the FileOwner of your UI nib your class, and in it just have a normal UIView with all your stuff. (The single view in the widget's nib can't be the class itself, or you get recursive in initWithCoder.) Then in the nib you want to use it, make a vanilla UIView and change its class. You won't be able to actually see the widget inside that square, but deal.
Self is now a blank view, and tMyActualSelf is the single view that you did the work in in the other nib. Yay!
- (id)initWithCoder:(NSCoder*)coder
{
if ((self = [super initWithCoder:coder]))
{
UIView *tMyActualSelf = nil;
// Initialization code
NSArray *tNibItems = [[NSBundle mainBundle] loadNibNamed:#"UIAwesomeSauce" owner:self options:nil];
for (id object in tNibItems)
{
if ([object isKindOfClass:[UIView class]])
tMyActualSelf = (UIView *)[object retain];
}
if( tMyActualSelf )
{
tMyActualSelf.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
[self addSubview:tMyActualSelf];
}
}
return self;
}
I feel your pain when it comes to the UIViewController being the only thing you can specify a nib load with, but I think it's because there has to be a File's Owner that's a controller, or something like that, though I'm not sure why a UIView can't be a File's Owner.
I think you can put things in the library under Custom Objects but I think you'd be limited to putting a UIView in there with the class just set to a custom subclass of UIView. I actually looked into trying to extend interface builder and it looks like you can do it for OS X stuff, but not for iPhone stuff. It involves compiling code for the new widget in interface builder to tell it how to draw the widget just within IB. I think there's actually an OS X template for this.
I think what I'd probably do is create a main view in a nib file that contains all the subviews, then in initWithCoder: in the UIAwesomeSauce thing open the nib file, load the main view, and just place it inside self so that you have an "unnecessary" view between the UIAwesomeSauce view and the subviews (or sub subviews as the case would be.) I'm not sure how inefficient that is, but in messing with some stuff it looks like some views like UIWebView seem to have one of these seemingly unnecssary intermediate views (IIRC).
If you come up with a better solution please tell me because I'd like to know too.
EDIT
Now that I look at it, I think the UIView CAN be the file's owner, you just can't set it up in IB. Basically you'd call the nib load something like this in the UIView subclass:
[bundle loadNibNamed:#"UISpecialSauce" owner:self options:...]
I think if you create outlets in UISpecialSauce or whatever then they should be assigned correctly because of the owner:self.
Theres a cocoapod that might actually help with that:
https://github.com/mobilejazz/NibWrapper
It provides a wrapper view that loads a child view from another NIB. You can specify the name of that NIB inside interface builder via a runtime attribute.

UIView (subview) does not send IBActions to AppDelegate

I'm having this problem because I originially made everything in the main NIB, but then after reading learned that it is better to use subviews.
I've got my IBActions in the AppDelegate, and I've successfully connected and loaded my subviews. However, when I try to connect my buttons in the subviews to the IBActions in the AppDelegate, the IBActions appear under the "First Responder". They seem to connect fine, but when running the application they do not trigger the IBActions (I've confirmed this with some NSLogs, it's not an error in the code within the IBActions). What am I doing wrong?
Thanks!
The AppDelegate should only be used for very specific items such as implementing the UIApplicationDelegate protocol (i.e. methods like applicationDidFinishLaunching) or in some cases storing global variables.
You should keep IBActions and other outlets in their respective view controller files (i.e. if you created MyViewController.h and MyViewController.m which are linked with MyViewController.xib where you have some buttons, images, etc.). They can then be hooked up via dragging the inspector control you want (i.e. TouchUpInside) to the File's Owner.
Something you should read to better understand view controllers: http://developer.apple.com/iphone/library/featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html
Typically it is best to create a unique view controller for each view you will present to the user. For instance, if I had a main screen and then an "about" or a settings screen, I would make each of those their own view controller. It helps organize things better than using one view with a whole bunch of subviews that you hide/show and will also improve loading times and general performance.
Update for your 2nd question in the comments about accessing the app delegate:
First, you need to import the .h file (i.e. #import "AppDelegate.h") for the app delegate into whichever view controller .m file you wanna use to access whatever variables, arrays, etc you have stored in the app delegate files. Make sure you synthesize whichever objects you create in the app delegate's .h file in the app delegate's .m file so the getters and setters are created (so you can access them).
Then in the view controller .m file, in whichever method you are using:
-(void)someMethod {
// here we just create a shortcut to the app delegate called "theAppDelegate"
YourAppDelegateFileNameHere *theAppDelegate = (YourAppDelegateFileNameHere *)[[UIApplication sharedApplication] delegate];
// now you can use the dot notation if you wanna access a variable
int SomeNewInteger = theAppDelegate.someIntegerYouHaveStored;
// or some array you have stored
return [theAppDelegate.someArrayYouCreated count];
}
Hope that helps!

How to use generic (NSObject) controller with subviews of a UIViewController?

I have a UIViewController that is loading several subviews at different times based on user interaction. I originally built all of these subviews in code, with no nib files. Now I am moving to nib files with custom UIView subclasses.
Some of these subviews display static data, and I am using loadNibNamed:owner:options: to load them into the view controller. Others contain controls that I need to access.
I (sort of) understand the reasons Apple says to use one view controller per screen of content, using generic controller objects (NSObjects) to manage subsections of a screen.
So I need a view controller, a generic controller, a view class and a nib. How do I put this all together?
My working assumptions and subsequent questions:
I will associate the view class with
the nib in the 'class identity' drop
down in IB.
The view controller will coordinate
overall screen interactions. When
necessary, it will create an instance
of the generic controller.
Does the generic controller load the
nib? How?
Do I define the outlets and actions
in that view class, or should they be
in the generic controller?
How do I pass messages between the
view controller and the generic
controller?
If anyone can point me to some sample code using a controller in this way, it will go a long way to helping me understand. None of the books or stackoverflow posts I've read have quite hit the spot yet.
Okay, I think I figured it out:
Extend NSObject to make your CustomController
Define your outlets & actons in CustomController.h, including a reference to the UIView in your nib
Set the File's Owner of your nib to CustomController
Hook up all your outlets & actions as usual, including the UIView outlet
In your CustomController.m init, load the nib
- (id)init {
self = [super init];
if (self != nil)
[self loadNib];
return self;
}
- (BOOL)loadNib {
NSArray *topLevelObjs = nil;
topLevelObjs = [[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:self options:nil];
if (topLevelObjs == nil) {
NSLog(#"Error! Could not load nib file.\n");
return NO;
}
return YES;
}
The new NSObject based controller will work very much like a view controller.
It sounds like what you want is what I've coined "reusable UIView widgets" -- reusable widgets that do something / present a display that you can incorporate in your app screens wherever and how many times as you want -- you can create them purely in code or instantiate them by placing their frame in another xib file (but you don't get to change the widgets' internal parameters in the xib file, that would require an IB plugin).
This is an organization that I've never seen discussed anywhere. Part of my frustration early on with iOS programming is wanting something like this but not seeing any way to express it in any example.
See my answer in this question to see how it can be structured:
UIView and initWithFrame and a NIB file. How can i get the NIB file loaded?
I recommend placing all widget-internal handling of direct/low-level events in the uiview widget subclass, and implememnt a delegate protocol for a highler level interaction with the widget client (i.e., "loginRequested:userName:passWord" instead of manually accessing button and textfields internal to the widget).
The (optional but recommended) xib file for the widget has an owner of the widget, and the init code in the widget owns the duty of loading the xib file. The customer of the widget simply instantiates the widget and implements whicever widget delegate functions makes sense for it.