Nib from different class - ios

I have tried several other examples, on this site and others, but for the life of me I cannot get this to work.
I have a NIB file which is a part of the class "ViewController2". I need to use a few of the views in this NIB file in my "ViewController1" class.
Each time I call
[[NSBundle mainBundle]loadNibNamed:#"ViewController2" owner:self options:nil];
it causes a crash, saying that one of my UIButtons is not KVC compliant, but I have properly linked all the buttons and outlets, to no avail.
Any help would be extremely appreciated! Thank you in advance!

that is faster at least 3 times than NSBundle:
ViewController2 *_viewController2 = [[[UINib nibWithNibName:#"ViewController2" bundle:nil] instantiateWithOwner:nil options:nil] objectAtIndex:0];
it will load the nib file and it will create a new instance of the ViewController2 class.
NB: if you'd like to use an already existing instance of the ViewController2 class for the nib, you will need to set that instance as owner.

When you load a nib and set the owner: property to self, iOS tries to wire up the outlets with KVC. If you don't have a UIButton with a keyPath that matches the one in the xib file inside of the class you're currently in, you'll get the crash. You need to set owner: to nil.

Related

What is File’s owner in XIB in this case?

I've searched for similar questions for quite a while, most of which mentioned UIViewController's xib stuff. I tried to add a xib file for my custom viewController model,and found that its Xib’s File’s Owner should be my custom viewController model's class--that is reasonable. But why the situation differs when i create a xib for my UIView model-- an example as follows:
I create my UIView model which named "KWView"(KWView.h and KWView.m)
then i create xib for this model, initializing it by
KWView *oneView = [[[NSBundle mainBundle] loadNibNamed:#"KWView" owner:nil options:nil ]lastObject];
This Xib’s File’s Owner name is “NSObject”(then i try any other more,whatever i choose, it runs smoothly),and there, i choose the view’s Custom Class as “KWView”[This xib named "KWView.xib"]
Questions are :
1.Whatever i change the Xib’s File’s Owner name of my custom view, it works. If so, what the work does this File’s Owner do here, or saying, why can this happen?
2.Generally, should i set the custom view's Xib’s File’s Owner to my custom view's class or the viewController's class which this view is going to be added to ? or just set it "NSObject"?
The answer to your question depends how you intend to extract the view from the nib at nib-load time. You are going to extract it, as you have helpfully shown us, like this:
KWView *oneView =
[[[NSBundle mainBundle] loadNibNamed:#"KWView" owner:nil options:nil]
lastObject];
That means you aren't using the owner: for anything here — it is nil. Therefore you can leave the nib's file's owner at NSObject.
The purpose of the File's Owner is to permit you to establish, in the nib, Action and Outlet connections between the view (or its subviews) and the object that will be the real owner at load time — like a view controller (owner) and its view (the main view of the view controller). But in your case, there is no such real owner and no such Action or Outlet connections.

Is there a shortcut to accessing a xib's initial attributes?

I do a lot of animations with UIKit and I frequently store any animated view's initial frames in viewDidLoad to always have a reference to the frame as it appears in a xib.
This is kind of smelly and seems like the kind of thing that would be automated, but I can't seem to find any info on this. Is there a property on UIView that stores initial xib frame sizes? Or maybe a UIKit utility method that scans the xml of a xib for it's attribute values by name?
So while I could not find any way to access an interface builder file's initial attributes (unless you count writing an IB XML parser a way), I found a brilliant alternative from Do I need to set heightForRowAtIndexPath if I am using a custom UITableViewCell?. Instead of accessing some constant values from an object itself (I was hoping for a self.attribute.nib.value), you can make two outlets per IB object- one for manipulating, and one for a prototype- which will hold all the original values of the xib.
To makes things simple, just create an(other) outlet for your nib and override its getter.
For example, if we have a LargeCell.xib, we would create an outlet for largeCell and another for largeCellPrototype.
Then we would make a lazy accessor for this prototype to ensure the prototype is not nil and only loaded once.
- (LargeCell*)largeCellPrototype
{
if (!_largeCellPrototype)
{
[NSBundle mainBundle] loadNibNamed:NSStringFromClass([LargeCell class]) // Presuming you don't name files with reckless abandon
owner:self
options:nil];
}
}
now getting the initial values is as simple as
CGFloat initialViewHeight = self.largeCellPrototype.frame.size.height;

setting file owner of a xib file

Hi I am reading a book where I had to deal with such situation.
I created a XIB file named HeaderView.xib. Then I connected the File Owner of this XIB
file to ItemsViewController. All is fine so far. I also connected
some outlets of the ItemsViewController with views on the XIB.
Now, in the ItemsViewController I had to call such code:
- (UIView *)headerView
{
// If we haven't loaded the headerView yet...
if (!headerView) {
// Load HeaderView.xib
[[NSBundle mainBundle] loadNibNamed:#"HeaderView" owner:self options:nil];
}
return headerView;
}
Code above would set headerView outlet of ItemsViewController point to the corresponding
view on the XIB file (the one I made connections with on XIB file).
My question is, why did I have to, two times, specify the owner? (e.g., once in the XIB as I mentioned in the start of this port, and second time, above in the code, e.g., owner: self).
You did not specify the file owner twice:
The first time (in the XIB file) you specified the type of the file owner; this is necessary for the Interface Builder to know which outlets it can connect.
The second time (in the Objective C code) you specified the instance of the owner. This is necessary at runtime to know the object into which the outlets are connected.
Specifying the owner in the XIB tells Xcode what the controller understands (what outlets it has) so that it can offer the connections to you. This is at a class level.
Specifying the owner in code tells the unarchiving process which instance of the controller is actually going to fulfil that role and should therefore have the connections established to the new instance(s) which are unarchived from the NIB.
I found it... When the world is not using XIB(s) anymore...
Open the XIB or NIB file in your favourite text editor
you will find this line there...
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="MyCalssName">
Change the class name, save the file and your file's owner will be changed.
Caution: Close Xcode (To auto reload the changes) and also take a backup in case you mess up something.

Using registerNib:forCellReuseIdentifier: prevent me from interacting with control inside my table cell

I am using registerNib:forCellReuseIdentifier: to load cell from nib b/c I was told that with that I can always get a cell from [tableView dequeueReusableCellWithIdentifier:] and thus reduce those boilerplate codes.
I do always get a cell but the problem is that my IBAction (a button in my cell) started to fail by raising the exception 'NSInvalidArgumentException', reason: xxx unrecognized selector sent to instance.
If I remove the call registerNib:forCellReuseIdentifier: and add these codes as usual (below), everything works fine. So I guess the problem was caused by this call.
So what did I do wrong?
BTW, I set the file owner of my cell nib file to my table view controller. "Programming IOS 5" said "There is no need to specify a File’s Owner class in the nib" in this case, but since I need to set my IBAction I still set it. I don't think this will cause the problem, right ?
//The "old" codes without calling registerNib:forCellReuseIdentifier:
UITableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:#"MyCell"];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MyCell"
owner:self
options:nil];
cell = [nib objectAtIndex:0];
...
Your IBAction is in the controller that loads the cell, I take it? Don't think that's going to work. If you look at the call stack when dequeing -- as I'm doing for a spot of debugging right now -- you'll see
- [YOURCELLCLASS awakeFromNib];
- [UINib instantiateWithOwner:options:]
- [UITableView dequeueReusableCellWithIdentifier:]
How does the table view know what controller object it should be passing to UINib as the owner to match the class you declared it as? Doesn't appear there's any way for it to do that. And indeed, when I replicate your button above and check its target in -awakeFromNib, that target is very definitely not the controller; thus your crash. Interestingly, it doesn't appear to be the table view either, which is what I would have expected; it's an NSObject whose address doesn't match anything obvious at first glance.
shrug Any-ways, the lesson appears to be that you shouldn't use the file's owner for anything in this xib. Move your logic into a cell custom class, or set the control(s)' targets yourself after dequeing.
I hit the problem again and did some further investigation. And here is what I found,
Setting File's own in the cell nib is pointless(and dangerous). I set file's own in xib to hook up my IBAction to MyCell class, but then I realized that the owner provided in [[NSBundle mainBundle] loadNibNamed:owner:options:] is the one runtime honored. It is easy to verify that, just provide 2 IBActions (same method name), one in MyCell and the other in owner and watch which one was called.
There are many articles said you can create cell by calling cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; But I think this was wrong. To use the cell in xib file you have to call [[NSBundle mainBundle] loadNibNamed:owner:options:]
Now I am kinda believe this is a bug that using registerNib:forCellReuseIdentifier: will set your IBAction to the wrong target or you just can not use it with IBAction together. Like #alexcurylo said when you used it there is no way to set file's owner correctly (even you had set it in xib). The answer to the question here UITableView registerNib:forCellReuseIdentifier: said runtime just set the owner to nil. But the weird thing is that IBOutlet is always set correctly.
So if you just need to update IBOutlet's property, e.g. update UILabel's text, using registerNib:forCellReuseIdentifier: is simpler b/c you always get a cell, no need to check it again nil. But if you need action, you have to call addTarget:action: after you get your UIView(with either [[NSBundle mainBundle] loadNibNamed:owner:options:] or registerNib:forCellReuseIdentifier:)
To create a customized table cell you can either subclass UITableViewCell and place controls in codes or just use cell xib file, but no need to do both(which was also suggested by many articles). Because you won't get your IBAction right and you can easily get your subview without IBOutlet. It is probably simpler to just use xib. When you use xib file, remember to set the tag of your subview in cell then you can get UIViews with [cell viewWithTag:]
I think there are two main options if you'd like to use IB to connect your actions:
Don't use registerNib:forCellReuseIdentifier: - load the nib manually in your cellForRowAtIndexPath: using the old-style code.
Send the IBActions to First Responder instead of Files Owner, in flagrant violation of Guy English's advice.

UIView with Separated .h, .m and xib files

I want to create a UIView that will appear in several different UIViewControllers upon user action. For example, a "How To" pop view that whenever the user clicks on a "?" button it pops holding the relevant information about the specific action the user is interested in. The view may also have other elements like a "done" button and a UITextView that will hold the text, etc...
Intuitively, it makes me think of creating a separated UIView with .h, .m and xib files and have each UIViewController that requires this UIView will simply alloc+init it and do [self.view addSubView:flexUIView] or insert it with animation. Well... it doesn't work...
I couldn't find any tutorial that explains how to do something like that.
Is anyone familiar with this approach and have some directions?
If not, what is the common approach for such a scenario?
Alloc init will not load the nib of your custom UIView as in ViewControllers.
You should load the nib using the below code after alloc init
Suppose you have CustomView.h, CustomView.m, CustomView.xib
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:self options:nil];
yourView = (CustomView *)[nib objectAtIndex:0];
I also found this tutorial that also has a link to the projects files and it gives a better idea regarding to how to do this:
Creating Reusable UIViews
Here's a tutorial I wrote for creating a custom UIView with .xib, .h, .m files. I've added two sample projects showing Interface Builder and programmatic approaches.
https://github.com/PaulSolt/CompositeXib

Resources