use a custom uiview inside a storyboard - ios

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];

Related

Why does custom xib require UIView property

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.

Custom UIView loaded from Xib

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

How does loadNibNamed work? UIView outlets not initializing using loadNibNamed

I know this is quite straight forward but after too much hair-pulling I am nowhere near solution.
I have seen tutorials explaining how to create view using XIB and all. But none of them address the situation that I have here.
I have an XIB file, a custom UIView subclass that has few labels and buttons. The UIView subclass is reusable, and that is the reason I can't have outlets inside any single View controller. As a result I store individual controls (subviews) of this view inside my custom UIView itself. This is logical, as no view controller should own the subviews of this custom view which is to be included in every view controller.
The problem is, I don't know how to initialize the entire UI fully.
Here is my code for UIView Subclass:
#interface MCPTGenericView : UIView
+(id)createInstance : (bool) bPortrait;
#property (weak, nonatomic) IBOutlet UIView *topView;
#property (weak, nonatomic) IBOutlet UIView *titleView;
#property (weak, nonatomic) IBOutlet UILabel *titleLabel;
#property (weak, nonatomic) IBOutlet UIButton *logoButton;
#property (weak, nonatomic) IBOutlet UITextField *searchTextField;
#property (weak, nonatomic) IBOutlet UIButton *menuButton;
#end
Later on, I also plan to use this same XIB file for landscape orientation of this UIView too, and I plan to use the same above outlets with landscape oriented controls in same XIB.
And here is the implementation:
#implementation MCPTGenericView
//#synthesize topView, titleLabel, titleView;
+(id)createInstance : (bool) bPortrait
{
UIView * topLevelView = nil;
MCPTGenericView * instance = [MCPTGenericView new];
NSArray * views = [[NSBundle mainBundle] loadNibNamed:#"MoceptGenericView" owner:instance options:nil];
int baseTag = (bPortrait)?PORTRAIT_VIEW_TAG_OFFSET:LANDSCAPE_VIEW_TAG_OFFSET;
// make sure customView is not nil or the wrong class!
for (UIView * view in views)
{
if (view.tag == baseTag)
{
topLevelView = view;
break;
}
}
instance.topView = (MCPTGenericView *)[topLevelView viewWithTag:baseTag + 1];
instance.searchTextField = (UITextField *)[topLevelView viewWithTag:baseTag + 2];
instance.menuButton = (UIButton *)[topLevelView viewWithTag:baseTag + 3];
instance.logoButton = (UIButton *)[topLevelView viewWithTag:baseTag + 4];
instance.titleView = [topLevelView viewWithTag:baseTag + 5];
instance.titleLabel = (UILabel *)[topLevelView viewWithTag:baseTag + 6];
return instance;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
if ((self = [super initWithCoder:aDecoder]))
{
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"MCPTGenericView" owner:self options:nil] objectAtIndex:0]];
}
return self;
}
-(void)awakeFromNib
{
[super awakeFromNib];
[self addSubview: self.titleView];
[self addSubview:self.topView];
}
- (id) init
{
self = [super init];
if (self)
{
[[NSBundle mainBundle] loadNibNamed:#"MCPTGenericView" owner:self options:nil];
[self addSubview:self.topView];
[self addSubview:self.titleView];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// Initialization code
[[NSBundle mainBundle] loadNibNamed:#"MCPTGenericView" owner:self options:nil];
[self addSubview:self.topView];
[self addSubview:self.titleView];
}
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
Something that worked:
I succeeded in calling initWithFrame:frame from my viewcontroller. That way, I could see all controls properly initialized. But then, why should I be supplying a frame if I have already drawn an XIB? Shouldn't loadNibNamed be handling frame setting and layout stuff since that is the intended use of XIBs?
I am also baffled at the way loadNibNamed needs an owner object. Why do we already need an object to get the same object from XIB? That too, a half-baked one?
Please help...
What was baffling me was the way loadnibnamed loses xib layout & outlet information. I finally found a way to achieve it.
Here is a recap of what works:
1) Suppose MyCustomView is your custom view class - you design it and its subviews as part of XIBs. You do this via interface builder, so self-explanatory.
2) Add MyCustomView.h and MyCustomView.m (boilerplate) via Xcode -> File -> New -> Objective C Class.
3) Next, within MyCustomView.xib, set File's Owner = MyCustomView (class name just added). Do not touch top most View's custom class - leave it as UIView. Else it will end up in recursion!!!
4) In MyCustomView.h, create few outlets corresponding to subviews within MyCustomView.xib.
Such as:
#property (weak) IBOutlet UILabel * label1;
#property (weak) IBOutlet UIButton * button1;
5) Go to MyCustomView.xib. Select each subview (label, button), right click, drag from "New Referencing Outlet" and drag it up to File's Owner.
This will popup a list of outlets matching the subview's type from where you have dragged. If you dragged from a label, it will pop up label1, and so on. This shows that all you did up to this step is correct.
If you, on the other hand, screwed up in any step, no popup will appear. Check steps, especially 3 & 4.
If you do not perform this step correctly, Xcode will welcome you will following exception:
setValue:forUndefinedKey: this class is not key value coding-compliant for the key
6) In your MyCustomView.m, paste / overwrite following code:
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
NSString * nibName = #"MyCustomView";
[[[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil] firstObject];
[self addSubview:self.labelContinentName];
}
return self;
}
This step is crucial - it sets your outlet values (label1, button1) from nil to tangible subviews, and most importantly, sets their frame according to what you have set within MyCustomView.xib.
7) In your storyboard file, add view of type MyCustomView - just like any other view:
Drag a UIView in your View Controller main view rectangle
Select the newly added view
In Utilities -> Identity Inspector, set custom class value = MyCustomView.
It should be up & running no problem!
loadNibNamed does not handle frame setting, it only loads content and makes the objecet available to your code. initWithFrame: must be called to insert a new object to the view heirarchy of a window.

