Loading custom UIView from nib, all subviews contained in nib are nil? - iphone

I have a custom UIView object with a nib that defines the view/subview layout. My outlets are connected, and when I init the object from initWithFrame: everything is in order.
The problem I'm having is when I'm using IB to embed the UIView into another nib; the custom UIView appears, but none of the subviews it contains appear - in fact their symbols all resolve to nil. I have a very minimal initWithCoder: and awakeFromNib: implementation (just does logging) and my understanding is that as the nib is deserialized the subviews should at least be initialized during the process?
The only conclusion I'm coming to on my own is that one of two things is happening: either my outlet references are corrupt/bad and aren't working, or my understanding of load-from-nib process is faulty and I'm missing something.
Thanks in advance!
Edit: (Code posted for Nekto as requested... as you'll see, it does logging and thats it, heh.)
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
NSLog(#"ThumbnailGridView.initWithCoder frame= %f, %f", self.frame.size.width, self.frame.size.height);
}
return self;
}
- (void)awakeFromNib
{
NSLog(#"ThumbnailGridView:awakeFromNib");
}
Edit 2: Nibs, controllers, subviews, etc.
Nibs: I have a single nib containing a UIView. This UIView contains a single UIScrollView, which is filled with a grid of UIViews that are defined in another nib. (Note: this part works fine, as the fill is done programmatically and works with an initWithFrame: call.) The problem here is that the UIScrollView symbol is nil after initWithCoder: and awakeFromNib: are both called, so objects are just being added to nil and nothing happens. If the scrollview symbol was not nil, I'm sure this would work.
Controllers: There are two controllers that utilize this, one is done with initWithFrame: and works perfectly, the other embeds it as a nib-based reference. (As mentioned elsewhere here, defining a UIView in IB, setting the custom class.) I stepped through the code, and that view -is- being initialized properly - only its subviews are "missing".
Does this help give a clearer picture of the situation at all?

You may be misunderstanding how nib loading works. If you define a custom UIView and create a nib file to lay out its subviews you can't just add a UIView to another nib file, change the class name in IB to your custom class and expect the nib loading system to figure it out. You need to modify initWithCoder of your custom UIView class to programmatically load the nib that defines its subview layout. e.g.:
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:self options:nil];
[self addSubview:self.toplevelSubView];
}
return self;
}
Your custom view's nib file needs to have the 'File's owner' class set to your custom view class and you need to have an outlet in your custom class called 'toplevelSubView' connected to a view in your custom view nib file that is acting as a container for all the subviews. Add additional outlets to your view class and connect up the subviews to 'File's owner' (your custom UIView).
Alternatively, just create your custom view programmatically and bypass IB.

Check that you are calling the 'super' implementation of initWithCoder: and awakeFromNib in each of your overridden methods i.e.
- (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super initWithCoder:decoder])) {
...your init code...
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
...your init code...
}

Related

how to load custom view with xib on a xib or storyboard

I just want to load a custom view (with xib) on a viewcontoller's xib or stroy board.What is the best practice to do the same.
I have used awakeAfterUsingCoder function code below.
(id) awakeAfterUsingCoder:(NSCoder*)aDecoder
{
BOOL theThingThatGotLoadedWasJustAPlaceholder = ([[self subviews] count] == 0);
if (theThingThatGotLoadedWasJustAPlaceholder)
{
CustomView* theRealThing = (id) [CustomView view];
theRealThing.frame = self.frame;
theRealThing.autoresizingMask = self.autoresizingMask;
return theRealThing;
}
return self;
}
but after using this function my awakeFromNib started calling multiple time.
Please sugegst.
The correct answer currently linked to is overly complicated and buggy, as you have found out. There is a much more standard and better way to do this:
Create an empty XIB
Add a UIView to your XIB so that it is the only top-level object (aside from the proxies First Responder and File's Owner)
Change the class of your new UIView to CustomView
Instantiate the XIB and retrieve your CustomView instance like so:
CustomView *view = [[UINib nibWithNibName:#"CustomView" bundle:nil] instantiateWithOwner:self options:nil][0];
Add it to your view hierarchy in your view controller's viewDidLoad method:
[self.view addSubview:view];
Be sure to override -initWithCoder: in your CustomView subclass in case you need to do any custom initialization. I know this is a lot, so please let me know of any if these steps confuse you or if you get stuck.

Draw SubView in Storyboard

I want to present a modalView (it can be a viewController) that I draw in Storyboard. I don't want to have to make the whole thing programmatically. Is there a way to do this without it being a full screen view?
I guess another way to ask the question is: how do I [self.view addSubView:mySubView] where mySubView is drawn in InterFaceBuilder/Storyboard?
To do this properly, you should look at View Controller containment in the docs. Basically you would addChildViewController after instantiating the viewController from your storyboard and then add the viewController's view to your current view hierarchy.
To just get it working however, the following will get you going:
UIViewController *childViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"<identifier you set in Interface Builder>"];
[self.view addSubView:childViewController.view];
Note that one of the reasons to do it 'properly' will be to ensure that autorotation and presentation callbacks are sent to the sub view controller.
Override the initWithCoder method in the object-c class.
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil] objectAtIndex:0]];
}
return self;
}

