I am working on an iOS app.
The view hierarchy is like,
-- HomePageNavigationView
-- LoginNavigationView
-- UserProfileView
The root view controller of the appDelegate is the HomePageViewController
And in the viewWillAppear of the homepage, I check if there is a validate token.
If there is no validate token, then I present the Login Navigation View as modal view.
After the Login/SignUp process, the user profile is required to be edited. The UserProfileView is presented as modal view.
The other circumstance is, after opening the App, the validate token is found, but the user profile is not completed, so I have to present the user profile as modal view upon the Homepage.
So how to achieve this hierarchy, how can I dismiss view controller twice when I'm presenting userProfile view upon the login navigation view which is presented upon the homepage?
So you can create block property for UserProfileViewController
#property(nonatomic,copy)void(^onDismissed)();
In LoginViewController:
UserProfileViewController *profileViewCtr = [[UserProfileViewController alloc] init];
profileViewCtr.onDismissed = ^{
[self(YOUR_LOGIN_VIEWCTR) dismissViewControllerAnimated:NO completion:nil];
};
and in UserProfileView:
[self(YOUR_PROFILE_VIEWCTR) dismissViewControllerAnimated:YES completion:^{
if (self.onDismissed) {
self.onDismissed();
}
}];
or more simply in UserProfileView
[self(YOUR_PROFILE_VIEWCTR) dismissViewControllerAnimated:YES completion:^{
[self.presentingViewController dismissViewControllerAnimated:NO completion: nil];
}];
Related
I launch a modal view controller VCM embedded in a Navigation controller from VC1.
The modal VC has a button to go to another View Controller, VC2.
if the user presses the button to go to VC2, I need to simultaneously dismiss the modal and also change the sending from VC1 to VC2. (It's important to dismiss the modal to get rid of it, so I don't have VCs accumulating on the stack.)
Here is the action method in the modal attached to the VC2 button.
- (IBAction)VC2ButtonPressed:(id)sender {
[self dismissViewControllerAnimated:true completion:nil];
//Above line works fine
[VC1 performSegueWithIdentifier:#"showVC2" sender:VC1];//No known class method error
}
I can dismiss the modal without any problem (first line) but can't figure out how to simultaneously segue to VC2. There is a storyboard segue in VC1 named 'showVC2' for what it's worth. I do not have any relevant class methods currently in the sending VC. Should I create a special class method? Or how can I invoke a method in the VC that launched the modal.
Edit:
I also tried:
[self.presentingViewController performSegueWithIdentifier:#"showVC2" sender:self];
This compiles but gives runtime error () has no segue with identifier 'showVC2''
I think this is because VC2 is embedded in a tabbarcontroller.
Edit2:
I tried putting code in completion handler as suggested by #Oscar but while code gets fired, VC2 does not load
[self dismissViewControllerAnimated:YES completion:^{
[self gotoVC2];
}];
new method
-(void)gotoVC2 {
LogDebug(#"gotoVC2 called");//DOES log to console
UIStoryboard *storyBoard = self.storyboard;
IDFeedVC *VC2 =
[storyBoard instantiateViewControllerWithIdentifier:#"vc2"];
[self presentViewController:VC2 animated:YES completion: nil];
}
Thanks for any ideas.
I have 2 UIViewController's presented with [self presentViewController:viewController animated:YES completion:nil];, I want to dismiss the first one of them, without animation (It's not visible to the user anyway) and when the second one (currently visible) will be dismissed, the user will see the parent view controller who present them both.
- Parent
- First -> Dismiss first without animation
- Second -> Dismiss second with animation
How can I do that?
With your current view controller hierarchy if first view controller will be dismissed it will dismiss second view controller automatically. If you don't want that behaviour than make parent present second view controller. You can do that from first view controller by using [self.presentingViewController presentViewController:secondViewController animated:YES completion:nil]
Why do you want to do this?
You should do it like this for cleaner view hierarchy and better user experience:
Present first view controller :
[self presentViewController:viewController1 animated:YES completion:nil];
Dismiss first & present second view controller :
__weak MyViewController *aBlockSelf = self;
[self dismissViewControllerAnimated:YES completion:^{
[aBlockSelf presentViewController:viewController2 animated:YES completion:nil];
}];
I got View A present -> Modal View B, B present -> Modal View C Modal View D -> Modal View E . It's a main View A and then succession of Modal view.
So each time I present new modal C or D I want to dismiss the previous one (In this way when I close the new presented Modal It will show me The Main view A always not the previous Modal).
If I'm in B and I want to present C then I have to present C first
then In background I dismiss B.
If I'm in C and I want to present E then I present E first and then
dismiss C in background
I've tried this code :
if (_openNextView) {
if ([[NSUserDefaults standardUserDefaults] stringForKey:#"generatedCode"]) {
NSLog(#"generated Code %# : ", [[NSUserDefaults standardUserDefaults] stringForKey:#"generatedCode"]);
NSLog(#"phone Number %# : ", [[NSUserDefaults standardUserDefaults] stringForKey:#"phoneNumber"]);
// Present C View
RegisterSecondViewController *registerSecond = [[RegisterSecondViewController alloc] initWithNibName:#"RegisterSecondViewController" bundle:nil];
//[self presentNatGeoViewController:registerSecond];
[self presentViewController:registerSecond animated:YES completion:nil];
} else {
RegisterFirstViewController *registerFirst = [[RegisterFirstViewController alloc] initWithNibName:#"RegisterFirstViewController" bundle:nil];
//present D view
//[self presentNatGeoViewController:registerFirst];
[self presentViewController:registerFirst animated:YES completion:nil];
}
// Dismiss privious View (the current view before presenting new one)
[self.parentViewController dismissViewControllerAnimated:YES completion:nil];
Also I've o add this code in the new presented Modal view
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
But didn't worked and the view is not dismissed
I'd modally present a UINavigationController and use it to present the content views. Then, when you currently present new modal views instead you would just set the viewControllers (animated) of the navigation controller.
This will give you an animated view of progression through your setup process but won't allow the user to go back and will deallocate the VCs when they're complete.
UIViewController has a property named presentedViewController :
presentedViewController
Property The view controller that is
presented by this view controller, or one of its ancestors in the view
controller hierarchy. (read-only)
This means, you can only present one modal controller at a given time. In order to present another one, you need to dismiss the current one first.
If you want to have a custom navigation, I recommend you to create a custom container view controller since the normal modal presentation won't fit your use case. Here is a guide from Apple.
You can dismiss the current vc after that presenting a new vc. Try this:
[self presentViewController:vc animated:YES completion:^{
if (self.presentingViewController != nil) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:NO completion:nil];
});
}
}];
I have a UITabBarController-based application in which my login page is presented modally by the default tab's VC(UITabBarController index 0), and is dismissed modally by dismissViewControllerAnimated:.
From my settings page I have a logout button, and if the user immediately logs back in I have to call [self.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil]; and then reset the UITabBarController's selectedIndex property in order to get rid of the settings page and also go back to the starting tabBar tab. So I do this:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString* responseString = [[NSString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding];
/*Succesful Login*/
if([responseString isEqualToString:#"success"])
{
UITabBarController *tabBarController = (UITabBarController *)self.presentingViewController;
if (tabBarController){
NSLog(#"I have a tab bar~");
[self.tabBarController setSelectedIndex:0];
}
else{
NSLog(#"I don't have a tab bar~");
}
//dismisses from a second, immediate, re-login attempt
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
//dismisses from first login attempt
[self dismissViewControllerAnimated:YES completion:nil];
NSLog(#"Succesful login.");
}
In this case, the method setSeletedIndex: does not work because this methodology essentially creates a new instance of the tabBarController, when it is the original that is actually displayed. I CAN NOT seem to access the already existing UITabBarController which was before my modally presented login page.
EDIT:
I have tried using this conditional to check its existence:
if (self.tabBarController){
NSLog(#"I have a tab bar~");
//[self.tabBarController setSelectedIndex:0];
}
else{
NSLog(#"I don't have a tab bar~");
}
where I have even changed self.tabBarController to self.presentingViewController.tabBarController and get nil, and self.presentingViewController.presentingViewController.tabBarController and get nil, how am I to access the previously existing UITabBarController?
Objective: Access the application's UITabBarController that is present before the presentation of the login page. (Access the Appdelegate's UITabBarController)
Your use case is a natural fit for delegation.
In the header file for your Login, define a delegate. E.g.,
#class Login
#protocol LoginDelegate <NSObject>
#required
- (void) userDoneWithLoginController:(Login *) controller;
#end
#interface Login: UIViewController
#property (weak, nonatomic) id<LoginDelegate> viewDelegate;
#end
Then your presenting view controller segue can set itself as the delegate to the login view controller by setting this "viewDelegate" object inside the prepareForSegue for the presenting view controller before control passes to the Login page. [Let me know if you want this clarified.]
In addition the presenting view controller will implement the required method--which can simply dismiss the login page when called
-(void) userDoneWithLoginController:(Login *) controller
{
[controller dismissViewControllerAnimated:YES completion:nil];
}
Once you've done this, the login page simply calls its delegate method when it is done, thusly:
[self.viewDelegate userDoneWithLoginController:self];
Viola!
This is preferred way to dismiss a modal view in iOS. The calling controller should be the controller dismissing the view. Try to avoid having view controllers dismiss themselves. Instead, have their calling/presenting controllers (delegates) dismiss them.
The following should work.
// check that the vc presenting your current modal vc is a tab bar controller
if ([self.presentingViewController isKindOfClass:[UITabBarController class]]) {
// get the pointer of type UITabBarController
UITabBarController *tabBarController = (UITabBarController *)self.presentingViewController;
// set to the desired tab
tabBarController.selectedIndex = 2;
}
// dismiss the current modal vc
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
I believe I found my solution.
I was able to access the app's UITabBarController from the Appdelegate like so:
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
[[appDelegate myTabBar] setSelectedIndex:0];
Here is my problem. I had a UITableViewcontroller to which UINavigation controller is Embedded. To UITableviewController I had Add screen and an edit screen. Add screen is working perfectly. When I click on the records on the table view cell it is able to redirect to the edit page (detailed view). When I hit on Submit button the page is not navigating to the tableview cell. Here is the below error.
Warning: Attempt to present on whose view is not in the window hierarchy!
Here is the code I was trying for navigation.
UINavigationController *questionnaireNavController = [self.storyboard instantiateViewControllerWithIdentifier:#"DLProjectsTasksubtasks"];
[questionnaireNavController setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
DLProjectsTasksubtasks *qvc = (DLProjectsTasksubtasks *)[questionnaireNavController topViewController];
[qvc.tableView reloadData];
[self presentViewController:questionnaireNavController animated:YES completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
Between I was using a Segue for transfering the data from tableview data to Edit Screen
I think your segue is being pushed on the navigation controller rather than being presented modally. So you have two options, either change the segue in your storyboard to be modal and use the dismissViewControllerAnimated:YES completion: or keep it the way it is and use popViewControllerAnimated: on your navigation controller.
[self.navigationController popViewControllerAnimated:YES];
For passing the data to the previous page you can either pass a reference of the previous view controller OR accessing it through your navigationController.viewControllers. Index n-2 will be your previous view controller (n is the count of viewControllers).