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.
Related
Hello StackOverflow.
I am trying to setup a UIView such that it loads itself from XIB
file in Xcode.
To do this, I went through the following initial steps:
Created empty UIView subclass.
Added a blank XIB file.
Then, I added all the subviews I wanted into the XIB file, and made corresponding IBOutlet Properties inside the UIView Subclass.
I watched this video to show me how to properly connect the outlets and set the files owner. The video instructs me to do the following things:
Do not set the UIView class to your subclass in XIB. Time link
Set the File's Owner to your UIView subclass in XIB:Time Link
Insert a IBOutlet into your UIView class of type UIView so your XIB file can load into that.Time link
Override initWithCoder like (image) if you intend to initialize the custom UIView within another XIB file.
Override initWithFrame like (image) if you intend to initialize the custom UIView programatically within another class file.
Cool, I have done all of these things, and am choosing to initialize my UIView programatically.
See my UIView subclass implementation file:
#import "CXHostsTableViewCellContentView.h"
#implementation CXHostsTableViewCellContentView
#pragma mark Custom Initializers
-(instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[[NSBundle mainBundle]loadNibNamed:#"CXHostsTableViewCellContentView" owner:self options:nil];
[self setBounds:self.view.bounds];
[self addSubview:self.view];
}
return self;
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[[NSBundle mainBundle]loadNibNamed:#"CXHostsTableViewCellContentView" owner:self options:nil];
[self addSubview:self.view];
}
return self;
}
And of course, my header file:
#import <UIKit/UIKit.h>
#import "CXStyleView.h"
#interface CXHostsTableViewCellContentView : UIView
#pragma mark UIView Properties
#property (strong, nonatomic) IBOutlet UIView *view;
#property (nonatomic,weak)IBOutlet UIView *standardView;
#end
I also have an image here of the XIB file's owner and another of the IBOutlet link from the base UIView to the outlet on file's owner.
Right, so everything's looking pretty good, should be no problem running this right?
Nope, whenever I initialize this subview and present it, I get a crash:
CXHostsTableViewCellContentView *scrollContentView = [[CXHostsTableViewCellContentView alloc]init];
I've really got no idea how to solve this, as I'm sure I'm following all of these steps right. I've googled and come across this question which has the same symptoms, but the answer has identical code to what I'm using, and this question with a contradictory reply.
I'm not sure what to do at this point, or what is causing the crash. I know that if I have NO outlets linked at all, it works. But then again, nothing displays either.
I think that You will face Problem When You Allocate Memory to Your scrollContentView object.
so,Try To allocate Memory With Frame.
i.e
Write this in .m file
- (void)myAllocation {
//do your stuff
}
- (id)initWithFrame:(CGRect)aRect {
self = [super initWithFrame:aRect];
if (self) {
[self myAllocation];
}
return self;
}
- (id)initWithCoder:(NSCoder*)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self myAllocation];
}
return self;
}
...
CXHostsTableViewCellContentView *scrollContentView = [[CXHostsTableViewCellContentView alloc] initWithFrame:CGRectMake(0, 10, 0, 20)];
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.
I have created a custom subclass of UIView along with a xib file and declared IBOutlets and IBActions within the custom class.
#interface ContactUsView : UIView
#property (nonatomic, weak) IBOutlet UIButton *displayCloseButton;
- (IBAction)callButtonPressed:(id)sender;
- (IBAction)emailButtonPressed:(id)sender;
- (IBAction)displayCloseButtonPressed:(id)sender;
#end
In the xib file I have dragged in a UIView to represent my custom view. I have set:
Files owner = to my custom class
Have set the dragged in UIView to my custom class.
I have then added various buttons which are hooked up to the 3 methods stated above.
Inside the ContactUsView.m I have the following:
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
NSArray* array = [[NSBundle mainBundle] loadNibNamed:#"ContactUsView" owner:self options:nil];
for (id object in array) {
if ([object isKindOfClass:[ContactUsView class]])
self = (ContactUsView *)object;
}
}
return self;
}
When I come to create this view I do the following:
- (void)viewWillAppear:(BOOL)animated
{
ContactUsView *contactUs = [[ContactUsView alloc] initWithFrame:CGRectZero];
CGPoint origin = self.view.frame.origin;
CGSize size = self.view.frame.size;
[contactUs setFrame:CGRectMake(origin.x,
CGRectGetMaxY(self.view.frame) - 100,
size.width,
contactUs.frame.size.height)];
[self.view addSubview:contactUs];
}
Issue
When I press on one of the buttons the application crashes with:
Thread 1: EXC_BAD_ACCESS(code=2, address=0xb0c
Can anyone help me with this. I feel like I am probably making a mistake somewhere in regards to creating and loading custom uiviews from xibs.
If you require anymore information let me know. Many thanks.
Future reference
When creating a custom view using a xib DO NOT set the files owner. Instead create all your IBOutlets and IBActions as you normally would and then to hook them up open the Utilities tab and control drag from there.
• Files owner = to my custom class
Wrong. Files owner should be empty. The view itself is files owner. It means that you should connect all actions and outlets with ContactUsView in your xib.
[[NSBundle mainBundle] loadNibNamed:#"ContactUsView" owner:self options:nil]
...
self = (ContactUsView *)object;
After you passed self as ownerparameter. You changing it. Which means that previously allocated ContactUsView (self) will be destroyed since -loadNibNamed:owner:options: do not retain it. If you apply my first advice you should send nil as owner parameter
forloop here is not necessary use just array[0], because this is always your view if you have valid views hierarchy in your xib
If you are loading a UIView for an xib then you should create a class method to load the view.
In your customview.h
+(id)customView;
& in your customview.m
+ (id)customView
{
CustomView *customView = [[[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:nil options:nil] lastObject];
if ([customView isKindOfClass:[CustomView class]])
return customView;
else
return nil;
}
You can initialize it anywhere using:
CustomView *myView = [CustomView customView];
EDIT: Make sure you have changed your customview's class in identity inspecter & also make sure your connection of IBActions are with that class' methods.
You can use delegate for this
this is how you can do this
#protocol CustomViewDelegate <NSObject>
- (void)callButtonPressed:(id)sender;
- (void)emailButtonPressed:(id)sender;
- (void)displayCloseButtonPressed:(id)sender;
#end
#interface ContactUsView : UIView
#property (nonatomic, weak) IBOutlet UIButton *displayCloseButton;
#property (nonatomic, weak) id<CustomViewDelegate> ButtonDelegate;
- (IBAction)callButtonPressed:(id)sender;
- (IBAction)emailButtonPressed:(id)sender;
- (IBAction)displayCloseButtonPressed:(id)sender;
#end
and in .m file
- (IBAction)callButtonPressed:(id)sender
{
[self.ButtonDelegate callButtonPressed:sender];
}
- (IBAction)emailButtonPressed:(id)sender{
[self.ButtonDelegate emailButtonPressed:sender];
}
- (IBAction)displayCloseButtonPressed:(id)sender{
[self.ButtonDelegate displayCloseButtonPressed:sender];
}
After that just set the delegate with viewcontroller refrence and use those delegate here
- (void)viewWillAppear:(BOOL)animated
{
ContactUsView *contactUs = [[ContactUsView alloc] initWithFrame:CGRectZero];
contactUs.ButtonDelegate = self;
CGPoint origin = self.view.frame.origin;
CGSize size = self.view.frame.size;
[contactUs setFrame:CGRectMake(origin.x,
CGRectGetMaxY(self.view.frame) - 100,
size.width,
contactUs.frame.size.height)];
[self.view addSubview:contactUs];
}
- (void)callButtonPressed:(id)sender
{}
- (void)emailButtonPressed:(id)sender
{}
- (void)displayCloseButtonPressed:(id)sender
{}
I have done this and works totlly fine
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.
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