I have a generic custom view that has a nib file. I subclass this custom view and initialize it like this:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"GenericCustomView"
owner:self
options:nil];
UIView *view = [nibContents objectAtIndex:0];
[self addSubview:view];
}
return self;
}
I want to set some properties on the IBOutlets of the generic view, but if I set them in the initWithFrame method, the IBOutlets in the generic view haven't been loaded yet and are still nil. The awakeFromNib method in the custom view is never called. How can I set the properties of the generic nib files IBOutlets in the custom view class?
I'm only targeting iOS 7.0 and up, using Xcode 5.
Related
I followed the tutorial outlined here to load a custom xib in my view controller.
The class of the xib inherits from UIView but also needs a property view:
#interface MYBannerView : UIView
#property (nonatomic, weak) IBOutlet UIView *view;
#end
I find it strange that it needs this, as its like having a view within a view which seems redundant. Is there any particular reason for this?
Edit
I followed this tutorial here which outlines this:
http://www.maytro.com/2014/04/27/building-reusable-views-with-interface-builder-and-auto-layout.html
The author of that tutorial is using the view outlet to load the view from the xib when MYBannerView is initialized programatically
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
NSString *className = NSStringFromClass([self class]);
self.view = [[[NSBundle mainBundle] loadNibNamed:className owner:self options:nil] firstObject];
[self addSubview:self.view];
return self;
}
return nil;
}
Anything created in Interface Builder will not be loaded with a programatic init. loadNibNamed loads the UI from the xib.
If you want to override initWithFrame: to load the UI elements from the xib, your init method would look something like this:
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame]; // initialize your objects
if (self) {
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil]; // load your view from IB
}
return self; // return the view with all of the IB UI loaded
}
No, your UIView subclass doesn't need an outlet to the view to be functional. In fact, using self you have access to the view itself.
What about tutorial: when author added view A (let's call it so) on MYViewController's view, he set view A class to be MYBannerView. Running app won't show anything because MYBannerView xib wasn't loaded and didn't replaced content from view A. So author loads this xib in initWithCoder, set outlet using value returned by loadNibNamed: and adds view loaded from MYBannerView to view A. The process is a little bit messy but it works.
I have 2 UIViews that both use the same nib:
PictureCell and LabelCell that both inherit from ParentCell. Both of these use the nib picturecell.xib because their layouts are very similar.
PictureCell and LabelCell both override a method called setImage from ParentCell.
Currently picturecell.xib's owner is set to PictureCell.
I instantiate the PictureCell by doing [[NSBundle mainBundle] loadNibNamed:#"picturecell" owner:self options:nil][0];
How would I instantiate LabelCell?
I would make separate xibs for each cell, and use registerNib:forIdentifier: instead of loading them the way you were. You can copy and paste the cell to the new xib, so you don't have to remake it.
After Edit:
I did find one way that works to share a common UI made in a xib between two cell subclasses. Instead of making a xib that's a cell, make one that is a UIView. Add all your common subviews to it, and make the file's owner the base cell class so you can hook up any outlets you've created in that class. In the base cell's init method, you can add this view as a subview of the contentView ("content" is a property created in the .h of the base cell).
#implementation RDBaseCell
-(instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
_content = [[NSBundle mainBundle] loadNibNamed:#"CellContent" owner:self options:nil][0];
[self.contentView addSubview:_content];
}
return self;
}
-(void)layoutSubviews {
[super layoutSubviews];
self.content.frame = self.contentView.bounds;
}
In the table view controller, register the class for both of your subclasses. In the init method for the subclasses, you can add any custom views that are specific for that subclass.
So, I am using storyboard and I have dragged inside my UIViewController a UIView.
Let's call it customView, and it's class is called SPView. The class is set in the inspector window. Inside the SPView.h there are a number of properties
I have tried the following :
If I drag a label inside the UIView (in the storyboard), the label is shown, but I cannot connect it to one of my outlets in the SPView.h by drag and drop.
If I create a new XIB file, with the label inside, I can do the connections as I like.
Then inside my UIViewController I have tried either of these:
self.customView =[[SPView alloc] initWithFrame:CGRectMake(60, 60, 200, 260)]
self.customView =[[SPView alloc]init];
and inside my SPView, if I use this:
- (id)initWithCoder:(NSCoder *)aDecoder {
NSLog(#"initWitchCoder called");
if ((self = [super initWithCoder:aDecoder])) {
//[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"SPView" owner:self options:nil] objectAtIndex:0]];
[self baseInit];
}
the label is not shown.
If I uncomment the comment, the initWithCoder is called for ever and the app eventually crashes.
What I want is :
to have a custom UIView inside a UIViewController, set either in the storyboard or programmatically (but it will be better if the graphic data are set in storyboard or in a separate .XIB file so as to inspect them more easily).
Can you help me on that?
you cannot drag outlets to custom class uiview class . only its allowed when using xib.
you can drop outlets of that custom view in its parent viewcontroller only.
you can set tags in subviews of your custom view. and can easily access them by using this below code .
lets say you have a subview in your custom view with tag :2
- (id)initWithCoder:(NSCoder *)aDecoder {
NSLog(#"initWitchCoder called");
if ((self = [super initWithCoder:aDecoder])) {
//[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"SPView" owner:self options:nil] objectAtIndex:0]];
UILabel *lbl = (UILabel *)[self viewWithTag:2];
[self baseInit];
}
so you had to set tags to subviews to access them
in your uiviewcontroller you had to drop outlet directly . no need for this
like this it will be initialized automatically
#property (weak, nonatomic) IBOutlet SPView *customView; //right and easy way
X self.customView =[[SPView alloc] initWithFrame:CGRectMake(60, 60, 200, 260)]
X self.customView =[[SPView alloc]init];
You can have following method defined in your UIViewController class and have an XIB created for SPView.
- (SPView*) loadCustomViewFromNib
{
SPView *spView = nil;
NSArray *array = [[NSBundle mainBundle] loadNibNamed:#"SPView"
owner:nil
options:nil];
for (id object in array)
{
if ([object isKindOfClass:[SPView class]])
{
spView = (SPView*)object;
break;
}
}
return spView;
}
And have SPView initialized as shown below
self.customView = [self loadCustomViewFromNib];
I have a custom UIView (MyView) object that gets loaded from a xib file. In the MyView.m I have a method that gets called in initWithCoder that does some initialization. This works for the most part, but I was trying to set the text in some IBOutlet label properties here, and it wasn't working, I realized because the properties were not initialized at this point.
I tried setting the text of the outlets in the drawRect method, and it does work. But I think I read that implementing drawRect if you don't need to can cause some performance hits. My question is, where would the best place to set text of my IBOutlet label properties? (What I'm actually doing is setting it to localized versions of what's in the xib. I know about localized xib files, but I want to set the text in code.)
Here is some code. Here is how I initialize the view in my view controller:
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"MyView"
owner:self
options:nil];
_myView = [nibContents objectAtIndex:0];
Here is the initWithCoder method in MyView.m
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self localizeLabels];
}
return self;
}
And here is the localizeLabels method:
- (void)localizeLabels
{
self.powerPlant.text = NSLocalizedString(#"POWERPLANTBREAKDOWN_HEADERCOLUMN1", nil);
self.output.text = NSLocalizedString(#"POWERPLANTBREAKDOWN_HEADERCOLUMN2", nil);
self.capacity.text = NSLocalizedString(#"POWERPLANTBREAKDOWN_HEADERCOLUMN3", nil);
}
I have checked that these methods do get called using breakpoints, and that the properties are nil in the localizeLabels method.
UPDATED:
Your MyView.m class should be like this in the start.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"MyView"
owner:self
options:nil];
UIView *_myView = [nibContents objectAtIndex:0];
[self addSubview: _myView];
[self localizeLabels];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code
[self localizeLabels];
}
return self;
}
and in your viewcontroller. you need to initialize your MyView like this.
MyView _view = [[MyView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
[self.view addSubview:_view];
Your IBOutlet properties are initialized if they are properly wired up to the xib in Interface Builder. The initialisation takes place as the xib is loaded into the app. These properties are only weak references to xib-instantiated objects after all.
So my best guess is that you have not wired up the IBOutlets to their respective xib objects - check in Interface Builder.
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