For a while I've been using custom cells (with their own nibs) for tables without issues. Now in a new project I see the need for a reusable custom view (not a cell) the would have a title, a button, and another UIVIew to hold more views. I'll call it "Section":
The idea would be to be able to use this Section in storyboards (using a UIView and setting the custom class accordingly). That way whatever views I put inside that UIView would actually be contained in the inner UIView of the Section.
I thought the hard part would be to actually get the views put using IB and Storyboard to actually reside in that inner UIView instead of the root UIView of Section. Turns out just making the custom view (without any inner views yet) is not working as I would have expected. Here is the code, which is based off of the dozens of custom cells I've done and have worked (though adjusted for the specific init methods of a generic UIView):
#import "SectionContainer.h"
#implementation SectionContainer
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:#"SectionContainer" owner:self options:nil];
self = [nibArray objectAtIndex:0];
/*NSArray *nibRoot = [[UINib nibWithNibName:#"SectionContainer" bundle:nil] instantiateWithOwner:self options:nil];
[self addSubview:[nibRoot objectAtIndex:0]];*/
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:#"SectionContainer" owner:self options:nil];
self = [nibArray objectAtIndex:0];
/*NSArray *nibRoot = [[UINib nibWithNibName:#"SectionContainer" bundle:nil] instantiateWithOwner:self options:nil];
[self addSubview:[nibRoot objectAtIndex:0]];*/
}
return self;
}
The matching XIB has its root view set to this custom class (just like I do in the custom cells)
THE PROBLEM
This custom class causes a EXC_BAD_ACCESS code=2 and from what I can tell by stepping through it, it's as if the class is being called recursively. Call after call after call to initWithDecoder is being made until the EXC_BAD_ACCESS error happens
WHAT I'VE TRIED
Given the seeming recursive calls I tried another approach I saw that set the XIB's file owner to the Custom Class instead of the XIB's root View. This caused the following error:
'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key sectionContainerView
Tried a slightly different method (commented out in the code above) where the XIB's root is added to the custom class (addSubView) instead of being set to it. This didn't change anything, same recursive calls (or error above if that is set up)
I would REALLY appreciate some guidance on this. Thank you.
You need to use a component called Custom Container View in storyboard. I can't just post code here because it involves some configuration in your storyboard and the code would depend on how you plumb your views / VCs, but you can read the relevant guide here:
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html
First, the recursive call is on initWithCoder:, loading a nib means instatiating its views through initWithCoder:.
That's why you can't use your UIView subclass you've designed on a nib this way (by setting a view's class on a storyboard or even on another nib actually).
The only way to use it is to instantiate it through the nib, in code.
Section *sectionView = [[[NSBundle mainBundle] loadNibNamed:nibName owner:owner options:options] objectAtIndex:index];
Now, with wiring up things from the nib you've made:
You can make connections from the objects on your nib to another object which is not found on the nib. That is what File Owner is for. You have to set its(File Owner's) class, and make the connections to it, and use an instance of its class to which you want the connections to be realized, as the owner parameter when loading the nib.
But I guess this is not what you wanted. I think you wanted to make the sub views on the nib accessible through "Section" which I assume is the root view on the nib. You create IBOutlet (or, IBAction, IBOutletCollection) properties on the Section class. To wire these up with the rest of the objects on your nib, control click on the "Section" view on your nib, and you'll see what to do from there.
Related
//Update provided
I have a storyboarded UIViewController application.
I have other XIB (inherited from UIView having only a label on it) like below
The following are contents Main.storyboard (MyController is an interface extending UIViewController)
> ViewController (UIViewController)
> MyHolderView (UIView)
The following are contents of MySquare.xib (MySquare is an interface extending UIView)
> MySquare (UIView)
> MyLabel (UILabel) (Having Default value LABEL, entered in attribute inspector)
Now I have to make 3 UIViews of instances MySquare and add it to MyHolderView
I tried to assign new label to these 3 UIView's labels' text.
But I am not able to see the new labels but only the default label LABEL is coming.
MySquare *square=[[MySquare alloc]init];
//square.myLabel.text = #"TRY";
[square.myLabel setText:[[NSString alloc]initWithFormat:#"%d",(myVar)]];
Please help.
Update
I have overrode my MySquare's init method like this. Still no luck.
I am calling the below method from UIViewController where I init my MySquare views.
Calling from UIViewController:
MySquare *square=[[MySquare alloc]initWithFrame:CGRectMake(20,20,50,50) string:[[NSString alloc] initWithFormat:#"%d",myVar]];
Implementation of the overridden init function
- (id)initWithFrame:(CGRect)frame string:(NSString *)str;
{
self = [super initWithFrame:frame];
if (self) {
self.myLabel.text=#"A";
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"View" owner:self options:nil] objectAtIndex:0]];
[self.myLabel setText:[[NSString alloc]initWithString:str]];
}
return self;
}
You need to understand the difference between a class and an instance. MySquare is a class, but you need a reference to the actual instance of MySquare in your interface. Thus this code is pointless:
MySquare *square=[[MySquare alloc]init];
[square.myLabel setText:[[NSString alloc]initWithFormat:#"%d",(myVar)]];
It is working perfectly, but the problem is that this MySquare instance is not the MySquare instance that is in your interface. (It is just a separate MySquare instance that you created, floating loose in your code.) Thus you cannot see anything happening.
Now let's consider this code:
[self addSubview:[[[NSBundle mainBundle]
loadNibNamed:#"View" owner:self options:nil] objectAtIndex:0]];
[self.myLabel setText:[[NSString alloc]initWithString:str]];
Here, you did fetch a MySquare instance from the nib and put it in your interface. Good. But you did not keep any reference to it, so you have no (easy) way to talk to it! In particular, self.myLabel is not the same as the MySquare instance's myLabel.
You left out a step! You need a reference to the MySquare instance, like this:
MySquare* square = [[[NSBundle mainBundle] loadNibNamed:#"View" owner:self options:nil] objectAtIndex:0]];
[self addSubview:square];
[square.myLabel setText:#"Look, it is working!"];
Even that is not enough, if you want to talk to square.myLabel in the future. You will need to keep a reference to square (or to square.myLabel) as an instance variable.
FluidInfo *fluidInfo = [[FluidInfo alloc]init];
UIView *info = [[[NSBundle mainBundle] loadNibNamed:#"FluidInfoSheet" owner:fluidInfo options:nil] objectAtIndex:0];
[self createFormulaPopup:info];
I have a nib file with a UIView. and I have a subclass of UIView called 'FluidInfo.' I make this UiView appear as a popup inside my viewController using my function 'createFormulaPopup'
I have made my UIView a subclass of FluidInfo. When I create outlets and actions they are all working correctly.
The problem is that my init function within my UIView is working unexpectedly. If I log something then it appears at the same time as my popup.. but if i set something like background color of the view it seems to disappear. If I set it in interface builder it will stick. If I attach the change of the color to an action within UIView then that will work as well. But when the view is initialized it seems to undo all the stuff I've done programmatically.
okay so i fixed this. i loaded the nib within the uiview itself instead of within my viewcontroller
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self = [[[NSBundle mainBundle] loadNibNamed:#"FluidInfoSheet" owner:self options:nil]objectAtIndex:0];
// Initialization code
}
return self;
}
in your code you pass FluidInfo *fluidInfo instated of your view Controller
[[[NSBundle mainBundle] loadNibNamed:#"FluidInfoSheet" owner:fluidInfo options:nil]objectAtIndex:0];
refer this link
https://developer.apple.com/library/ios/documentation/uikit/reference/NSBundle_UIKitAdditions/Introduction/Introduction.html
in that given you have to pass owner that would be your file owner here you pass owner as view subclass that's why it may not work pass self (your view controller)so it would find file owner in application.
[[[NSBundle mainBundle] loadNibNamed:#"FluidInfoSheet" owner:self options:nil]objectAtIndex:0];
refer this link of Stack over flow give you very good description of file owner What describes the "File's Owner" best in objective-c / cocoa Nib?
I have a class, FooView, that is a subclass of UIView, and whose view is loaded from a nib, something like:
+ (instancetype)viewFromNib
{
NSArray *xib = [[NSBundle mainBundle] loadNibNamed:#"FooView" owner:self options:nil];
return [xib objectAtIndex:0];
}
The nib itself has its Custom Class set to FooView in the Identity Inspector.
This is instantiated as:
FooView *view = [FooView viewFromNib];
This behaves as you'd expect. However, when FooView is itself subclassed as FooSubclassView, and instantiated as:
FooSubclassView *view = [FooSubclassView viewFromNib];
view is still of type FooView, not FooSubclassView.
Swizzling the class with object_setClass doesn't fix the fact that the underlying object is an instance of FooView, and thus methods called on the subclass instance will be those of the superclass (FooView), not FooSubclassView.
How can I correct this so that subclasses are of the correct type, without having to create a new nib for every subclass, or having to reimplement viewFromNib in every subclass?
Swizzling is not (ever) the answer.
The problem is in your NIB; it is archived with object[0] being an instance of FooView, not FooSubclassView. If you want to load the same NIB with a different view subclass as object[0], you need to move the instance out of the NIB's archive.
Probably the easiest thing to do, since your class is already loading the NIB, is make an instance of FooView or FooSubclassView the File's Owner.
This question has a decent explanation of the File's Owner pattern. Note that you are pretty much already there in that your class is what is loading the XIB/NIB anyway.
And here is the official docs on File's Owner.
I'm not sure you are onto the best solution, but I think this is what you are looking for.
+ (instancetype)viewFromNib
{
NSString *className = NSStringFromClass([self class]);
NSArray *xib = [[NSBundle mainBundle] loadNibNamed:className owner:self options:nil];
return [xib objectAtIndex:0];
}
That is as long as you can be sure that the NIB has the same name as the class.
After realizing that I mistook one of the requirements, I say I'll have to agree with #bbum.
- (id)init
{
// NOTE: If you don't know the size, you can work this out after you load the nib.
self = [super initWithFrame:GCRectMake(0, 0, 320, 480)];
if (self) {
// Load the nib using the instance as the owner.
[[NSBundle mainBundle] loadNibNamed:#"FooView" owner:self options:nil];
}
return self;
}
+ (instancetype)viewFromNib
{
return [[self alloc] init];
}
edit #1
please read the edit #1 at the bottom of the question; I'm trying to figure out all the ways nib's can be used in creating custom views so all the examples deal with nib files.
end
I have been trying to figure out all the techniques for creating custom views that use nibs in controllers. I have listed the ways at the bottom of this question.
Here's the code for my TRtestview (one implication of how I have this set-up is that initWithCoder will always be called):
// TRtestview.m
-(id)initWithCoder:(NSCoder *)aDecoder{
self=[super initWithCoder:aDecoder];
if (self) {
if (self.subviews.count == 0) {
UIView *myView=[[[NSBundle mainBundle] loadNibNamed:#"testview" owner:nil options:nil] lastObject];
myView.userInteractionEnabled=YES; // doesn't do anything
[self addSubview:myView];
}
}
NSLog(#"about to return here in initWithCoder");
return self;
}
edit 1
-(void)setup
{
UIView *myView=[[[NSBundle mainBundle] loadNibNamed:#"testview" owner:nil options:nil] lastObject];
[self addSubview:myView];
}
- (id)initWithFrame:(CGRect)frame
{
NSLog(#"at top of initWithFrame");
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
in TRViewController.m, I tried setting thisView to userInteractionEnabled but it didn't change things:
- (void)viewDidLoad
{
[super viewDidLoad];
self.thisView.userInteractionEnabled=YES;
This has worked for the scenario of calling initWithFrame (technique #1) and loadNibNamed (technique #2). It will load the view by dragging it out from the Object Inspector and then setting the Class in the Custom Class attribute inspector (technique #3). However, the button is non-functional and thus this way doesn't work.
How would I create a custom view with a nib using technique #3 and allow for user interaction?
thx
edit 1
I'd really like to understand all the ways that a custom view can be created and instantiated. Ideally, I'd like to be able to have custom views that can be created via initWithFrame, loadNibNamed, or dragged from the object library.
It seems like the scenarios are:
initWithFrame from a ViewController; if using with a nib, I call loadNibNamed in my initWithFrame - I need to prevent recursive loading
loadNibNamed from a ViewController (which will call initWithCoder) and would want to load only once
dragging an instance of a UIView and then setting the custom class to my custom UIView (TRtestview in this case). This will call initWithCoder.
It looks like you're loading testview from inside the testview class initialiser with a hacky check for subviews to prevent it from becoming recursive. Since the code outside probably also loads the nib, what you're actually doing here is loading the nib twice, once inside the other. If you set up the owner then the inner one will be setting the outlets on the outer one.
It's just a guess as there is not enough information here to know for sure - but if you are setting outlets in your view class for some of the sub-views within it then you must supply an owner object to loadNibNamed:owner:options: with the type of your class. So you could pass self as owner in your code and this might work. However it's really the wrong way to use nibs for single-views.
Instead of having the single view in your nib file being the TRtestView class, have the single-view just as a holder for your other internal views and set the "File's Owner" place-holder object to have the class TRtestview. Create an outlet for the holder and assign it then do something like this:
TRtestview* testview = [[TRtestview alloc] initWithFrame:desiredFrame];
[[NSBundle mainBundle] loadNibNamed:#"testview" owner:testview options:nil];
Now all the outlets in testview will be set when the nib is loaded, including your holder which you can add as a sub-view.
Also, if you want to use a TRtestview within a different nib (for example TestViewController), you can do this by dragging in a view and setting that object's class to TRtestview - this will create a TRtestview and call its initWithCoder and you can assign it to an outlet in TestViewController called testview. Then in the awakeFromNib for TestViewController you would put the second line of code from above, passing in the testview outlet as the owner object.
Edit:
It's tricky to see what you're trying to do exactly, but loading the nib from inside any of the view's init methods is wrong (loading the nib creates the view object, not the other way around). I think I misunderstood at first where the IBOutlets that you want to set are, probably the answer you're looking for is actually that you can load the nib but from a class constructor method like this:
+ (MyCustomView*)newCustomView
{
MyCustomView* view = [[NSBundle mainBundle] loadNibNamed:#"testview" owner:nil options:nil][0];
// Do custom setup things to view here...
// Perhaps based on additional inputs to this method.
view.doStuff;
return view;
}
I'm familiar with most of the process of creating an XIB for my own UIView subclass, but not everything is working properly for me - it's mostly to do with the IBOutlets linking up. I can get them to work in what seems like a roundabout way.
My setup is this:
I have MyClass.h and MyClass.m. They have IBOutlets for a UIView (called view) and a UILabel (called myLabel). I added the 'view' property because some examples online seemed to suggest that you need this, and it actually solved an issue where I was getting a crash because it couldn't find the view property, I guess not even in the UIView parent class.
I have an XIB file called MyClass.xib, and its File's Owner custom class is MyClass, which prefilled correctly after my .h and .m for that class existed.
My init method is where I'm having issues.
I tried to use the NSBundle mainBundle's 'loadNibNamed' method and set the owner to 'self', hoping that I'd be creating an instance of the view and it'd automatically get its outlets matched to the ones in my class (I know how to do this and I'm careful with it). I then thought I'd want to make 'self' equal to the subview at index 0 in that nib, rather than doing
self = [super init];
or anything like that.
I sense that I'm doing things wrong here, but examples online have had similar things going on in the init method, but they assign that subview 0 to the view property and add it as a child - but is that not then a total of two MyClass instances? One essentially unlinked to IBOutlets, containing the child MyClass instantiated via loadNibNamed? Or at best, is it not a MyClass instance with an extra intermediary UIView containing all the IBOutlets I originally wanted as direct children of MyClass? That poses a slight annoyance when it comes to doing things like instanceOfMyClass.frame.size.width, as it returns 0, when the child UIView that's been introduced returns the real frame size I was looking for.
Is the thing I'm doing wrong that I'm messing with loadNibNamed inside an init method? Should I be doing something more like this?
MyClass *instance = [[MyClass alloc] init];
[[NSBundle mainBundle] loadNibNamed:#"MyClass" owner:instance options:nil];
Or like this?
MyClass *instance = [[[NSBundle mainBundle] loadNibNamed:#"MyClass" owner:nil options:nil] objectAtIndex:0];
Thanks in advance for any assitance.
The second option is the correct one. The most defensive code you could do is like this:
+ (id)loadNibNamed:(NSString *)nibName ofClass:(Class)objClass {
if (nibName && objClass) {
NSArray *objects = [[NSBundle mainBundle] loadNibNamed:nibName
owner:nil
options:nil];
for (id currentObject in objects ){
if ([currentObject isKindOfClass:objClass])
return currentObject;
}
}
return nil;
}
And call like this:
MyClass *myClassInstance = [Utility loadNibNamed:#"the_nib_name"
ofClass:[MyClass class]];
// In my case, the code is in a Utility class, you should
// put it wherever it fits best
I'm assuming your MyClass is a subclass of UIView? If that's the case, then you need to make sure that the UIView of your .xib is actually of MyClass class. That is defined on the third Tab on the right-part in the interface builder, after you select the view
All you need to do is create the subview via loadNibNamed, set the frame, and add it to the subview. For example, I'm adding three subviews using my MyView class, which is a UIView subclass whose interface is defined in a NIB, MyView.xib:
So, I define initWithFrame for my UIView subclass:
- (id)initWithFrame:(CGRect)frame
{
NSLog(#"%s", __FUNCTION__);
self = [super initWithFrame:frame];
if (self)
{
NSArray *nibContents =
[[NSBundle mainBundle] loadNibNamed:#"MyView"
owner:self
options:nil];
[self addSubview:nibContents[0]];
}
return self;
}
So, for example, in my UIViewController, I can load a couple of these subclassed UIView objects like so:
for (NSInteger i = 0; i < 3; i++)
{
CGRect frame = CGRectMake(0.0, i * 100.0 + 75.0, 320.0, 100.0);
MyView *myView = [[MyView alloc] initWithFrame:frame];
[self.view addSubview:myView];
// if you want, do something with it:
// Here I'm initializing a text field and label
myView.textField.text = [NSString stringWithFormat:#"MyView textfield #%d",
i + 1];
myView.label.text = [NSString stringWithFormat:#"MyView label #%d",
i + 1];
}
I originally advised the use controllers, and I'll keep that answer below for historical reference.
Original answer:
I don't see any references to view controllers here. Usually you'd have a subclass of UIViewController, which you would then instantiate with
MyClassViewController *controller =
[[MyClassViewController alloc] initWithNibName:#"MyClass"
bundle:nil];
// then you can do stuff like
//
// [self presentViewController:controller animated:YES completion:nil];
The NIB file, MyClass.xib, could specify that the base class for the UIView, if you want, where you have all of the view related code (e.g. assuming that MyClass was a subclass of UIView).
Here's one method that I use:
Create a subclass for UIView, this will be called MyClass
Create a view xib file. Open in interface builder,
click File's Owner and in the Identity Inspector, change the class
to that of your parent view controller, e.g.
ParentViewController.
Click the view already in the list of objects and change it's
class in Identity Inspector to MyClass.
Any outlets/actions that you declare in MyClass will be
connected by click-dragging from View (not File's Owner). If you want to connect them to variables from ParentViewController then click-drag from File's Owner.
Now in your ParentViewController you need to declare an instance
variable for MyClass.
ParentViewController.h add the following:
#class MyClass
#interface ParentViewController : UIViewController {
MyClass *myClass;
}
#property (strong, nonatomic) MyClass *myClass;
Synthesize this in your implementation and add the following in your
viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSBundle mainBundle] loadNibNamed:#"MyClass" owner:self options:nil];
self.myClass.frame = CGRectMake(X,Y,W,H); //put your values in.
[self.view addSubview:self.myClass];
}