iPhone SDK: CustomControls as in C# - iphone

I have a UIViewController. The UIViewController has a NIB with one outlet - a UIView, containing several buttons and labels. Imagine, it is something like a UIDatePicker.
In order to not be forced to copy and paste all the controlling code into a new environment, I was trying to encapsulate the UIView into a separate UIView subclass with an own NIB, sort of a C# CustomControl approach.
From a controlling (other) UIViewController I'm instantiating the view from the NIB
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil];
selectorView = (DateTimeSelectorView*)[nibObjects objectAtIndex:0];
selectorView is a property in the using UIViewController class. The problem: "initWithFrame" of my UIView is never called. My UIView is covering the whole space (320x480), whereas it should have a smaller size. How can I achive this? Furthermore the UIView seems to hide all other controls, instantiated from the UIViewController class.
Regards

When a view is instantiated by the nib loader, initWithFrame: isn't called. The nib loader calls initWithCoder:. You should implement initWithCoder to perform any initialization, including setting the frame.

So here is a "HowTo" for the question: How to embed a self-contained UIView into my UIViewController with NIB?
1) Say you have a UIView with a button and a textfield, making some login. You have the UI in a separate NIB called Login.xib. The functionality is in Login.m and Login.h, a subclass of UIView. The class name is "Login". Take care, that the class is set properly in Login.xib. Everything is fine.
2) Now you want to use this "out of the box" in a new app.
3) Drag the three files (Login.m, Login.h and Login.xib") into your new UIView based project
4) Add a property in your UIViewController class, pointing to your Login class (of course, include the Login.h first)
5) Open IB with Login.xib and set the file's owner to your current UIViewController class
6) Connect the main view of your Login.xib with the property defined in UIViewController (!! this is important !!)
7) Add the following to your viewDidLoad in UIViewController (supposed, the name of your property is "myLogin")
[[NSBundle mainBundle] loadNibNamed:#"Login" owner:self options:nil];
myLogin.frame = CGRectMake(0 ,100, 320, 200); // Optional, you may also use the initial bounds
[self.view addSubview:myLogin];
The view will appear where you let it appear. Other controls from your superview will be available too.
It took me several hours to find that out. There is here and there some scattered info, but I didn't find a complete "how to" for that simple task up to now.
Regards

Related

Adding UIView subView not working

I have a UIViewController class MyClass that initially had no XIB, and was initialized programmatically. Now, I don't want to load it from an XIB, but I do want to make a small 50x50 UIView (settings overlay view), and I want to add it MyClass, so instead of programatically declaring the new settings overlay view, I thought I would create an XIB file, set the file's owner to MyClass, declare in MyClass.h an IBOutlet UIView *settingsOverlay, and link it in the XIB.
Then in the viewDidLoad method, I do [self.view addSubview:settingsOverlay], but for some annoying reason it doesn't work. It just doesn't appear. I tried creating a simple UIImageView programmatically and adding it to the subView, and it works just fine, but when done through the XIB, it doesn't work. Can anyone point out what could possible be wrong? Please let me know what other details I might need to include.
If you are trying to add a view using xib then you need to use loadNibNamed method.
[[NSBundle mainBundle] loadNibNamed:#"settingsOverlay" owner:self options:nil];
You can refer developer link for more info - http://developer.apple.com/library/IOs/#documentation/UIKit/Reference/NSBundle_UIKitAdditions/Introduction/Introduction.html
Creating an XIB for the settings overlay view and setting it's owner to MyClass does not implicitly cause that XIB to be loaded as a result of manually instantiating MyClass. You would have to manually load the settings overlay view XIB in MyClass viewDidLoad and add it as a subview. Loading it and passing owner as self will cause it to be bound to the IBOutlet you created, but you still have to add it as a subview.
The code in viewDidLoad would look like this:
[[NSBundle mainBundle] loadNibNamed:#"OverlayView" owner:self];
[self.view addSubview:self.overlayView];
You need to load the MyClass xib before settingsOverlay will be set. Try adding this line before your addSubview call.
[[NSBundle mainBundle] loadNibNamed:#"MyClass" owner:self options:nil];

Subclassing UIView and displaying multiple instances

I am trying to do the following:
Display an instance of a custom UIView. This UIView subclass should load it's content from Interface Builder, because it shows stuff that I don't want to create by hand. My question is: How can I load from Interface Builder in a UIView subclass?
I will eventually animate this view out of the screen and release it. (I know how to do that :-) )
GOTO Step 1, i.e creating a new instance of my UIView and display it.
Probably a noobish question, but I can't seem to figure it out. I would appreciate some help, Fabian
In iOS 4.0+, create an instance of UINib referencing your nib file (+nibWithNibName:bundle:), then instantiate the objects in the nib with -[UINib instantiateWithOwner:options:].
If you need to target iOS 3.x, too, call:
[[NSBundle mainBundle] loadNibNamed:owner:options:]
which returns an array of the objects in the nib file.
To load an interface from Interface Builder you need to use a UIViewController. If you don't use interface builder though, you can create a uiview subclass, create your interface, and then create instances of your class. For every class all you need to do is this:
MyView *view = [[MYView alloc] initWithFrame:theFrame];
MyView *secondView = [[MyView alloc] initWithFrame:theFrame];
//do stuff with the views
//release the views
[view release];
[secondView release];

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.

How to load a nib from a customized UIView?

I have a nib call "Hello.xib", and I have a HelloView that is inherit from the UIView, and I want to do the layout in the Hello.xib, and I want to allocate them to the HelloView.m / HelloView.h, how can I do so? Thank you.
You normally do so on the outside. In HelloView you should have a UIViewController derived class. Then when initializing it on the outside you would call:
hello = [[HelloViewController alloc] initWithNibName:#"Hello" bundle:nil];
The bundle:nil make Cocoa use the default bundle.
In the interface builder's inspector for your Hello.xib's view, set the class of the view (in identity tab) to HelloView. I hope thats what you are looking for.
Just today I wrote a demonstration code, that also uses instantiation of custom view by loading a nib
the controller has a member DetailContactHeaderView *headerView
in the nib, I have a DetailContactHeaderView and the Files' owner type is my Controller
the files' owner property headerView and the View get Connected
in this controller I have this code
[[NSBundle mainBundle] loadNibNamed:#"DetailContactHeader" owner:self options:nil];
See my MyContacts for an implementation.
FYI:
DetailContactHeaderView
DetailContactHeader.xib
DetailContactViewController
especially -tableView:viewForHeaderInSection:

Using a custom UIView from multiple view controllers

I have created a custom UIView that I would like to use on multiple different view controllers in my iPhone application. Currently, I have copied the UIView that contains the controls from the nib file for the first view controller, pasted it into the other view controllers, and then wired up all of the actions to each view controller. This works fine, but is not the optimal way that I would like to use this custom view.
My custom view is reasonably simple, it consists of some UIButtons with labels, and tapping on these buttons fires actions that changes the contents of controls on my view controller's view.
What would be a strategy to consolidate the definition and usage of this UIView? Ideally, I would like to just reference this custom view from the nib of view controllers, and have my view controller respond to actions from this custom view.
EDIT: OK, based on J.Biard's suggestions, I have tried the following with not much luck.
I created another UIView based nib file with the contents (for now just some UIButton objects) of my reusable view and UIView subclass .m and .h files, and then set the nib File's Owner class to my newly created class name.
Then, I added most of the code from J.Biard (I changed the rect to 50,50,100,100, left out the setDelegate out for now, as I am just trying to get it working visually for now, and i found that [self.view addSubview:view] worked much better than [self addSubView:view]) to the end of the viewDidLoad method of the first view controller that is displayed when the app fires up.
And what I get now is my main view with a black square in it. Have I missed an outlet somewhere, or is there some initialization needed in initWithFrame or drawRect in the UIView subclass?
Create your MyCustomView class and nib file.
In the nib file, set Files Owner to MyCustomView - then design your view as normal, with a top level UIView. Create an IBOutlet in MyCustomView to link to your top level UIView in your nib file.
In MyCustomView add this method:
- (BOOL) loadMyNibFile {
if (![[NSBundle mainBundle] loadNibNamed:#"MyCustomView" owner:self options:nil]) {
return NO;
}
return YES;
}
In your view controllers you can use this custom view like so
- (void)viewDidLoad {
MyCustomView *customView = [[MyCustomView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
[customView loadMyNibFile];
[self.view addSubview:customView.view]; //customView.view is the IBOutlet you created
[customView release];
}
You could also create a convenience class method on MyCustomView to do this if you liked.
If it is very simple I would recommend that you create a subclass of UIView in code and create instances of this class (or you can use Interface Builder to create the custom UIView that is then archived into the NIB file and restored later on also using code).
Using the code solution you could create instances of your custom UIView in your controller by calling something like:
#import "MyCustomView.h"
// let the superview decide how big it should be or set it as needed.
CGRect sizeRect = CGRectMake(0,0,0,0);
// create an instance of your view
MyCustomView *view = [MyCustomView alloc] initWithFrame:sizeRect];
// set a custom delegate on the view or set callback methods using #selector()...
[view setDelegate:self]; // self = view controller
// add the view to the controller somewhere... (eg: update CGRect above as needed)
[self addSubView:view];
// don't forget to release the view somewhere ;-)
This example assumes that you create a delegate protocol that your View Controller can respond to or you can wire up events dynamically using #selector. If you don't want to create instances of the view in code you could add a "UIView" to your NIB file and set it's class type in the inspector window (command -> 4 -> class dropdown).
If you want to do everything in interface builder you can create your custom UIView and use something like "- (NSArray *)loadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options" (see NSBundle) to load the NIB file dynamically. This presents it's own challenges though it is also feasible.
The most involved option would be to create your own xcode custom UI library / plugin so that your custom control / view could be dragged into each NIB file from the Library window like any other control shipped by Apple.
Hope this clarifies or eliminates some options for re-using controls for you.
Cheers-