Trying to initialize a view controller from storyboard - ios

I have a view controller I placed in the main storyboard but when I try to initialize and load up the view controller my app crashes. The xcode version I'm using doesn't really tell me the errors I get properly, but I did see that it was giving me a sigabrt signal. I don't know why it isn't working.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"SelectDateViewController"];
[vc setModalPresentationStyle:UIModalPresentationFullScreen];
[self presentModalViewController:vc animated:YES];
This is how I called it. All the names I used are correct but I still don't understand why it's crashing.

Set a breakpoint at the last line to verify vc (and also storyboard) are not nil.
presentModalViewController will crash if its viewController argument is nil.
If both are nil, then your storyboard name is likely incorrect (less likely).
If just vc is nil then the identifier is incorrect (either in code or the storyboard). Make sure the VC's Storyboard ID (as circled in the screenshot) matches your description. It is customary not to choose a class name for this (as you might have more than one instance of a given class in your storyboards).
The Storyboard ID is case sensitive and must be unique. I find the sure fire way to set it is type the name in that field after selecting the view controller and pressing return to end editing (don't just click off the field). I've noticed some corruption occurring in storyboards when converting from Xcode 4 to 5 and vice versa, so diving into the plist/xml might also help.
Edit:
Also the timing of when this is called is important, see
presentModalViewController not working.
Make sure the view controller receiving the message has already been shown (i.e. called in viewDidAppear, not viewDidLoad).
As mentioned by Abdullah, you should likely move off the deprecated method to:
[self presentViewController:vc animated:YES completion:nil];
In this case the completion block can be nil without issue, but vc must not be.

[self presentModalViewController:vc animated:YES]; is deprecated.
Try:
[self presentViewController:vc animated:YES completion:nil];

Did you set the initial ViewController from Storyboard?
If yes then Make sure the your Storyboard id is same.
If you are using storyboard you can directly present or dismiss the viewController no need to write the code for it
hope this help you :)

Related

Can't push viewController because navigationController is nil