Custom UIView from nib inside another UIViewController's nib - IBOutlets are nil

I'm trying to create a custom UIView which holds references to its own IBOutlets. I then want to put this custom UIView into another nib.
I'm doing some additional logic in the custom UIView's awakeFromNib method. Unfortunately, when I try to access the IBOutlets in awakeFromNib, they are nil.
Here's the setup:
I have a UIView subclass, CustomView.
I have a custom .xib file with three subviews
In the other nib (that belongs to the view controller), I have dragged a UIView onto the view, and then changed the custom class to CustomView.
I tried setting the view in the CustomView nib in IB to a custom class CustomView and connecting the IBOutlets to the view, but they were still nil.
I tried setting file owner to CustomView and connecting the IBOutlets to file's owner, but they were still nil.
I also tried using another IBOutlet UIView *view and then adding that as a subview to self in awakeFromNib but that also didn't do anything.
Here's the code:
// CustomView.h
#interface CustomView : UIView
#property (nonatomic, retain) IBOutlet UITextField *textField;
#property (nonatomic, retain) IBOutlet UIView *subview1;
#property (nonatomic, retain) IBOutlet UIView *subview2;
// CustomView.m
#implementation CustomView
#synthesize textField, subview1, subview2;
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:self options:nil];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self setup];
}
- (void)setup {
// Fails because self.textField is nil
self.textField.text = #"foo";
}
I ended up using the steps in the most recent edit here and they worked beautifully.
You use a plain UIView as the top level view in the xib.
You then set file's owner to the custom subclass (CustomView).
Finally, you add a line:
[self addSubview:[[[UINib nibWithNibName:#"CustomView" bundle:nil] instantiateWithOwner:self options:nil] objectAtIndex:0]];
in the if (self != nil) block in both initWithCoder and initWithFrame.
Voila! The IBOutlets are hooked up and ready to go after the call. Really pleased with the solution, but it was very difficult to dig up.
Hope this helps anyone else.
EDIT: I updated the link to one that isn't dead. Since I never spelled out the full code, here is what it looks like after modification:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
UIView *nib = [[[UINib nibWithNibName:#"CustomView" bundle:nil] instantiateWithOwner:self options:nil] objectAtIndex:0];
[self addSubview:nib];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
UIView *nib = [[[UINib nibWithNibName:#"CustomView" bundle:nil] instantiateWithOwner:self options:nil] objectAtIndex:0];
[self addSubview:nib];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self setup];
}
- (void)setup {
// Doesn't fail because life is awesome
self.textField.text = #"foo";
}
This pattern has become so common that I actually created a category on UIView called UIView+Nib, which implements the following method:
+ (UIView *)viewWithNibName:(NSString *)nibName owner:(id)owner {
return [[[UINib nibWithNibName:nibName bundle:nil]
instantiateWithOwner:owner options:nil]
objectAtIndex:0];
}
So the above code can be simplified to:
[self addSubview:[UIView viewWithNibName:#"CustomView" owner:self]];
Note also that the above code can be refactored even more, since the logic is exactly the same in initWithFrame: and initWithCoder:. Hope that helps!
As in Dr. Acula's answer, This is probably because custom view's nib is lazy loaded when loaded from another nib (Nested nib loading), so we need to instantiate it manually. In swift code will look like this :
override func awakeFromNib() {
super.awakeFromNib()
self.customview = UINib(nibName: "CustomViewNib", bundle: nil).instantiateWithOwner(self, options:nil)[0] as! UIView
self.customview?.frame = self.viewContainer.bounds
self.viewContainer.addSubview(self.customview!)
}
I am assuming the XIBs' structure is something like this
CustomView.xib
CustomView
UITextField -> linked to IBOutlet textField
other views
CustomViewController.xib
CustomView
If this is right, then your CustomView will be created but as it is not read from CustomView.xib it doesn't have any IBOutlets assigned.
However, if your CustomViewController.xib looks like following
CustomViewController.xib
CustomView
UITextField -> linked to IBOutlet textField of CustomView
then this should work. The IBOutlet of CustomView instance should be set by the CustomViewController.xib.
Better than setting any IBOutlets in the CustomViewController.xib would be to implement awakeAfterUsingCoder: in your CustomView and create a replacement object by loading your CustomView.xib in there. This way your CustomView remains truly custom and you don't have to edit other XIBs to change the structure, add/remove IBOutlets, etc.

Setting text for IBOutlet labels in a custom UIView

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.

Resources