I've got a nib with a view controller root element like this:
so I can position elements relative to the top and bottom layout guides using auto-layout.
When I first tried to load this nib using
SearchViewControllerPro* searchViewController = [[SearchViewControllerPro alloc]initWithNibName:#"SearchViewControllerPro" bundle:[NSBundle mainBundle]];
I got the following run-time exception:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: '-[UIViewController
_loadViewFromNibNamed:bundle:] loaded the "SearchViewControllerPro" nib but the view outlet was not set.'
Googling the error it was pointed out to me, that the file owner of the xib needed to be set to the class of my view controller and the view outlet had to be set to the view object in the xib. If I do so, then I get the following run-time error:
Terminating app due to uncaught exception
'UIViewControllerHierarchyInconsistency', reason: 'A view can only be
associated with at most one view controller at a time! View > is associated with . Clear this association before associating this view with
.'
Does not come as a surprise since the view is associated to both the file owner and the top-level view controller of the nib. But how can I tell the run-time that they are both in fact the very same thing instead of two separate entities?
Edit:
When I try to unpck the ViewController from the nib like so,
NSArray* xibContents = [[NSBundle mainBundle] loadNibNamed:#"SearchViewControllerPro" owner:nil options:nil];
SearchViewControllerPro* mapSearchViewController = [xibContents lastObject];
, it does no good either:
Terminating app due to uncaught exception 'NSUnknownKeyException',
reason: '[ setValue:forUndefinedKey:]: this class
is not key value coding-compliant for the key view.
Temporary solution:
I found a solution, but it is not pretty. Despite the structure as shown in IB, the view controller is not the last object in the xib. So I have:
__block SearchViewControllerPro* mapSearchViewController = nil;
[xibContents enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[SearchViewControllerPro class]]) {
mapSearchViewController = obj;
}
}];
and this seems to work without run-time crashes. However, it's everything but clean code.
how can I tell the run-time that they are both in fact the very same
thing instead of two separate entities?
You can't because they are not the same thing. You have created two SearchViewControllerPro intstances.
You need to either alloc-init a SearchViewControllerPro instance or unarchive one from a nib.
If you decide to create the ViewController in the nib the usual way to access it is the same way you would access any other item (view, button, textfield) that you create in a nib.
Add an outlet to the FilesOwner object, hook up the connection in interface builder and be sure to pass the object when you unarchive the nib.
eg If you want the Object that unarchives the nib to be FilesOwner:
[[NSBundle mainBundle] loadNibNamed:#"SearchViewControllerPro" owner:self options:nil];
The following works as well and (at least to me) is a little more explicit that creating an outlet for the ViewController:
NSArray* xibContents = [[NSBundle mainBundle] loadNibNamed:#"SearchViewControllerPro" owner:nil options:nil];
SearchViewControllerPro* mapSearchViewController = [xibContents objectAtIndex:0];
if (isIPad)
{
[[NSBundle mainBundle] loadNibNamed:#"ABC_iPad" owner:self
options:nil];
}
else{
[[NSBundle mainBundle] loadNibNamed:#"ABC_iPhone" owner:self options:nil];
}
Related
I am beginner doing objective C.
I was trying to understand/copy Jitsi code.
In their AppDelegate.m, they have done something like this
ViewController *rootController = (ViewController *)self.window.rootViewController;
[jitsiMeet showSplashScreen:rootController.view];
return YES;
line: https://github.com/jitsi/jitsi-meet/blob/master/ios/app/src/AppDelegate.m#L59
Here showSplashScreen is this
- (void)showSplashScreen:(UIView*)rootView {
[RNSplashScreen showSplash:#"LaunchScreen" inRootView:rootView];
}
Where RNSplashScreen showSplash method is this
+ (void)showSplash:(NSString*)splashScreen inRootView:(UIView*)rootView {
if (!loadingView) {
loadingView = [[[NSBundle mainBundle] loadNibNamed:splashScreen owner:self options:nil] objectAtIndex:0];
CGRect frame = rootView.frame;
frame.origin = CGPointMake(0, 0);
loadingView.frame = frame;
}
waiting = false;
[rootView addSubview:loadingView];
}
line: https://github.com/crazycodeboy/react-native-splash-screen/blob/master/ios/RNSplashScreen.m
Now, I did the same but they used .xib file and I was using storyboard.
Like in my info.plist I have this
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
So blindly copying the code, I did something like this in my project using storyboard but I am getting following error
someMobileMobile[73326:1790521] *** Terminating app due to uncaught
exception 'NSInternalInconsistencyException', reason: 'Could not load
NIB in bundle: 'NSBundle
</Users/xyz/Library/Developer/CoreSimulator/Devices/1B60169F-B19B-459F-85C6-E80537C7B18B/data/Containers/Bundle/Application/5CDFA673-1009-49C3-8F7D-7F1A0C8E7F57/someMobileMobile.app>
(loaded)' with name 'LaunchScreen''
Primary Question
Is this error because I am using storyboard instead of xib?
Secondary Question
Can someone please also explain the code snippet pasted above? and lastly, what does (ViewController *) mean
see the difference of the following 3 ways to instantiate something from your Interface Builder work.
[NSStoryboard.mainStoryboard instantiateControllerWithIdentifier:#"youridentifier"];
NSStoryboard *launchScreen = [NSStoryboard storyboardWithName:#"LaunchScreen" bundle:NSBundle.mainBundle];
[launchScreen instantiateControllerWithIdentifier:#"yourIdentifier"];
[NSBundle.mainBundle loadNibNamed:#"SomeXibOrNib" owner:self topLevelObjects:nil];
In your example you try to load via the NSNib method but you obviously meant the NSStoryboard LaunchScreen.storyboard
With your code it is in fact looking for a Nib called "LaunchScreen.xib" or "LaunchScreen.nib" which the error tells you does not exist.
and your 2nd Question.
If your info.plist contains a proper entry for a MainStoryboard and this Storyboard is setup correctly, it will instantiate your Window with a ViewController as Root that was given.
Which means after the "Main" Window is loaded from that Storyboard you can access its rootViewController property asking for its ViewController in charge.
UI/NSViewControllers come usually with a corresponding NS/UIView for free, but because subclassed Controllers may be different a typecast is needed to get rid of the warning telling you that. So to make sure the variable rootController will hold a pointer to an object of ViewController that has a property view it is typecasted.
Which works because the given RootViewController is of type "ViewController" and very likely a subclass of NS/UIViewController.
Typecasts also express you know the inheritance works because you guarantee that a view property does exist at runtime.
PS:it is always a bit confusing when chosen classnames are very simplified without any further indication which one is meant. ViewController could have been named "ANViewController" which will help you in larger projects when you have a lot different ViewControllers.
My app crashes when adding Gesture to Custom View (XIB). I am using Xcode version 6.4.
Below are the steps I followed to add a custom subview with tap gesture:
Added an XIB and a UIView subclass (MyView) to my project. And set the XIB class to MyView.
Added a TapGesture to MyView using Interface Builder
Created MyView object (myView) and added it as a sub view using [addSubview:myView].
When I run the app, it crashes
Removed the TapGesture in XIB and run again with no issues.
Code:
[[[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil] firstObject];
Log:
-[UITapGestureRecognizer setFrame:]: unrecognized selector sent to instance 0x7f924a4ce910
Sometimes like this,
-[UITapGestureRecognizer superview]: unrecognized selector sent to instance 0x7f924a4ce910
Please advice.
I solved the issue by replacing the code,
[[[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil] firstObject];
with
MyView *myView = nil;
NSArray *objects = [[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil];
for (id object in objects)
{
if ([object isKindOfClass:[MyView class]])
{
myView = object;
}
}
Note:
If the XIB have a single UIView object only, then the single line code mentioned above is enough. But if the XIB have multiple UIView objects or any Gestures added in XIB, then we need to find out the MyView object from the array returned by the loadNibNamed:owner:options: method.
This is a weird problem..
1.I create a custom “ view ” by xib, and initialize 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"]
======= That works !
2.Then i create another custom “viewController” by xib .
KWMenuVC *menuVC = [[[NSBundle mainBundle] loadNibNamed:#"KWMenuVC" owner:nil options:nil ]lastObject];// error occurs here
[self.view addSubview:self.menuVC.view];
This Xib’s File’s Owner name is “KWMenuVC”(i also try NSObject), and there, i choose the Custom Class of this VC’s view as “UIView”.[This xib named "KWMenuVC.xib"]
======= This one can not work, it stucks in “KWMenuVC *menuVC = [[[NSBundle mainBundle]loadNibNamed:#"menuVC" owner:nil options:nil ]lastObject]; “ and the error is “[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key view”
3.Later, i try another method to initialize the KWMenuVC ,which works well how matter how i change its Xib’s File’s Owner name
KWMenuVC *menuVC= [[KWMenuVC alloc]init];
[self.view addSubview:self.menuVC.view];
======= Works well !
How could this happen?? I used to construct apps' UI by codes. But xib seems more efficient in some cases. Now it seems not a easily understandable stuff.
Thx a lot for your help!
The correct way to initialize a UIViewController with a XIB is:
KWMenuVC *viewController = [[KWMenuVC alloc]initWithNibName:#"KWMenuVC" bundle:nil];
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/#//apple_ref/occ/instm/UIViewController/initWithNibName:bundle:
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.
I'm trying to write a unit test that setup the view controller, I've tried two ways to get the view init, the first way is to use the bundle to load nib content and filter out the one I'm looking for, as follow:
MyViewController *controller = nil;
....
NSArray* nibContents = [[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil];
NSEnumerator *nibEnumerator = [nibContents objectEnumerator];
NSObject* nibItem = nil;
while ( (nibItem = [nibEnumerator nextObject]) != nil) {
if ([nibItem isKindOfClass:[MyViewController class]]) {
controller = (MyViewController*) nibItem;
break;
} else {
NSLog(#"nibItem class is %#", [nibItem class]);
NSLog(#"nibItem is %#", nibItem);
}
}
After these code finished, the controller still be nil, I've insert some logs to inspect the nib class(the %# place holder), and it turns out is the same as MyViewController (at least both classes description did), and I'm so sure these code works very well in the debug/release target, but it's not worked while I run the unit tests.
So is that means the classes is different although their classes description are the same?
The second way I've tried is use the initWithNibNamed:owner:options method, just simply specify the xib file name, but Xcode reply that the nib loaded but view outlet not set, the situation just as the questions describe I found, but I have double check that things have been setup correctly:
In Interface Buildedr, specify the custom class name
Add xib file to the list of copy build resources section in my test target
Link the tableview outlet to the interface file (though I can not drag the view outlet to my class, but it's auto pointed to the tableView in the class)
till now the only way I could get the test pass is manually to set the view controller's view and table view.
MyViewController *controller;
NSArray* nibContents = [[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil];
controller=(MyViewController *)[nibContents objectAtIndex:0];
replace your existing code with this one , Hope this will help you.