I can't perform a transition between viewControllers because navigationController is nil. I have logged navigationController in different parts of the class but it returns nil everywhere. In storyboard the viewController is embedded in a navigationController. I have checked other threads on SO with the same issue, but none of the answer has helped or even really made sense to me.
Can't push because self.navigationController is nil
navigationController is nil,when push the viewcontroller
Why is it nil? And how do I solve this? An error message is also returned:
I have tried both using a segue:
[self.navigationController performSegueWithIdentifier:#"experienceDetails" sender:self];
as well as pushing:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Inspiration" bundle:nil];
ExperienceViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"experience"];
[self.navigationController pushViewController:viewController animated:NO];
Nothing happens using push but an error message is produced:
Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior
I have looked for solutions on that error too, but there doesn't seem to be a clear and concrete answer to how to solve it. Again, those suggestions I read and tried didn't work.
I'm really at a loss here. Such a simple thing to do but I'm hindered by something I don't even understand.
EDIT
If it helps, I have a tab bar and in one item I have the viewController that is embedded in a navigationController and from there I want to push to another viewController within the same storyboard.
EDIT
I got this to work:
[self showViewController:viewController sender:self];
very likely because it doesn't use navigationController. Its presented as modular though and is not part of the navigation stack, which is not something I want. Just good to know that things would work if navigationController wasn't nil.
I reproduced the described behaviour when segue experienceDetails was created from navigation controller... and here is solution
1) deleted segue experienceDetails form navigation controller
2) created push segue with identifier experienceDetails from included view controller to detail view controller
3) in view controller performed segue from self as below
[self performSegueWithIdentifier:#"experienceDetails" sender:self];

Pushing a new VC into the navigationController seems to have no effect

This problem sounds quite basic but I don’t understand what I am overlooking.
I am trying to push a new view controller into a navigation controller, however the topViewController remains unaffected.
#import "TNPViewController.h"
#interface TNCViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
#implementation TNCViewController
-(void)userDidSelectNewsNotification:(NSNotification*)note
{
TNPViewController *nextViewController = [[TNPViewController alloc] init];
[[self navigationController] pushViewController:nextViewController animated:YES];
UIViewController *test = [[self navigationController] topViewController];
}
The test shows an instance of TNCViewController instead of TNPViewController. How is this possible?
UPDATE
Thanks for everyone's participation. The method name indicating notifications is a red herring. I found the problem, as Stuart had mentioned previously but deleted later on. (As I have high reputation score, I still can see his deleted post).
My initial unit test was this:
-(void)testSelectingNewsPushesNewViewController
{
[viewController userDidSelectNewsNotification:nil];
UIViewController *currentTopVC = navController.topViewController;
XCTAssertFalse([currentTopVC isEqual:viewController], #"New viewcontroller should be pushed onto the stack.");
XCTAssertTrue([currentTopVC isKindOfClass:[TNPViewController class]], #"New vc should be a TNPViewController");
}
And it failed. Then I set a breakpoint and tried the test instance above and it still was showing the wrong topviewcontroller.
At least the unit test works if I change
[[self navigationController] pushViewController:nextViewController animated:YES];
to
[[self navigationController] pushViewController:nextViewController animated:NO];
A better solution is to use an ANIMATED constant for unit tests to disable the animations.
This doesn't really answer your question about why your navigationController is not pushing your VC. But it is a suggestion about another possible approach.
You could instead add a new VC on the Storyboard and simply activate the segue when the userDidSelectNewsNotification method is activated. Then change the information accordingly to the event in the VC, specially since you are initializing it every time anyway.
This is something of a stab in the dark, but the issue is hard to diagnose without more information.
I see you're trying to push the new view controller in response to a notification. Are you sure this notification is being handled on the main thread? UI methods such as pushing new view controllers will fail (or at least behave unpredictably) when not performed on the main thread. This may also go some way to explaining the odd behaviour of topViewController returning an unexpected view controller instance.*
Ideally, you should guarantee these notifications are posted on the main thread, so they will be received on that same thread. If you cannot guarantee this (for example if you're not responsible for posting the notifications elsewhere in your code), then you should dispatch any UI-related code to the main thread:
- (void)userDidSelectNewsNotification:(NSNotification *)note
{
dispatch_async(dispatch_get_main_queue(), ^{
TNPViewController *nextViewController = [[TNPViewController alloc] initWithNibName:#"TNPViewController" bundle:nil];
[self.navigationController pushViewController:nextViewController animated:YES];
});
}
Also, it appears you are not initialising TNPViewController using the designated initialiser (unless in your subclass you are overriding init and calling through to initWithNibName:bundle: from there?). I wouldn't expect this to cause the transition to fail entirely, but may result in your view controller not being properly initialised.
In general, you might be better creating your view controllers in a storyboard and using segues to perform your navigation transitions, as #Joze suggests in his answer. You can still initiate these storyboard segues in code (e.g. in response to your notification) with performSegueWithIdentifier:, but again, be sure to do so on the main thread. See Using View Controllers in Your App for more details on this approach.
*I originally wrote an answer trying to explain the unexpected topViewController value as being a result of deferred animated transitions. While it is true that animated transitions are deferred, this does not prevent topViewController from being set to the new view controller immediately.

Present View From Storyboard Programmatically

I am very new to using Storyboards, and am trying to learn. In my app, I want to change from one view to another, modally, without pushing any buttons, i.e. timer runs down then performs the action. I added a notification observer in the main view, and in my storyboard, I added a new view controller, and associated the Custom Class with it called URLWebViewController. In code I wrote out:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
URLWebViewController *myVC = (URLWebViewController *)[storyboard instantiateViewControllerWithIdentifier:#"URLWeb"];
[self presentViewController:myVC animated:YES completion:nil];
But I get the warning message
Incompatible pointer types sending 'URLWebViewController' to parameter of type 'UIViewController'
Like I said, I am very new to Storyboards, and here is what it looks like currently:
Your screenshot indicates you have not created a segue between the two view controllers. Do so by click-and-drag from one view controller to the other. In the attribute inspector, give your newly-created segue an identifier, i.e., a string that names it.
To trigger this segue programmatically, in your source view controller (the one you are segueing from) just call performSegueWithIdentifier and pass in the identifier you chose.
It's really simple! I didn't understand the order but let's say you want to pass from ViewController to WebViewController (the name from your storyboard)..Just ctrl-drag from ViewController to WebViewController to create a segue and select modal as the type.
Now click on the line that connects your view controllers and in the right panel give it an identifier. Now in your code just use the method performSegueWithIdentifier using the identifier you wrote.

After using presentViewController existing segues can't be found

I am returning to my login view using the code below. The view loads correctly and everything looks fine. All buttons work etc.
JALoginViewController *loginVC = [[JALoginViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:loginVC];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentModalViewController:navigationController animated:YES];
However, if a user tries to log in again, the segue that takes them to the next scene can't be found.
I'm using performSegueWithIdentifier if the users login credentials are correct, like this:
[self performSegueWithIdentifier:#"loginSegue" sender:self];
This is the error I receive:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver (<JALoginViewController: 0x8d614b0>) has no segue with identifier 'loginSegue''
I've done lots of searching on Google and through the docs for the solution to this, the closet I've got (at least I think) is this question. The explain and solution sound like they could be correct and relevant, but I can't put them into practice.
Documents I've read and tried:
initWithRootViewController
popToRootViewController - The current root view controller is for a tab bar - not the login scene I need so as far as I'm aware I can't use this.
popViewControl
pushViewControl - This works to an effect, I don't think it is the correct way though. I don't want there to be navigation bar and I don't want my tab bar to appear when the user returns to the login scene.
I've tried various methods with limited / no effect. At this stage any help would be greatly appreciated.
Please let me know if I haven't provided enough information.
Thanks
JA
Edit - Zoomed out image of storyboard
![Zoomed out image of Storyboard][1]
On the basis of the screen snapshot of the revised question, from your rightmost red-highlighted scene, you should be able to:
[self.tabBarController dismissViewControllerAnimated:YES completion:nil];
and you'll be back at that initial screen (I'm assuming you did modal segue from initial screen to your tab bar controller).
Original answer:
If you want to manually push to a view controller, rather than creating it via alloc/init, you should use
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"loginsSceneStoryboardIdHere"];
or, if that view controller was the "initial" scene (the one with the simple arrow coming in from the left), you could use
UIViewController *controller = [self.storyboard instantiateInitialViewController];
And you shouldn't be manually creating the navigation controller, either. If the loginVC needs a navigation controller, you should embed that scene in a navigation controller right in the Interface Builder, then give that new navigation controller its own unique storyboard identifier, and then you can
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"navControllerSceneStoryboardIdHere"];
[self presentViewController:controller animated:YES completion:nil];
I must confess that I'm worried by this whole "return to login via pushViewController" construct. I assume you know that you're not "returning" to it, but creating a new copy of it. If you push/modal from A to B and then B to C and then C to A, you're holding 4 views and their controllers in memory, two copies of A and one of B and one of C (which is, obviously, not good). I just wanted to make sure you don't have a circular set of segues and/or push/presentViewController references.
If the login is the initial scene in your app and if you've been doing only push segues (no modal segues along the way), you can do a:
[self.navigationController popToRootViewControllerAnimated:YES];
That will take you to the top level view controller, and it will pop off and release all of the intervening scenes.
If you're using iOS 6, you can avail yourself of the unwind segue, which can achieve the same functionality, but it doesn't care whether the preceding segues were pushes or modals.
There are lots of ways of skinning the cat, but generally doing a new presentViewController to the first scene in your storyboard is a very bad idea.

How to load a scene in a storyboard from a view controller

I am developing an application that loads a web page (using UIWebView) using Storyboard (I do know nothing about previous xib neither). I have already created a view controller for that UIWebView and everything works fine. The thing is: since previous versions of iOS don't allow to upload files, I need to make a new view (scene I thought it is called) that allows the user to pick and post a picture. I am able to develop both views separately and they work as expected but now I need to connect them based on event triggered when user wants to post a picture to the server. Using shouldStartLoadWithRequest I can catch that action, then I need to redirect to new view (which contains image picker and a button in order to upload the selected image) if iOS version is below 6.0 but I am really lost when it comes to load the new controller to show that view. Using buttons it is trivial but I don't know how to called inside the code. So far, I have a view controller linked to that scene and I have tried these ways:
WritePostViewController *postViewController = [[WritePostViewController alloc] init];
[self.navigationController pushViewController:postViewController animated:YES];
And even calling the storybard:
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
[sb instantiateInitialViewController];
UIViewController *vc = [sb instantiateViewControllerWithIdentifier:#"WritePostView"];
vc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:vc animated:YES completion:NULL];
The first approach does nothing and second one shows this error log:
* WebKit discarded an uncaught exception in the webView:decidePolicyForNavigationAction:request:frame:decisionListener: delegate: Storyboard () doesn't contain a view controller with identifier 'WritePostView'*
I have been browsing and reading a lot but nothing solves my problem. For sure this a problem with my not-so-large knowledge about iOS but I am really stuck. I will thank any help.
By the way, I need to come back after posting the file but I could imagine it is the same way opposite direction, right?
If your code is inside a view controller (which it seems to be), you can get the current storyboard with self.storyboard. You also don't need instantiateInitialViewController because, if all your UI is coming from the same storyboard, it has already gone through the loading of the initial controller.
As for the actual error, it's complaining that #"WritePostView" isn't a recognized name for any view controller in the storyboard. Note that what it looks for here is not the class name for the controller but the Storyboard ID for the controller. (Which makes sense since you could have different "scenes" with the same type of controllers.)
Ok, it seems is solved. I just have added a segue between scenes
and then I just need to add this one:
[self performSegueWithIdentifier:#"writePostViewSegue" sender:self];
and it works!. Anyway, I am not sure if it is the way to do it, so if someone knows better, please let me know.
Cheers

Resources