How can I load nib from a UIView in iOS 4 ?
I don't have a UIViewController, since I'm adding this custom view to a parent view.
I want to initialize this custom view from a nib file. Which initializer should I use ? initWithFrame or initWithCoder ?
So far, I've only assigned the custom class in the interface builder. (See pic: http://cl.ly/7pmj). Is this necessary ? If so, it is still not working, why is it not enough ?
NB I've found this related question in StackOverflow. However, it doesn't explain how to initialize the view itself with the nib.
I need to customize drawRect method, for this view, so I can't just add a subview from a nib file. I need the view itself being initialized from the nib file.
thanks
Yes, it's a must to change the custom view class in your nib file.
Make sure two things:
1. Go to yourCustomView.nib file, select the view in object, then make sure the custom class is correct under the identity inspector.
2. In the youCustomView.m file, make sure you have this part:
- (id)initWithCoder:(NSCoder*)coder
{
if ((self = [super initWithCoder:coder]))
{
//add custom change after init from nib.
}
return self;
}
Use the method from the related question (NSBundle loadNibNamed) and make sure the owner in that call is an object that has an IBOutlet for the view in question.
Hook that iboutlet up to the view in interface builder (you will need to set the class of the owner in IB to the class of the owner in your load nib call).
The nib will load the view into that iboutlet variable.
In .h file:
IBOutlet UIView *myView;
in .m file:
[[NSBundle mainBundle] loadNibNamed:#"viewToLoad" owner:self options:nil];
[someOtherView addSubView:myView];
The rest is in IB.
Related
I have a view called FeedView, handled by FeedViewController.
I also have a XIB called "NearestStore" which is handled by a view controller named "NearestStoreViewController". NearestStore xib has labels, buttons, etc. In the view controller I have outlets that are connected to the subviews in NearestStore.xib.
NearestStore inherits from UIButton (so it's easier to handle click event).
On FeedViewController.xib I have a UIButton that has been set to be of type NearestStore.
So far so good. This is on my FeedViewController:
__weak IBOutlet NearestStoreButton *btn_nearestStore;
The outlet is connected on the xib to the outlet.
NearestStoreViewController has several outlets to subviews like:
#property (nonatomic, weak) IBOutlet UILabel *lbl_distance;
#property (nonatomic, weak) IBOutlet UIImageView *img_distance;
For some reason, on my FeedViewController the reference to btn_nearestStore is fine, but all the subviews are nil.
For example:
btn_nearestStore.lbl_distance
is nil
What am I missing?
This sounds exactly as the system is supposed to work. It is not easy to create a custom widget using xibs.
Here's how it works:
Your FeedViewController will preform xib loading for the corresponding FeedView.
During this load, it notices the NearestStoreButton subview. As a consequence, it creates such a view using the - (id)initWithCoder: message on the NearestStoreButton class. It will not magically notice the corresponding .xib nor the corresponding viewController.
If you need to use a xib within a xib, you need to do the loading manually for all subviews. Keep in mind that you somehow need to create/use the appropriate owners (view controllers) for these secondary xibs.
It's hard to tell from your description, but this sounds like a problem with "owner" of the loaded NearestStoreButton XIB. When you load a NIB, you give the loader an owner, and its this owner on which most outlet bindings and actions are made. If you're loading your NearestStoreButton with UINib, then when you call instantiateWithOwner:options:, make sure you pass the object on which the outlets should be set as the owner.
When are you calling the outlet? If you are trying to access the property in the initWithCoder method of the view, it's not guaranteed that object has been instantiated.
If you access your property in the awakeFromNib method within the view you should be able to get it. For instance, I have a custom view, and my code looks as such:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
//Don't style the subviews in here since they won't be initialized
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self styleViews];
}
- (void)styleViews
{
//access my properties and style them in here
}
Following post contains detailed explanation about creating custom views using Nib:
creating custom view using Nib
After I create the customInit as mentioned in the post, I am able to get the IBOutlets allocated.
I have a storyboard driven iOS application.
My objective is to add a small square view (defined as other XIB) to another UIView which is on main ViewController.
*// Please note that, I have assigned the Class names in Identity Inspector correctly*
On the main ViewController, I added a UIView (myHolderView) manually and added a UIView member in the ViewController interface.
//.h
#interface ViewController : UIViewController
#property(nonatomic, strong) IBOutlet UIView *myHolderView;
#end
And, I have connected this IBOutlet by "BlueString" :)
Now, I have added another XIB with single view - Called it MySquare.xib and added a label on it.
And, added a UIView class with a .h and .m files. And, "BlueString" connections are made.
#interface MySquare : UIView
#property(nonatomic, strong) IBOutlet UILabel *myLabel;
#end
Now, in the implementation:
#implementation ViewController
#synthesize myHolderView;
- (void)viewDidLoad
{
[super viewDidLoad];
MySquare *sq=[[MySqure alloc]init];
// Do any additional setup after loading the view, typically from a nib.
[self.myHolderView addSubview:sq];
}
When I run it in the simulator, it just shows the main view, but on it the square view is not coming.
I know that main view had loaded because... I changed the colour of it so that I could see.
Please help.
Probably you don't do in your MySquare.m file the view initialisation using your xib.
- (id) initWithFrame:(CGRect)frame {
NSLog(#"This is called if you add programatically this view");
self = [super initWithFrame:frame];
if (self) {
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"MyIBView" owner:self options:nil] objectAtIndex:0]];
}
return self;
}
If you plan to add your xib using Interface Builder, you have to implement similarly the initWithCoder method:
-(id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"MyIBView" owner:self options:nil] objectAtIndex:0]];
}
return self;
}
Plese go through in this checklist, it might help others too:
Checklist adding a custom view with Interface Builder layout (xib):
1) create a new xib (File->New->IOS/User Interface->View), name it (eg. MyIBView)
2) create a new class (File->New->IOS/Cocoa Touch->Objective-C class), make sure it is a Subclass of UIView! Name it MyIBView.
Now you have MyIBView.xib, MyIBView.h and MyIBView.m files added to your project
3) Edit your MyIBView.xib: select the File's owner and using the Identity inspector (3rd icon) select Class you created recently, MyIBView.
4) Select the top level View (if its a new xib, only this exists) and make sure using the Identity inspector that the Class is UIView (grey) and not overwritten
5) Now you can add IBOutlets and/or IBActions to your MyIBView.h file and you can do connections using Interface Builder in your MyIBView.xib
Using this view programatically in another view:
6a) Create the first method (initWithFrame) above in your MyIBView.m file if you would like to use this custom view programatically
7a) include your MyIBView.h file where you would like to add your custom view programatically
8a) now you can add your view:
MyIBView *myIBView = [[MyIBView alloc] initWithFrame:CGRectMake(0,0,100,100)];
Using this view in Interface Builder in another view:
6b) Create the second method (initWithCoder) above in your MyIBView.m file if you would like to use this custom view in Interface Builder
7b) In your another view drop an UIView to your view canvas. Adjust the size and modify the Class using the Identity inspector to MyIBView.
Please note that you cannot see design time your custom view, only after trying to run the code.
Hope it helps, and you can find your miss-configuration!
I have a UIView that I want to use in several View Controllers so I created the view (.h/.m) and a nib file for it. In Storyboard view is added as subview to view controller but is not loading the layout as defined in nib.
Is it possible to load the nib for a UIView used within a view controller in a Storyboard?
If you ask about seeing your .xib content in Interface Builder when editing Storyboard, it's not possible. But if you need to load it in runtime, it's possible.
.h/.m should contain UIView subclass
in .xib file you need to set File's owner (Identity Inspector -> Custom Class) with your custom UIView subclass
in .h file add #property (strong, nonatomic) IBOutlet UIView* view; and connect your View container from Interface Builder to this field (Ctrl + Click on View and drag connection to above view property in .h
connect other subviews to .h
in .m file you need to place
- (void)awakeFromNib {
NSString* nibName = #"Place here your Nib name without extension";
if ([[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil]) {
[self.view setFrame:[self bounds]];
[self addSubview:self.view];
}
}
That's all. Move View to Storyboard scene, change Custom Class to your FooView, import .h in View Controller code and use IBOutlets as you want.
Have happy coding
EDIT
I want to notice that according awakeFromNib documentation we should avoid such code in awakeFromNib.
It is recommended that you maintain a one-to-one correspondence between your File’s Owner objects and their associated nib files. Loading two nib files with the same File’s Owner object causes that object’s awakeFromNib method being called twice, which could cause some data structures to be reinitialized in undesired ways. It is also recommended that you avoid loading other nib files from your awakeFromNib method implementation.
Name your view ViewInNib, then we have:
ViewInNib.h
ViewInNib.m
ViewInNib.xib
In your storyboard, put a placeholder UIView component in the view controller, remember to set it's class ViewInNib, then in ViewInNib.m:
static BOOL isReplaced = NO;
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
{
if (!isReplaced)
{
isReplaced = YES;
ViewInNib *replaced = [[[NSBundle mainBundle] loadNibNamed:#"ViewInNib" owner:nil options:nil] lastObject];
replaced.frame = self.frame;
replaced.translatesAutoresizingMaskIntoConstraints = NO;
return replaced;
}
isReplaced = NO;
return self;
}
In this method, we replace real view loaded from nib from view in storyboard. Hope it helps.
use XXNibBridge:
https://github.com/sunnyxx/XXNibBridge
Super simple to use and works with Swift as well.
Full credit goes to #sunnyxx
I want to create a self-contained UICollectionView subclass (acting as its own data source and delegate) so that I could load it in different viewControllers. Here's what I have so far:
CustomCollectionView.h
#interface CustomCollectionView : UICollectionView <UICollectionViewDataSource, UICollectionViewDelegate>
#property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
#end
CustomCollectionView.m
#import "SSCalendarView.h"
#implementation SSCalendarView
#synthesize collectionView;
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
[self registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"Identifier"];
[self addSubview:collectionView];
}
return self;
}
// Below are UICollectionViewDataSource and UICollectionViewDelegate methods
#end
CustomCollectionView.xib
Contains only one view - UICollectionView. It's class is set to CustomCollectionView
File's Owner's class is also set to CustomCollectionView
File's Owner is UICollectionView's delegate and data source
I understand that I have quite a few things wrong here. But perhaps we could use this as a starting point.
My questions are:
How to implement this sub-class correctly? I want to load the view fully from xib
To begin with, and aside from potential MVC violation (this sub-class would do it all), can a UICollectionView be its own data source and delegate?
If above is possible, how do I correctly create an instance of this subclass to use in my view controllers?
There already exists an object which you can use for this purpose - UICollectionViewController. This can be subclassed and added to any view controller (as a child view controller) and already contains a collection view which it is the datasource and delegate for.
The problems with your current approach are:
As you point out, you're putting too much responsibility on one object by having a view be its own datasource and delegate
File's owner for a xib can't be an object from within the xib. When you load the xib, the object you send to the owner argument is the file's owner. I've no idea what you actually end up with using the code you currently have.
I'm not sure why you insist on using a xib anyway - what does this give you, except the headache of an extra file and the complexity of nib loading? Just create a collection view controller, you can specify the layout and register cells in the init and viewDidLoad methods.
First of all making view to act like view controller is violation of MVC, ask you've said so - so you shouldn't probably do it.
Theoretically it's possible to force view to act as delegate & data source but I wouldn't recommend it.
If you still want to do it - just assign delegate & data source to self:
self.dataSource = self;
self.delegate = self;
and adopt UICollectionViewDelegate and UICollectionViewDataSource protocols in .h file
I have a UIView called baseViewand in that I have initWithFramewhere I add some other views and do some custom stuff. The same view also has a NIB file.
Now I have a UIViewController class named AppController in which I want to add the baseView view to the view of the AppController view so I am doing this:
self.view = baseView; but the problem is that the NIB file does not get loaded. How do I make sure the customized stuff AND the NIB file get´s loaded/run?
You have many options, depending on how your "baseView" class is meant to be used and integrated in to your application. It's not clear just how you intend to use this class -- as the view in a UIViewController subclass, or as a reusable modular component mean to be instantiated multiple times throughout your application, for use in many different view controllers.
If your view is meant to be the only view in a UIViewController subclass, then Phonitive is correct -- bundle it together with the UIViewController subclass .xib file and use the UIViewController's viewDidLoad to do final initialization.
But if you want your View class to be a subcomponent reused multiple times in different view controllers, integrated either via code or via inclusion in a .xib file for another controller, then you need to implement both the initWithFrame: init method, and awakeFromNib, to handle both cases. If your internal initialization always includes some objects from .xib, then in your initWithFrame you'll need to load your .xib manually in order to support "customer" classes that want to create your widget via code. And likewise, if a .xib file contains your object then you'll need to make sure you call any code-required finalization from awakeFromNib.
Here's an example of how to create a UIView subclass component with the UI design in a nib.
MyView.h:
#interface MyView : UIView
{
UIView *view;
UILabel *l;
}
#property (nonatomic, retain) IBOutlet UIView *view;
#property (nonatomic, retain) IBOutlet UILabel *l;
MyView.m:
#import "MyView.h"
#implementation MyView
#synthesize l, view;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// Initialization code.
//
[[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil];
[self addSubview:self.view];
}
return self;
}
- (void) awakeFromNib
{
[super awakeFromNib];
// commenters report the next line causes infinite recursion, so removing it
// [[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil];
[self addSubview:self.view];
}
- (void) dealloc
{
[l release];
[view release];
[super dealloc];
}
Here's what the nib file looks like (except that file's owner needs to be changed to MyView class).
be sure to hook up both the view and label outlets to File's Owner. That's it! A template for creating re-usable UIView widgets.
The really neat thing about this structure is that you can place instances of your MyView object in other nib files, just place a UIView at the location/size you want, then change the class in the identity inspector (CMD-4) to MyView, and boom, you've got an instance of your widget in whatever views you want! Just like UIKit objects you can implement delegate protocols so that objects using your widget can be notified of interesting events, and can provide data to display in the widget to customize it.
I found this post after having a problem trying to do this in my app. I was trying to instantiate the view from a NIB in the ViewDidLoad method, but the controls acted as if they were disabled. I struggled with this trying to directly set the userInteractionEnabled property and programmatically set the touch event selector for a button in this view. Nothing worked. I stumbled upon another post and discovered that viewDidLoad was probably too soon to be loading this NIB. I moved the load to the ViewWillAppear method and everything worked. Hope this helps someone else struggling with this. The main response was great and works well for me now that I have it being called from the proper place.
if you want to use a NIB, it's better for your UIView to be linked with a UIViewController, in this case you can use
UIViewController *vc=[[UIViewController alloc] initWithNibName:#"YourNIBWihtOUTEXTENSION" bundle:nil]
[self.view addSubView:vc.view ];
becareful of memory leaks, you have to release vc
If you have a custom UIView with a xib file.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
id mainView;
if (self)
{
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"HomeAllAdsView" owner:self options:nil];
mainView = [subviewArray objectAtIndex:0];
}
return mainView;
}
- (void) awakeFromNib
{
[super awakeFromNib];
}
This post helped me Building Reusable Views With Interface Builder and Auto Layout. The trick had to do with setting the IBOutlets to the FileOwner and then adding the content view to itself after loading the nib