How to present a TabBarController modal - ios

From one view(LoginTesteInicial) I can go to two tabBarControllers, but when I run the code, it crashes with this error:
Attempt to present <UITabBarController: 0x8a4a870> on <LoginTesteInicial: 0x8a46970> whose view is not in the window hierarchy!
here is my code from LoginTesteInicial.m:
UITabBarController *vc;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
vc = [[UIStoryboard storyboardWithName:#"Main_iPad" bundle:nil] instantiateViewControllerWithIdentifier:#"TabBarSemLogin"];
} else {
vc = [[UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil] instantiateViewControllerWithIdentifier:#"TabBarSemLogin"];
}
[vc setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self presentViewController:vc animated:YES completion:nil];

The answer to your question is that when -viewDidLoad is called, the view controller's view is not in the view hierarchy. You need to wait until the view is placed into the view hierarchy. This can been done either in -viewWillAppear: or -viewDidAppear:.
The "Unbalanced calls to begin/end appearance transitions" warning you get is because a view controller has not be fully loaded before being replaced with another view controller. To avoid that warning, you can use -performSelector:withObject:afterDelay: to schedule the present view controller in the next run loop.
- (void)viewDidAppear:(BOOL)animated
{
…
[self performSelector:#selector(showTabBarController) withObject:nil afterDelay:0.0];
}
- (void)showTabBarController
{
UITabBarController *vc;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
vc = [[UIStoryboard storyboardWithName:#"Main_iPad" bundle:nil] instantiateViewControllerWithIdentifier:#"TabBarSemLogin"];
} else {
vc = [[UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil] instantiateViewControllerWithIdentifier:#"TabBarSemLogin"];
}
[vc setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self presentViewController:vc animated:YES completion:nil];
}

Move [self presentViewController:vc animated:YES completion:nil] and related code it either of these
1 viewWillAppear
2 viewWillLayoutSubviews
3 viewDidLayoutSubviews
You will need to keep one flag which will take care that even if one of these method triggered multiple times your view will get presented only once.
Or you can avoid presenting view and add as subview, do this instead of presenting if you didn't like keeping flag etc.
[self.view addSubview:vc.view];
[self addChildViewController:vc];
[vc didMoveToParentViewController:self]

Related

Back button handling from a view in both present and push

I have a UIViewControllerC which is call from two another UIViewcontroller
ViewControllerA
ViewControllerB
From ViewControllerA to go to UIViewControllerC i have to make it presentviewcontroller
UIViewControllerC *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"UIViewControllerC"];
[vc setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
vc.tagfrom=#"present";
[self presentViewController:vc animated:NO completion:nil];
From ViewControllerB to go to UIViewControllerC i have to make it push view
UIViewControllerC *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"UIViewControllerC"];
[vc setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
vc.tagfrom=#"push";
[self.navigationController pushViewController:vc animated:YES];
Now i have to back from both view on back button i check tagfrom condition and handle it
-(IBAction)backBtn:(id)sender
{
if ([tagfrom isEqualToString:#"present"])
{
[self dismissViewControllerAnimated:NO completion:nil];
}
else
{
[self.navigationController popViewControllerAnimated:YES];
}
}
Which working fine in both senerio, but sometimes my push view behaves like presentmodelveiw, and having no transition effects in it, please help me to resolve it
First of all, you don't need tagfrom property on your UIViewController subclass.
A UIViewController instance has a property called presentingViewController which will let you know about the viewController ( A in your case ) that presented current viewController ( C in your case ).
-(IBAction)backBtn:(id)sender
{
if (nil != self.presentingViewController) {
[self dismissViewControllerAnimated:NO completion:nil];
}
else {
[self.navigationController popViewControllerAnimated:YES];
}
}
Second, modalTransitionStyle is supposed to work with only presentation and NOT push / pop transitions. If you are using this with push / pop, the behavior is undefined because it is not meant to be used there.

iOS: change view from viewDidAppear

I'm playing around with view life cycles & am having trouble changing a view from the load of a different view.
In my ViewController.h i have:
-(void)viewDidAppear:(BOOL)animated{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ViewController2 *viewController = (ViewController2 *)[storyboard instantiateViewControllerWithIdentifier:#"ViewController2"];
[self presentViewController:viewController animated:YES completion:nil];
}
However this only causes the view to be between ViewController, and ViewController2 appearing with animation (in a loop).
I used the code in viewDidLoad however neither of the view's loaded (from reading you cannot change view until the viewWillAppear)
Update: When using the code in viewWillAppear, whose view is not in the window hierarchy error is thrown.
How does one change the view from view setup stage?
Update
Using the above code, inside & out of GCD, in viewDidLoad, viewWillAppear & viewDidAppear either results in an infinite loop of animated showing of the ViewController2, or crash on 2nd attempt of segue (result from the loop).
EDITED:
I'm not sure exactly what you're trying to do, but assuming you are wanting the first viewcontroller to appear and then the second viewcontroller to immediately animate on top of the first one, you should be able to accomplish using several options:
First you could just wrap your calls in a dispatch_async call:
dispatch_async(dispatch_get_main_queue(), ^{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ViewController2 *viewController = (ViewController2 *)[storyboard
instantiateViewControllerWithIdentifier:#"ViewController2"];
[self presentViewController:viewController animated:YES completion:nil];
});
Or you could use a show modally segue:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"myModalSegue" sender:self];
});
}
Or you could use a navigation controller and use a standard show segue (formally push). This one doesn't require the dispatch_async:
- (void)viewDidLoad {
[super viewDidLoad];
[self performSegueWithIdentifier:#"myshowsegue" sender:self];
}
I've posted working examples of all three on: github
It is better to exchange views in loadView method.
- (void)loadView {
CGRect rect = [[UIScreen mainScreen] applicationFrame];
MyView *view = [[MyView alloc] initWithFrame:rect];
[view setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
self.view = view;
}

Need to return to the presenting UIViewController from within a presented UINavigationController

In MyMainViewController, I present a navigation controller like this:
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
UINavigationController* nc = [storyboard instantiateViewControllerWithIdentifier:#"NAVIGATION_CONTROLLER_ID"];
[self presentViewController:nc animated:YES completion:nil];
Later, from somewhere within the view hierarchy of the UINavigationController, I need to return to MyMainViewController. How can I do this?
(Note: MyMainViewController is defined in a .XIB, and not in the storyboard where the UINavigationController and it's children are defined.)
It sounds like you have modally presented a NavController that you want to remove. Modally presented VC's can remove themselves.
Somewhere in your NavController add:
[self dismissViewControllerAnimated:YES completion:^{
NSLog(#"Dismissed nav controller modally");
}]
[self.navigationController popToViewController:[[self.navigationController viewControllers] objectAtIndex:2] animated:YES];
I gues you know the index of your view controller. If you simply want to return to the rootViewController you can do it like
[self.navigationController popToRootViewControllerAnimated:YES];
If you want to push new viewController to the navigation stack just do it like
MyMainViewController *mainController = [[MyMainViewController alloc] initWithNibName:#"MyMainViewController" bundle:nil];
[self.navigationController pushViewController:desController animated:YES];
Returning to the previous viewController would be
[self dismissViewControllerAnimated:YES completion:nil];

Removing ViewController after showing modally a second ViewController

Guys in my app I have some code in the app delegate method application:didFinishLaunchingWithOptions: that determines if the initial View Controller should be the LoginViewController or the MainViewController.
If the LoginViewController is showed first and the user logs in successfully I show the MainViewController modally with this piece of code:
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
FSMainViewController *vc = (MainViewController *)[storyBoard instantiateViewControllerWithIdentifier:#"MainViewController"];
vc.loginViewController = self;
[self presentViewController:vc animated:YES completion:nil];
What I want to do next, after the MainController is showed on the screen, is remove the LoginViewController from memory so in the viewWillApper:animated: method of the MainViewController I use this code to remove (or at least try to) the LoginViewController:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.loginViewController) {
[self.loginViewController dismissViewControllerAnimated:NO completion:nil];
}
}
Problem is that this code leads to strange behaviors like the MainViewController being removed from the screen and this error message showing up in the console.
Unbalanced calls to begin/end appearance transitions for <LoginViewController: 0xb06e350>
I also tried calling [self dismissViewControllerAnimated:NO completion:nil] in the completion block of the presentViewController:animated:completion method but still no luck, it didn't work.
What am I doing wrong? How can I remove from memory the underlying LoginViewController when the MainViewController is presented modally?
Don't present your main view controller if you want the login controller to go away, just make it the window's root view controller.
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
FSMainViewController *vc = (MainViewController *)[storyBoard instantiateViewControllerWithIdentifier:#"MainViewController"];
Self.window.rootViewController = VC;
You can't dismissViewController after presenting another one on it or its presentingViewController. At here, you should dismiss LoginViewController first, then present MainViewController.
Otherwise, if you'd like pushViewController, you can call [self.navigationController setViewControllers: animated:] to remove LoginViewController.
If you think presentingViewController is just what you want, try something like this in application:didFinishLaunchingWithOptions:
if (self.loginViewController) { //Define loginViewController in appDelegate.h
[self dismissViewControllerAnimated:NO completion:^{
[self presentViewController:mainViewController animated:YES completion:nil];
}];
}
else{
[self presentViewController:mainViewController animated:YES completion:nil];
}

iOS switch back to background UIViewController

I'm looking for a way to present a modal view over my current UIViewController to basically show a UIActivityIndicator and force users to wait while data is being loaded.
in BaseViewController.m (base class of all my UIViewControllers):
// show loading view
-(void) showLoading
{
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
LoadingViewController *loading = [storyBoard instantiateViewControllerWithIdentifier:#"loadingView"];
loading.view.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:0.7];
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:loading animated:NO completion:nil];
}
This works great, but how can I go back to the background view after the loading view should be done?
Need a stopLoading method to go back to the original view:
// stop loading
-(void) stopLoading
{
// code here
}
If I try to present a new view after I present the loading view like so:
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
UIViewController *view = [storyBoard instantiateViewControllerWithIdentifier:#"loadingView"];
[self presentViewController:view animated:YES completion:nil];
The debugger gives Warning:
Attempt to present PropertyPickerViewController: 0x8af6010 on ViewController: 0x8ab23c0 which is already presenting LoadingViewController: 0x8acf530.
Try:
[self dismissViewControllerAnimated:YES completion:nil];
In fact, I'm not sure that it'a great idea to present new controller with animated gif.
The best option is (imo) show UIActivityIndicator + place a view on top on all other views to prevent user from clicking anything.
You must [self dismissViewControllerAnimated:YES completion:nil] first.
Check the Apple Documentation.

Resources