Custom view in nib instantiated as parent type

I've created a custom class inheriting from UILabel. I placed a UILabel in my nib and set the class of the label to my custom class. However, when the viewDidLoad method is called, the view is of the class UILabel instead of my custom class.
Any ideas?
I was able to create an instance of my CustomLabel in a test project. What I did differently was instead of dragging a UILabel, I pulled in an instance of CustomLabel into the view.
I implemented awakeFromNib against a target for the latest sdk as follows.
-(void)awakeFromNib
{
[self setText:[NSString stringWithFormat:#"%#",[self class]]];
}
- (void)drawTextInRect:(CGRect)rect // called as expected
{
NSLog(#"drawTextInRect: called");
[super drawTextInRect:rect];
}
Couple of mysteries...
I implemented init and initWithFrame in CustomLabel, neither was called by the machinery that loads the view from the nib.

Common header view on top of nib files with Interface builder

.Hi,
I have a nib file that contains an header that will be used in most of my views, so that I can change it's layout just once when I need. I'd like to know if it's possible to add the header nib view with interface builder, or if I need to do that programmatically and how should it be done.
I've thought about setting the subclass of the subview to a UIView subclass that automatically loads the nib file.
- (id)initWithFrame:(CGRect)frame {
UIView *cell;
NSArray *nib = [[NSBundle mainBundle] loadNibNamed: #"MainHeaderView"
owner: self
options: nil];
for (id oneObject in nib)
if ([oneObject isKindOfClass: [UIView class]])
cell = (UIView *) oneObject;
if ((self = [super initWithFrame: [cell frame]])) {
// Initialization code
}
return self;
}
But this also doesn't seem to work.
It should work, at least in theory. For that matter, it should be possible (but with rather a lot of effort) to add it via IB, from what used to be called a palette, a long time ago; I believe that option was recreated.
I would say that loading from initWithFrame: is likely not to work. Other possible places to load would be awakeFromNib (with a caveat about multiple nib loadings causing it to be called multiple times), or viewDidLoad. Try moving the load to viewDidLoad, and see if your cell is connected up. You should also be testing for failure to load the nib (nil return).
Ok, I've solved this another way.
I've created the headerView and the controller, and a subclass of UIViewController for all views that needed the header to be displayed, loading them all with the header. Something like this:
#implementation MyDefaultViewController
- (void)viewDidLoad {
[super viewDidLoad];
MyTestAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
[self.view addSubview: [appDelegate.headerViewController view]];
}
Every view that needs the header to be there will have a controller that's a subclass of MyDefaultViewController. Seems to work, although the fact that I don't specify where to place the header scares me a bit xD

how to associate uicontroller to custom uiview programmatically

I have a custom UIview which is created programmatically. How to associate to it a custom UIViewController (programmatically as well)
Thanks and regards,
Implement loadView in the UIViewController to create a view hierarchy programmatically without a nib file.
- (void)loadView {
// allocate the subclassed UIView, and set it as the UIViewController's main view
self.view = [[[UIViewSubclass alloc] initWithFrame:CGRectMake(0, 0, 320, 460)] autorelease];
}
You can continue setting up the view/subview hierarchy in two ways. One is to add them in the custom UIView's initialization method, like so:
// in the MyView.m file
- (id)initWithFrame:(CGRect)f {
if (self = [super initWithFrame:f]) {
// add subviews here
}
return self;
}
The second way is to continue using the loadView method implemented in the UIViewController subclass, and just using [self.view addSubview:anotherView]. (Alternatively, use the viewDidLoad method in the UIViewController subclass.)
Note: Replace initWithFrame: with whatever the custom UIView's initialization method is (e.g., initWithDelegate:).
Say the view you created is called newView and the controller is newController. The simple approach would be:
newController.view = newView;
But I'd rather subclass UIViewController and override its - (void)loadView and - (void)viewDidLoad methods and create and/or manipulate the view there - that's the way Apple wants you to do it, and for good reason.