creating a reusable control - iphone

(Using IOS 6, XCode 4.6, ARC, Storyboard)
I'm Trying to create a reusable UIView (like user control in C#), because I have two viewControllers having the same data one for input and other for display.
I read here that the way to do it is by creating a separate xib file for the ReusableUIView, as well as creating corresponding h + m files for it. Adding all the controls and linking the outlets to them.
In the storyboard I have UIViewController, in its viewDidLoad i wrote the following code:
NSArray *myNibsArray = [[NSBundle mainBundle] loadNibNamed:#"ReusableUIView" owner:self options:nil];
ReusableUIView *myCustomView = [myNibsArray objectAtIndex:0];
Few questions:
nothing happens at this point so I'm guessing i have to connect somehow self (the UIViewController) with myCustomView
The ReusableUIView is one of many controls in the UIViewController how do i set it's place
In the xib file i can't find a way to set the size of the UIView, where and when we set the size of it

It is better to custom view loading it its class itself - add some static method in your custom view class that will look like this:
'+' (ReusableUIView *) loadView {
ReusableUIView *loadedView = nil;
NSArray *nibs = [[NSBundle mainBundle] loadNibNamed:#"ReusableUIView"
owner:self options:nil];
if ([nibs count] > 0) {
loadedView = [myNibsArray objectAtIndex:0];
}
return loadedView;
}
As ReusableUIView inherits UIView you can easily config its sizes and positions using frame property and adding it as a subview on parent view.
I am not sure what is the problem, but you can change view sizes in xib easily.
Also, please note two things:
When you are loading view from nib then - (id) initWithCoder(NSCoder *)aDecoder constructor method is calles. So if you need any additional setups - you have to implement then in this method.
When you are making custom view with xib and .h/.m files you have to mark you custom class in xib not on files owner but directly on your UIView.

Related

Instantiating a nib object to present in a view

I have a LegendViewController that shows a legend. Originally, I just plopped an UIImageView in there. However now, we need a few legends and I want to reuse the LegendViewController. So I created a new initializer:
- (id)initWithView:(UIView *)view withNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
[self initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[self.view addSubview:view];
}
return self;
}
So now this works assuming I pass in a UIView object. For one of my legends, I was wondering if I could load a .xib into my view controller without an image or a UIView object. I have a very simple legend where I just want some color coded squares (UIViews with colors), and some text (UILabels). I can create this in a standalone .xib file, but I wasn't sure how I could load it into my LegendViewController. I've got so far as:
UINib *nib = [UINib nibWithNibName:#"HealthLegend" bundle:nil];
where HealthLegend is my standalone .xib file with my data. Or can this not be done and I need to either create an image in some drawing program, or draw the code manually in drawRect? Thanks.
You can load a nib like this
NSArray *topLevelObjects = [[NSBundle bundleForClass:[self class]] loadNibNamed:#"HealthLegend"
owner:nil
options:nil];
// Assuming you only have one root object in your nib
[self.view addSubview:[topLevelObjects objectAtIndex:0];
Change the owner to the appropriate object depending on what you set the File's Owner to in the nib (if that's even required - sounds like you have a static view so it may not be)
From the reference guide
When you create an UINib object using the contents of a nib file, the
object loads the object graph in the referenced nib file, but it does
not yet unarchive it. To unarchive all of the nib data and thus truly
instantiate the nib your application calls the
instantiateWithOwner:options: method on the UINib object
UINib object can provide a significant performance improvement
So I guess you want something like:
UINib *nib = [UINib nibWithNibName:#"HealthLegend" bundle:nil];
NSArray *views = [nib instantiateWithOwner:nil options:nil];
UIView *view = [views objectAtIndex:0];

How do I use one xib with multiple view controllers?

In my program, I have a UIViewController subclass MyViewController and two subclasses of that view controller.
I want them all to use the same xib so I initiate them as
SubClass *SC = [[SubClass alloc] initWithNibName:#"MyViewController" bundle:nil];
[self presentModalViewController:SC animated:NO];
[SC release];
SubClass is a subclass of MyViewController, which is a subclass of UIViewController. In MyViewController.xib, I have File's Owner set to MyViewController.
If I only was going to have two subclasses, I would probably just duplicate the xib but I plan to have many, many subclasses, all using the same xib.
You can load any XIB with
- (NSArray *)loadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options
of the NSBundle class. With
NSArray *arr = [[NSBundle mainBundle] loadNibNamed:#"foo" owner:nil options:nil];
you can load all contents of a XIB into an array. The order of the items in the array is the same you defined in Interface Builder without File's Owner and First Responder.

How to use a xib and a UIView subclass together?

I'd like to create a couple of custom views, sort of reusable UI components, and would prefer to not layout the UI in code in the UIView subclass. I'd like to use a xib for that. I've used a xib by itself. And I've used a UIView subclass by iteself. But I haven't used them together. How do I "attach" them to one another? I'd like to use IBOutlets to access the UILabels in my custom view.
Will this kind of thing work?
NSArray *xib = [[NSBundle mainBundle] loadNibNamed:#"MyCustomView" owner:self options:nil];
MyCustomView *view = [xib objectAtIndex:0];
view.myLabel.text = #"fred";
Yes that's the way that it works when you're loading xibs that aren't parents to viewControllers
Edit August 15, 2013:
You can't always just assume that you're going to get exactly what you're looking for out of index 0 of the returned NSArray, which is what makes it good to use type checking.
NSArray *xibArray = [[NSBundle mainBundle] loadNibNamed:#"MyCustomView" owner:nil options:nil];
MyCustomView *myView = nil;
for (id xibObject in xibArray) {
//Loop through array, check for the object we're interested in.
if ([xibObject isKindOfClass:[MyCustomView class]]) {
//Use casting to cast (id) to (MyCustomView *)
myView = (MyCustomView *)xibObject;
}
}

loading custom view using loadNibNamed showing memory leaks

I have a number of custom table cells and views that I built using interface builder
In interface builder, everything is set up similarly. There is a table cell and a couple other UILabels and a background image
Object owner if the nib is NSObject
Class for the table cell is the name of the class for my table cell
Here is how I create the table cell in my code:
SectionedSwitchTableCell *cell = nil;
NSArray *nibs = [[NSBundle mainBundle] loadNibNamed:kSectionedSwitchTableCellIdentifier owner:owner options:nil];
for(id currentObject in nibs)
{
if([currentObject isKindOfClass:[SectionedSwitchTableCell class]])
{
cell = (SectionedSwitchTableCell *)currentObject;
break;
}
}
return cell;
For my custom table headers I have this
NSArray *nibs = [[NSBundle mainBundle] loadNibNamed:#"CustomTableHeader" owner:self options:nil];
for(id currentObject in nibs)
{
if([currentObject isKindOfClass:[CustomTableHeader class]])
{
return header
}
}
In my .h and .m files for the custom view, I have IBOutlet, #property set up for everything except for the background image UIImageView. Everything that has the IBOutlet and #property are also #synthesized and released in the .m file.
Leaks is showing that I have memory leaks with CALayer when I create these custom view objects. Am I doing something wrong here when I create these custom view objects? I'm kind of tearing my hair out trying to figure out where these leaks are coming from.
As a side note, I have a UIImageView background image defined in these custom views but I didn't define properties and IBOutlets in my .h and .m files. Defining them doesn't make a difference when I run it through Leaks but just wanted to confirm if I'm doing the right thing.
Any input would be super helpful. Thanks :)
Check your custom cell xib file, ensure you have set the identifier (kSectionedSwitchTableCellIdentifier) to the cell.
I have the similar problem and fixed with this.

Loading a Nib within a Nib

I am new to interface builder and I would like to have a screen which contains a 3x3 grid of a UIView each of which contain a UIImageView, and 4 UILabels.
My logic is probably flawed but the way I am trying to achieve this was to:
Create a UIView Nib file MyUIView.nib and layout in IB with an imageView and 4 labels
Create a UIView subclass called MyUIView.m which contains 1 IBOutlet UIImageView and 4 IBOutlet UILabels. Link the MyUIView.nib and MyUIView.m as files owner and connect the outlets.
Then create another nib MyGridViewController.nib which has 9 MyUIView in it laid out in the 3x3 grid.
Create a UIViewController which has 9 IBOutlet MyUIView and connect them via Interface Builder.
Is it possible to load a nib into another nib graphically from within InterfaceBuilder, if so how do I do it? Do I drag a "standard" UIView onto the canvas and then change the class to be a MyUIView?
Or do I need to do this all programmatically within the MyGridViewController.m with something like:
for (int i=0; i<9; i++)
{
NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:#"MyUIView" owner:self options:nil];
[myViewArray addObject:[ nibViews objectAtIndex: 1]];
}
The only other way I have gotten this to work was to have a single Nib and put 9 UIImageViews and 36 UILabels but this obviously is a pain when I want to change something as I need to update each one of the 3x3 "cells". I thought it would be easier to change it in one file and all 9 would be updated.
You cannot do it in Interface Builder.
What I would probably do is to make a custom view, say MyGridItem, that loads MyUIView.nib as a subview when it awakes, and then use 9 of them in your MyGridView.nib.
Be careful with awakeFromNib, as it can be called twice if the view is involved in the loading of two different nibs (eg, if MyGridItem is the owner when loading MyGridView.nib, then MyGridItem awakeFromNib will be called once when it is loaded as part of loading MyUIView.nib, and once when it loads the MyGridView.nib.
Also, since you're loading the nib 9 times, you may want to cache the nib once using
NSNib* theNib = [[NSNib alloc] initWithNibNamed:#"MyGridItem" bundle:nil];
load it with:
if ( [theNib instantiateNibWithOwner:self topLevelObjects:NULL] ) {
You then may want to deallocate it after you've loaded all nine subviews.
"Yes you (almost) can."
I do it in my projects using Interface Builder.
The only flaw is that you see a white area to represent the 'nested nibs' in Interface Builder. Let's say that, in the mean time (I main waiting for Apple to add this feature in XCode), the solution I present here is acceptable.
First read this: https://blog.compeople.eu/apps/?p=142
Then, if you do ARC, follow these instructions and grab my UIVIew+Util category included here.
For ARC, you will have to allow this 'self' assignation. (https://blog.compeople.eu/apps/?p=142 state that it's not needed, but it is. If you do not, you will get some 'messages send to deallocated instance')
To achieve this in an ARC project, add the '-fno-objc-arc' flag compiler setting on your file.
Then do NO-ARC coding in this file (like dealloc setting nils, calling super dealloc, etc..)
Also, client nib's viewcontroller should use strong property to hold the instance returned by awakeFromNib. In the case of my sample code, the customView is referenced like this:
#property (strong, nonatomic) IBOutlet CustomView* customView;
I finally added some other improvements to properties handling and nib loading using copyUIPropertiesTo: and loadNibNamed defined in my UIView+Util category.
So awakeAfterUsingCoder: code is now
#import "UIView+Util.h"
...
- (id) awakeAfterUsingCoder:(NSCoder*)aDecoder
{
// are we loading an empty “placeholder” or the real thing?
BOOL theThingThatGotLoadedWasJustAPlaceholder = ([[self subviews] count] == 0);
if (theThingThatGotLoadedWasJustAPlaceholder)
{
CustomView* customView = (id) [CustomView loadInstanceFromNib];
// copy all UI properties from self to new view!
// if not, property that were set using Interface buider are lost!
[self copyUIPropertiesTo:customView];
[self release];
// need retain to avoid deallocation
self = [customView retain];
}
return self;
}
The UIView+Util category code is
#interface UIView (Util)
+(UIView*) loadInstanceFromNib;
-(void) copyUIPropertiesTo:(UIView *)view;
#end
along with its implementation
#import "UIView+Util.h"
#import "Log.h"
#implementation UIView (Util)
+(UIView*) loadInstanceFromNib
{
UIView *result = nil;
NSArray* elements = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass([self class]) owner: nil options: nil];
for (id anObject in elements)
{
if ([anObject isKindOfClass:[self class]])
{
result = anObject;
break;
}
}
return result;
}
-(void) copyUIPropertiesTo:(UIView *)view
{
// reflection did not work to get those lists, so I hardcoded them
// any suggestions are welcome here
NSArray *properties =
[NSArray arrayWithObjects: #"frame",#"bounds", #"center", #"transform", #"contentScaleFactor", #"multipleTouchEnabled", #"exclusiveTouch", #"autoresizesSubviews", #"autoresizingMask", #"clipsToBounds", #"backgroundColor", #"alpha", #"opaque", #"clearsContextBeforeDrawing", #"hidden", #"contentMode", #"contentStretch", nil];
// some getters have 'is' prefix
NSArray *getters =
[NSArray arrayWithObjects: #"frame", #"bounds", #"center", #"transform", #"contentScaleFactor", #"isMultipleTouchEnabled", #"isExclusiveTouch", #"autoresizesSubviews", #"autoresizingMask", #"clipsToBounds", #"backgroundColor", #"alpha", #"isOpaque", #"clearsContextBeforeDrawing", #"isHidden", #"contentMode", #"contentStretch", nil];
for (int i=0; i<[properties count]; i++)
{
NSString * propertyName = [properties objectAtIndex:i];
NSString * getter = [getters objectAtIndex:i];
SEL getPropertySelector = NSSelectorFromString(getter);
NSString *setterSelectorName =
[propertyName stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[propertyName substringToIndex:1] capitalizedString]];
setterSelectorName = [NSString stringWithFormat:#"set%#:", setterSelectorName];
SEL setPropertySelector = NSSelectorFromString(setterSelectorName);
if ([self respondsToSelector:getPropertySelector] && [view respondsToSelector:setPropertySelector])
{
NSObject * propertyValue = [self valueForKey:propertyName];
[view setValue:propertyValue forKey:propertyName];
}
}
}
Tadaaaa :-)
Credits goes to https://stackoverflow.com/users/45018/yang for initial solution. I just improved it.
If you create your view with the 1 image view and 4 labels in a nib (I'll call this view the "grid cell"), you can create another nib for your grid and drag in 9 instances of your grid cell. The 9 instances will only show up as placeholders (blank views) in IB, but they'll work when you run the app.
This article tells you how to do it. Check out the section called "A reusable subview". And since you're making a grid with 9 identical cells, it might make sense to use AQGridView -- see the section "A reusable AQGridViewCell".
AFAIK, there's no way to load a NIB within a NIB. What I would do, in your case, is add the UILabels programmatically in MyUIView.
Here's a slightly more dynamic way to do something like this.
I didn't really want to be pulling in the nibs and sifting through their objects wherever I wanted to use them in code, seemed messy. Also, I wanted to be able to add them in interface builder.
1) Create your custom subclass of UIView (lets call it myView)
2) Create your nib and call it myViewNIB
Add these two methods (would be smarter to have them in a superclass UIView and subclass that)
- (UINib *)nib {
NSBundle *classBundle = [NSBundle bundleForClass:[self class]];
return [UINib nibWithNibName:[self nibName] bundle:classBundle];
}
- (NSString *)nibName {
NSString *className = NSStringFromClass([self class]);
return [NSString stringWithFormat:#"%#NIB", className];
}
The so-called magic is that last line there where it returns a nibName by appending NIB to the current class.
Then for your init method, which is initWithCoder since you want to use this in interface builder (you could make it more robust so it can be used programmatically too by also setting up initWithFrame):
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code
NSArray *nibObjects = [[self nib] instantiateWithOwner:nil options:nil];
UIView *view = [nibObjects objectAtIndex:0];
[self addSubview:view];
}
return self;
}
So this requires that myViewNIB's first (and probably only) member is a view. What this won't give you is a way to set things like labels programmatically inside the nib, at least not easily. Without looping through the views and looking for tags I'm not sure how else you'd do that.
Anyway. At this point you can drag out a new UIView in IB, and set the class to myView. myView will automatically search for our associated myViewNIB.xib and add its view as a subview.
Seems there should be a better way to do this.
What I've done, is:
1) For the owning view, I put in the subview as an empty view with the class name of the custom view as a placeholder. This will create the custom view, but none of its subviews or connections will be made as these are defined in the custom view's nib.
2) I then manually load a new copy of the custom view using its nib and replace the placeholder custom view in the parent.
Use:
NSView *newView = (Code to load the view using a nib.)
newView.frame = origView.frame;
NSView *superView = origView.superview;
[superView replaceSubview:origView with:newView];
Ugh!