I am building an application in which the main content is a navigation controller. However, the first time the app loads, I am pushing another view controller on top of that controller (the user has to register etc). This has to be separate from the navigation controller so the user cannot just backtrack. Currently, Navigation through my navigation controller works only on times when the other view controller isn't loaded.
The code to load the 'register' VC if it is needed:
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if ([prefs objectForKey:#"id"]) {
}
else {
RegisterViewController * rvc = [storyboard instantiateViewControllerWithIdentifier:#"registerVC"];
[self.navigationController presentViewController:rvc animated:NO completion:^(void){}];
}
On completion of registration:
[viewCtrl dismissViewControllerAnimated:NO completion:^(void){
NSLog(#"VIEW DISMISSED PROPERLY");
}];
Note: viewCtrl is the viewController pushed on. Also On times when the navigationcontroller stops working 'view dismissed properly' is called. So I know that the view is being dismissed.
Finally, code to push another page normally onto the navigation controller:
ContactViewController * cvc = [self.view.storyboard instantiateViewControllerWithIdentifier:#"contactVC"];
[self.view.navigationController pushViewController:cvc animated:NO];
Just to reiterate, ContactViewController only loads if the registration page wasn't loaded.
Thanks in advance.
You should push other ViewController after dismiss finished:
[viewCtrl dismissViewControllerAnimated:NO completion:^(void){
NSLog(#"VIEW DISMISSED PROPERLY");
ContactViewController * cvc = [self.view.storyboard instantiateViewControllerWithIdentifier:#"contactVC"];
[self.view.navigationController pushViewController:cvc animated:NO]
}];
Related
I am following what I believe is the preferred pattern of allowing the user to edit content in a modal view controller. When the user taps done, the modal VC is dismissed.
However, after the modal VC is dismissed, leaving the original VC that displays the content, the content is not getting updated from core data.
The content is set in viewwillappear, however, dismissing the modal VC does not seem to trigger an update of the screen with the new data. If you do something else and come back the screen does update, so the changes are being made. The screen just isn't redrawn after the modalVC is dismissed.
How can I force an update of the presenting VC once the modal is dismissed?
Presenting VC code that sets label text from managedobject
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.myLabel.text = self.dataItem.content;//where dataItem is a managedobject in Core Data
}
//Here is code that launches the modalVC:
- (void) showEditVC:(id) sender
{
UIStoryboard *storyBoard = self.storyboard;
MyEditVC *editVC =
[storyBoard instantiateViewControllerWithIdentifier:#"editvc"];
editVC.item = _dataItem;
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController: editVC];
[self presentViewController:nav animated:YES completion:^{
}];
}
The code to dismiss the modal is in the Modal VC and triggered by a tap on DONE:
- (IBAction)cancel:(id)sender {
// Dismiss View Controller
[self dismissViewControllerAnimated:YES completion:nil];
}
I have the whole app embedded in UINavigationController. Now there is the Home Screen that has several modules for user to choose. Now, when the user clicks on the module it is NAVIGATED and if user desires to choose some another module from any other modules available, there is a button in navigation bar, which PRESENTS the HomeViewController modally on top of the current module and then user can choose any module from there which will NOT BE PRESENTED instead they will NAVIGATE.
Now what I have done is made a delegate called navigate on HomeViewController and will be override by viewcontrollers of each module and it will take the reference of the new ViewController with it. Then when this method is called I have first dismissViewController the HomeViewController and then navigated to the new ViewController that I have the reference.
Now, what the real issue is that SOMETIMES there is a jerk when navigating from one module to other and sometime it works fine. That why I am not able to debug also. The jerk is that when a module is clicked from HomeViewController, the home screen disappears and the appears again and then it actually navigates.
The code for navigating to a module from HomeViewController is
RadiusSearchViewController *rad = [self.storyboard instantiateViewControllerWithIdentifier:#"RadiusSearchViewController"];
[self.delegate navigate:rad];
This navigation overrided method in all modules is
-(void)navigate:(UIViewController*)uiViewController{
NSLog(#"inside navigate method");
[self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
[self.navigationController pushViewController:uiViewController animated:YES];
}
I assure you that it is coming in this method.
Now the code that presents the HomeViewController modally is
ViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"HomeVC"];
vc.view.backgroundColor = [[UIColor whiteColor]colorWithAlphaComponent:0];
vc.delegate = self;
vc.providesPresentationContextTransitionStyle = YES;
vc.definesPresentationContext = YES;
vc.modalPresentationStyle = UIModalPresentationOverCurrentContext;
vc.fromOutside = true;
NSLog(#"Presneting...");
[self presentViewController:vc animated:NO completion:nil];
The reason for PRESENTING and NOT NAVIGATING the HomeViewController is that it comes on the top of the current module in transparent form which is necessary.
REMEMBER: It happens sometimes not all of the time. Like you can say half of the times.
[self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
[self.navigationController pushViewController:uiViewController animated:YES];
this should be changed to...
[self.presentedViewController dismissViewControllerAnimated:YES
completion:^{
[self.navigationController pushViewController:uiViewController animated:YES];
}];
this will make sure navigation view controller will be called after dismiss is done. check and see if it solves your problem.
I have 3 different viewControllers. first one is the main page where you will see many thumbnail of images. Second controller will only open when an image is clicked. Last ViewController is the login which opens when the user is not logged in but trying to view the second controller or clicked login from the main controller. In the login controller I have a back button to go to main page. Remember the user can access login from two different ways. by clicking on the image or clicking login in the main controller directly. So the issue is the back button does work properly. When the login page is accessed from the main controller directly, then the back button works fine it takes you to the main page. However, if you click on any of the thumbnails the second controller will try to open but since your not logged in the login page will show and when you click the back button to send you to the main controller, it won't go to main instead it will reload the same page and you kind of stock in a loop you have to login or exit the app. any ideas why?
The First ViewController Identifier is set to "mainImages".
- (IBAction)backToMain:(id)sender {
[self dismissViewControllerAnimated:NO completion:nil];
NSString * storyboardName = #"MainStoryboard";
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle: nil];
UIViewController * MI = [storyboard instantiateViewControllerWithIdentifier:#"mainImages"];
UINavigationController *navController = self.navigationController;
if (navController) {
[navController pushViewController:MI animated:NO];
}
}
I also tried this but no luck:
[self dismissViewControllerAnimated:NO completion:nil];
UIViewController * MI = [self.storyboard instantiateViewControllerWithIdentifier:#"mainImages"];
[self presentViewController:MI animated:NO completion:nil];
Try this if you are presenting as modal:
- (IBAction)backToMain:(id)sender {
[self dismissViewControllerAnimated:NO completion:^{
UIWindow *window = [[UIApplication sharedApplication].windows firstObject];
UINavigationController *nav = (UINavigationController *)window.rootViewController;
[nav popToRootViewControllerAnimated:YES];
}];
}
If you need to "go back" from the login view to any other view (in this case 2 of them), you should try using a Modal Segue, a modal segue lose the navigation controller view stack but allow you to dismiss the login view and go back to you last view.
On the first (or thumbnail images view controller) show your new modal view as this:
UIViewController *modalViewController = [[UIViewController alloc] init];
[self presentViewController:modalViewController animated:YES completion:nil];
And then, to dismiss (on login view controller):
- (IBAction)backToMain:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
EDIT
After some more digging around it has become apparent that if i try to call a segue from the view controller class after a fast application switch to/from Safari i get an exception stating that the segue does not exist. It definitely does, in the VC that is loaded on screen, and i have checked the spelling and it's 100% correct.
What makes it even weirder is that if i call that exact same method from an IBAction it works fine.
Here's the code i am using:
-(void)goToPhotoPage{
[self performSegueWithIdentifier:#"twitterAuthComplete" sender:self];
}
If i call this when the app resumes i get an exception, however if i call it from this IBAction, attached to a UIButton, with doing nothing different the segue works as expected and no exception.
- (IBAction)twitterToPhoto:(id)sender
{
[self goToPhotoPage];
}
Arrrrgrghrhhgrgrg!
ORIGINAL QUESTION
I am working on an iPad application that uploads user photos to Facebook/Twitter.
As part of the process i have to jump to Safari to do OAuth for twitter, the app then jumps back via a specific URL and get's the tokens e.t.c
However for some reason when the application re-awakes i cannot trigger any segue's or manually load any VC's.
I have a success block in my AppDelegae which get's called when the upload is complete which calls a method in the VC to close a spinner and segue to the success view.
The spinner stops as expected, but no matter what i try i cannot get the segue to work.
Everything is hooked up, the segue has an ID and i get no error or exceptions in the console, just nothing happens. The only way i can get code triggered segue to work after this point is to use a user trigger one connect to a UIButton, after that one complete they start to work again.
Here is the code with callback in my AppDelegate:
- (void)postToTwitter:(NSString*)postText image:(UIImage*)image
{
NSData *data = UIImageJPEGRepresentation(image, 0.5);
[_twitter postStatusUpdate:postText
mediaDataArray:#[data]
possiblySensitive:nil
inReplyToStatusID:nil
latitude:nil
longitude:nil
placeID:nil
displayCoordinates:nil
uploadProgressBlock:^(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite) {
} successBlock:^(NSDictionary *status) {
ViewController * vc = [[ViewController alloc]init];
[vc triggerSuccess];
} errorBlock:^(NSError *error) {
ViewController * vc = [[ViewController alloc]init];
[vc uploadFail:error];
}];
}
in the VC i have tried the following:
- (void)triggerSuccess
{
[self segueToSuccess];
}
- (void)segueToSuccess
{
[self hideMessage];
[self.navigationController performSegueWithIdentifier:#"approveSegue" sender:self];
}
As well as:
- (void)triggerSuccess
{
[self segueToSuccess];
}
- (void)segueToSuccess
{
[self hideMessage];
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"done"];
[self.navigationController pushViewController:controller animated:YES];
}
In a moment of pure desperation i even tried:
- (void)triggerSuccess
{
[self segueToSuccess];
}
- (void)segueToSuccess
{
[self hideMessage];
//[self.navigationController performSegueWithIdentifier:#"approveSegue" sender:self];
[self dismissViewControllerAnimated:NO completion:nil];
[self.navigationController popToRootViewControllerAnimated:NO];
[self performSelector:#selector(segueToSuccessPart2) withObject:nil afterDelay:0.25];
}
- (void)segueToSuccessPart2
{
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"done"];
[self.navigationController pushViewController:controller animated:YES];
}
I've obviously missed something, and i'm guessing it's to do with the application going into the background and the VC being "unloaded" from memory, and needing re-instatiating, but i'm not sure where to begin with that.
Any help would be greatly appreciated as i'm about to throw the computer out of the window . .
Thanks
Gareth
Edit 1
After doing some more troubleshooting it appears that during this call in the VC:
[self dismissViewControllerAnimated:NO completion:nil];
[self.navigationController popToRootViewControllerAnimated:NO];
self.navigationController is nil . . .
I'm guessing that is likely the cause of the issues i'm seeing?
Edit 2
So as suggested by Kelin, i am trying to make sure i retain the Navigation Controller.
I have done the following in AppDelegte:
#property (nonatomic, strong) UINavigationController *navController;
and
#synthesize navController;
But when i try to access this from the VC using the below code, it's still nil . .
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
UINavigationController *navController = appDelegate.navController;
[self dismissViewControllerAnimated:NO completion:nil];
[navController popToRootViewControllerAnimated:NO];
Edit 3
I have also added this to the AppDelegate, which means the Navigation Controller is no longer nil. But still nothing happens . . .
if (!self.navController)
{
self.navController = [[UINavigationController alloc]initWithNibName:#"Main" bundle:nil];
}
The problem is you trying to perform segue from storyboard using programmatically created View Controller.
You use -performSegueWithIdentifier: sender: completely wrong. In this method "sender" is a button, or any other control which action initialises segue, but not the view controller to be pushed.
Usually segue is created with storyboard. But if you really need to create it manually, call:
-[UIStoryboardSegue initWithIdentifier:source:destination:]
where source and destination are view controllers.
Read this: https://developer.apple.com/library/ios/recipes/xcode_help-interface_builder/articles-storyboard/StoryboardSegue.html
or this (about custom segues):
You can not do both dismiss and pop together either you have to pop or dismiss the view controller and
Every ViewController have it's own NavigationController which is default point out to navigation controller you added to AppDelegate.
So, You also can access it by self.navigationController (Edit 3)
Also check whether you initialised navigation controller in
AppDelegate
self.navController = [[UINavigationController alloc] initWithRootViewController:firstVC];
I have an app whose initial scene is a tab bar controller with 3 tabs. I created a uitabbarcontroller class and set it to that scene (MainTabViewController).
In that class I call presentLogin from the viewDidAppear method and that method reads:
- (void)presentLogin{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if (![prefs stringForKey:#"storedUser"] && ![prefs stringForKey:#"storedPass"]) {
NSLog(#"No user prefs stored");
// BUT WAIT, before all this, lets pop up a view controller for user registration
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
ModalViewController *popupController = [sb instantiateViewControllerWithIdentifier:#"ModalViewController"];
[self presentViewController:popupController animated:YES completion:nil];
} else {
NSString *storedUser = [NSString stringWithFormat:#"User:%#",[prefs stringForKey:#"storedUser"]];
NSString *storedPass = [NSString stringWithFormat:#"User:%#",[prefs stringForKey:#"storedPass"]];
UIAlertView *internetAlert = [[UIAlertView alloc] initWithTitle:storedUser
message:storedPass
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Ok", nil];
[internetAlert show];
}
}
But the modalVC isnt showing for some reason. I get this crash log:
Attempting to begin a modal transition from <MainTabViewController: 0xa55d0d0> to <ModalViewController: 0x15e2b5e0> while a transition is already in progress. Wait for viewDidAppear/viewDidDisappear to know the current transition has completed
I believe you get this error because the tab bar controller is putting the view of the controller in its first tab on screen at the same time you're presenting the modal controller. Instead of presenting it from the tab bar controller, present it in the viewDidAppear method of the controller in the first tab. Call it with no animation to see the modal view controller without seeing the firs tab controller.
Try to add a tiny delay like below:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self performSelector:#selector(presentLogin) withObject:nil afterDelay:0.1];
}
The view of the tabbarcontroller contains the viewHierarchies of the viewControllers that the tab bar itself owns. Maybe something is of because of that. Try to see of you still get the error if you only have one viewcontroller set to the tabbar.