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.
Related
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'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];
}
I have the next code:
- (void)func
{
MyViewController *ctrl = [MyViewController new];
[ctrl doSmth];
[self presentViewController:ctrl animated:NO];
}
//------------
- (void)doSmth
{
CGRect *rect = self.view.frame;
// Do something with rect
self.view.frame = rect;
}
Ok, I know, that when -[UIViewController view] is equal to nil then it's being created. And this code did work before my changes (only buttons and labels visibility, nothing extraordinary) and now it throws SIGABRT on self.view, it looks like it cannot be created. Suddenly. If I revert my changes, all work like a magic. And even if I won't call this function it'll crash on presenting and in Summary it'll show that view = 0x00000000.
I've got only one question: WAT?
I've found the error.
After XCode's “refactoring” (renaming some IBOutlets) old outlets had been marked with the ! sign but not removed and nothing XCode told me about it.
What I mean by !-s, is Interface Builder showing not valid outlets with an icon. Not valid outlet means that you have a connection between a view and a property / instance variable that does not exist. So when the NIB will be loaded in runtime, the application will crash, because there are no such property / instance variable.
So if you run in such error, you should either remove the outlet, or add the property / instance variable with the same name to class which you have linked the XIB to (usually a File's Owner).
I have an UIViewController with several subviews in its view property (UISearchbar and several UIButtons). The UIButtons hooked up to typical IBActions like -(IBAction)buttonPressed:(id)sender for the UIControlEventTouchUpInside state - it doesn't matter if I do it in IB or programmatically.
- (void)viewDidLoad {
MUZTitleViewController *title = [[MUZTitleViewController alloc]
initWithNibName:nil bundle:nil];
self.navigationItem.titleView = title.view;
}
In my project there's also an UINavigationController. When I set the navigationItem.titleView of the UINavigationBar to the view of my UIViewControllers view I get an EXC_BAD_ACCESS exception, as soon as I tap one of the button. I don't know why this is.
I uploaded a small sample project to illustrate my problem: Test010.xcodeproj (it's ARC enabled)
More and more I come to the conclusion that it's not a good idea to use the UIViewControllers view and assign it to the titleView but I don't see any alternative here.
Edit: Sorry, the sample project commented out the call which causes the exception. I reuploaded the linked project file.
Edit^2: As PengOne pointed out I've skipped the exact error message I got:
2011-09-10 23:09:50.621 Test010[78639:f803] -[CALayer buttonPressed:]: unrecognized selector sent to instance 0x9254ae0
2011-09-10 23:09:50.623 Test010[78639:f803] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CALayer buttonPressed:]: unrecognized selector sent to instance 0x9254ae0'
Have you tried setting NSZombieEnabled to YES? If I do this, the console shows the following output:
2011-09-10 22:56:23.329 Test010[6481:ef03] *** -[MUZTitleViewController
performSelector:withObject:withObject:]: message sent to deallocated
instance 0x7a7ff70
As the project is ARC enabled, the controller seems to get deallocated some time after this line:
MUZTitleViewController *title = [[MUZTitleViewController alloc] initWithNibName:nil bundle:nil];
I am not sure what the best solution is, but a property definitely helps to prevent the exception like so:
// MUZDetailViewController.h
#property (strong, nonatomic) MUZTitleViewController *title;
// MUZDetailViewController.m
#synthesize title;
self.title = [[MUZTitleViewController alloc] initWithNibName:nil bundle:nil];
self.navigationItem.titleView = title.view;
The problem that you were having with ARC can also be resolved by setting the initial view controller of your application as your main window's rootViewController property instead of using addSubview.
Doing this avoids the need to add each custom view controller as a property.