I'm confused. I have a navigation controller with a BarItem which opens a first view. After some work is done, I want this view to disappear and I want a second view to open.
root view: navigation controller
first view: activity indicator, where some data is put together
second view: MFMailComposeViewController
In the root view, the BarItem runs these lines to open the first view:
IndicatorViewController *indicator = [[IndicatorViewController alloc] initWithNibName:#"IndicatorViewController" bundle:nil];
indicator.view.backgroundColor = [UIColor clearColor];
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentModalViewController:indicator animated:YES];
The first view (IndicatorViewController) does some work and finally runs
[self dismissModalViewControllerAnimated:YES];
This works fine. But - how do I open the second view?
I tried this:
I open the second view. After closing the second view, my first view pops up again (since it is still there) and get's dismissed at this point. This code is placed in the first view:
- (void) viewDidAppear:(BOOL)animated {
static BOOL firstTime = YES;
if (firstTime) {
//do stuff that takes some time (that's why I show the indicator)
MailViewController *controller = [[MailViewController alloc] init];
if (controller)
[self presentModalViewController:controller animated:YES];
firstTime = NO;
} else {
[self dismissModalViewControllerAnimated:YES];
}
}
Since the first view pops up again, the user can see the indicator one more time, after the second view is closed - and that is not what I want.
What am I missing here? What would be the better way to do this?
I would do something like this. Make a navigationController, and make the first view as the root controller. Then do something like this:
FirstView.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES];
}
- (void) nextView { // however you get to your next view, button/action/etc.
UIViewController *screen = [self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
[self.navigationController pushViewController:screen animated:YES];
}
Then in the second view:
SecondView.m
- (void) nextView { // however you get to your next view, button/action/etc.
UIViewController *screen = [self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
[self.navigationController pushViewController:screen animated:YES];
}
And finally in the rootview:
RootView.m
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *navStack = [NSArray arrayWithObject:self];
self.navigationController.viewControllers = navStack;
[self.navigationController setNavigationBarHidden:NO];
}
This will make your RootView the new rootview of the NavigationController.
self.navigationController.viewControllers
is the array with all the ViewControllers that are on the navcontrollers stack. The first object is the rootcontroller. If we replace the whole array with our own array, it knows only one item. You CAN go back by dismissing if that's what you want though. This isn't the prettiest way of doing it, but it's not the worst either.
Related
in my app i am trying to make a slider using the LWSlideShow LWSlideShow
the source of images are fetched from my server after trying the solution here i stuck with error that said unbalanced call and it means that i am presenting a modal view on a view that did not completed his animation after solving this problem by putting animation to no the splashView that i present will be dismissed before the images are downloaded here is my code for further explanation:
- (IBAction)goDownload {
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Splash"];
[self.navigationController presentViewController:vc animated:YES completion:nil];
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray *array = [#[] mutableCopy];
LWSlideItem *item = [LWSlideItem itemWithCaption:#""
imageUrl:#"http://code-bee.net/geeks/images/cover-1.jpg"];
[array addObject:item];
item = [LWSlideItem itemWithCaption:#""
imageUrl:#"http://code-bee.net/geeks/images/cover-2.jpg"];
[array addObject:item];
item = [LWSlideItem itemWithCaption:#""
imageUrl:#"http://code-bee.net/geeks/images/cover-3.jpg"];
[array addObject:item];
LWSlideShow *slideShow = [[LWSlideShow alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 120)];
slideShow.autoresizingMask = UIViewAutoresizingFlexibleWidth;
//slideShow.delegate = self;
[self.view addSubview:slideShow];
slideShow.slideItems = array;
if ([slideShow.slideItems count] == [array count]) {
[self dismissViewControllerAnimated:YES completion:nil];
}
});
}
//
//-(void)viewWillAppear:(BOOL)animated
//{
//
// UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Splash"];
// [self.navigationController presentViewController:vc animated:YES completion:nil];
//}
- (void)viewDidLoad {
[super viewDidLoad];
[self goDownload];
}
also you can see from the code that also i try to use viewWillAppear same thing happened what i want is when the images are downloaded the splashView need to be dismissed i dont know what i am doing wrong
Running that code from a VC anytime before viewDidAppear (like viewDidLoad, viewWillAppear) will cause the problem you describe. But you probably don't want the slide show view to appear - even for an instant - until you're done fetching the assets. This is a common problem.
The solution is to realize that the "splash screen" and the network tasks aren't just preamble, they are as much a part of your application as the slide show.
EDIT
Make that Splash vc the app's initial view controller in storyboard. Right now, the slide show vc probably looks like this:
Uncheck the "Is Initial View Controller" checkbox, find your splash view controller (in the same storyboard, I hope) and check it's box to be the initial view controller. Now your app will start up on the splash vc, like you want it.
When the splash vc done, it can present the slide show vc, or it can even replace itself (with the slide show ) as the app window's root.
To replace the UI, I use variations of this snippet...
// in the splash vc, after all of the asset loading is complete
// give what used to be your initial view controller a storyboard id
// like #"MySlideShowUI"
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"MySlideShowUI"];
UIWindow *window = [UIApplication sharedApplication].delegate.window;
window.rootViewController = vc;
[UIView transitionWithView:window
duration:0.3
options:UIViewAnimationOptionTransitionCrossDissolve
animations:nil
completion:nil];
My iOS app has:
TabBarController
NavigationController1
TableView1
ViewController1 (Details View)
NavigationController2
TableView2
ViewController2 (Details View)
Behavior:
When the app loads, I see the TableView1.
I select an Item in the table, and it takes me via Show (Push) segue the details view 1.
I switch to the second tab on the bottom, and see TableView2.
I select an item and it takes me to details view 2
I navigate back to first tab, and see details view 1
Desired:
When performing last step, I'd like to dismiss the details view and see the first TableView1, and when switching back to second tab, I want that one to be dismissed and to see the table view.
I've tried different combinations of dismissViewControllerAnimated and popToRootViewControllerAnimated but I just don't seem to figure it out.
MainTabBarController.h
#interface MainTabBarController : UITabBarController <UITabBarControllerDelegate>
MainTabBarController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.delegate = self;
}
...
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{
// NSLog Works fine, and displays information in the output
NSLog (#"%# %lu", tabBarController.selectedViewController.title, tabBarController.selectedIndex);
// None of the lines below achieve the desired result
[viewController.navigationController popToRootViewControllerAnimated:YES];
[viewController dismissViewControllerAnimated:YES completion:nil];
[tabBarController.navigationController popToRootViewControllerAnimated:YES];
[tabBarController dismissViewControllerAnimated:YES completion:nil];
}
One option is to make use of the UITabBarControllerDelegate. Listen for changes to the tab selection. Based on the new tab, get the tab's navigation controller and call its popToRootViewControllerAnimated: method.
Update based on the code added to the question:
The problem is with how you try to pop the view controllers. You want this:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController{
// NSLog Works fine, and displays information in the output
NSLog (#"%# %lu", tabBarController.selectedViewController.title, tabBarController.selectedIndex);
// If the selected tab's root controller is a navigation controller
// pop to the top view controller in the tab's navigation stack
if ([viewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *nav = (UINavigationController *)viewController;
[nav popToRootViewControllerAnimated:NO];
}
}
Here is a simple solution for this.
Try to implement the following methods of UIViewContorller
- (void)viewWillDisappear:(BOOL)animated; // Called when the view is dismissed, covered or otherwise hidden. Default does nothing
- (void)viewDidDisappear:(BOOL)animated; // Called after the view was dismissed, covered or otherwise hidden. Default does nothing
Go to your detail-1 view controller and implement the method - (void)viewWillDisappear:(BOOL)animated.
Do a pop for that controller.
Same you should do for the detail-2
Here is the code snippet that will help you.
In Appdelegate.m
#interface AppDelegate ()<UITabBarControllerDelegate>
#property(nonatomic, strong) MainTabBarController *rootTabBarController;
#end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.rootTabBarController = [[MainTabBarController alloc]init];
self.rootTabBarController.delegate = self;
self.window.rootViewController = self.rootTabBarController;
[self.window makeKeyAndVisible];
}
TabBarController delegate implementation
-(void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
NSUInteger index = [self.rootTabBarController.viewControllers indexOfObject:viewController];
NSLog(#"Index : %lu", (unsigned long)index);
switch (index) {
case 0:
// pop other tab barcontrollers pushed or modal windows
[self.rootTabBarController flushViewControllerStackForIndex:1];
break;
case 1:
[self.rootTabBarController flushViewControllerStackForIndex:0];
break;
default:
break;
}
}
MainTabBarController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setViewControllers:#[
[[UINavigationController alloc] initWithRootViewController:[[FirstViewController alloc]init]],
[[UINavigationController alloc] initWithRootViewController:[[SecondViewController alloc]init]]
] animated:YES];
}
-(void)flushViewControllerStackForIndex:(NSUInteger )index {
[[self.viewControllers objectAtIndex:index] popToRootViewControllerAnimated:NO];
}
Here is screenshot in sequence for the sample I ran.
Here is the Sample code.
That should solve your purpose & is the right approach.
Now you may need to fine tune your own logic in flushViewControllerStackForIndex to check if there is just only controller being pushed on stack or a combination of push & modal. So better try to navigate on the Stack & do-a-dismiss-if-a-modal or do-a-pop-if-a-push.
Hope that helps.
You can directly set the view controllers currently on the navigation stack. All you have to is directly set the viewControllers property of the navigation controllers when switch tabs in the tabbar controller.
Set NavigationController1.viewcontrollers = #[tableView1] when you switch to tab1
I have a trio of functions and a property that I use to control my popovers as follows:
-(void)dismissPopoverIfPresentAnimated:(BOOL)animated
{
if (self.currentPopover)
{
[self.currentPopover dismissPopoverAnimated:animated];
self.currentPopover = nil;
}
}
-(void)presentViewController:(UIViewController *)viewController inView:(UIView *)view fromRect:(CGRect)rect suppressArrow:(BOOL)suppressArrow
{
//Did the user just tap on a button to bring up the same controller that's already displayed?
//If so, just dismiss the current controller.
BOOL closeOnly = NO;
if (self.currentPopover)
{
UIViewController *currentController = [self.currentPopover.contentViewController isKindOfClass:UINavigationController.class] ? ((UINavigationController *)self.currentPopover.contentViewController).topViewController : self.currentPopover.contentViewController;
UIViewController *newController = [viewController isKindOfClass:UINavigationController.class] ? ((UINavigationController *)viewController).topViewController : viewController;
if ([currentController isKindOfClass:newController.class])
closeOnly = YES;
[self dismissPopoverIfPresentAnimated:NO];
}
if (!closeOnly)
{
self.currentPopover = [[UIPopoverController alloc] initWithContentViewController:viewController];
self.currentPopover.backgroundColor = [UIColor whiteColor];
[self.currentPopover presentPopoverFromRect:rect inView:view permittedArrowDirections:(suppressArrow ? 0 : UIPopoverArrowDirectionAny) animated:YES];
}
}
(instancetype) initWithContentViewController:(UIViewController )viewController
{
self = [super initWithContentViewController:[[UIViewController alloc] init]];
if (self)
{
UIViewController contentViewController = super.contentViewController;
[contentViewController addChildViewController:viewController];
[viewController didMoveToParentViewController:contentViewController];
[contentViewController.view addSubview:viewController.view];
[self setPopoverContentSize:viewController.preferredContentSize animated:NO];
}
return self;
}
This runs fine in iOS 7, but in iOS 8 the problem is there is a delay between the call to presentPopoverFromRect and when the item actually shows up onscreen. So, if a user double taps a button to show a popover, the first tap will properly dismiss, then "start" the showing of the new controller. The second tap will make the dismiss call (the popover is not yet visible) and then not show the new controller (this is a design feature so that click a button will show a popover, clicking it again will hide it).
The problem is that the call to dismiss the popover doesn't actually work and the popover will show up. At that point I can't get rid of it because my property is nil and I think it is not showing.
My guess is this is an iOS 8 bug where the dismiss somehow doesn't see a visible popover and thus doesn't do anything, where instead, it should prevent it from showing up.
Oh, one last note is that the call to presentViewController is always done on the main thread.
My storyboard looks like the following:
What I'm trying to achieve is when "Click Me" is pressed on the home page, to segue to "One" , check some logic on this controller, if successful, automatically segue to "Two".
Then when the "Back" button is pressed on "Two" it would take the user back home, essentially popping "One" off the stack.
Below is what my "One" controller looks like:
#implementation OneViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// example logic, in this case just force them to view two
if(1 == 1)
{
[self.navigationController popViewControllerAnimated:NO];
TwoViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"two"];
[self.navigationController pushViewController:vc animated:YES];
}
}
#end
I'm getting strange behavior and receiving the following error:
Finishing up a navigation transition in an unexpected state.
Navigation Bar subview tree might get corrupted.
I can't seem to figure out what I'm doing wrong. I've included the full dead simple source: http://andrewherrick.com/spike/pushpop.zip
EDIT:
I've tried moving the logic to ViewDidAppear and it simply kicks me back to the "Home" view automatically which isn't what I want.
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)viewDidAppear:(BOOL)animated {
// example logic, in this case just force them to view two
if(1 == 1)
{
[self.navigationController popViewControllerAnimated:NO];
TwoViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"two"];
[self.navigationController pushViewController:vc animated:NO];
}
}
Putting your second push logic in viewDidAppear: rather than viewDidLoad solved the problem for me.
You should also consider the UX of your app. If a viewController is being used only for a few seconds to process some data and automatically segues into another viewController, it would be better practice to show a UIActivityIndicator or a small visual indicator.
EDIT:
Your automatic push should not pop itself before pushing another.
- (void)viewDidAppear:(BOOL)animated
{
if(1 == 1)
{
TwoViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"two"];
[self.navigationController pushViewController:vc animated:YES];
}
}
Then in your TwoViewController you need to programmatically call
[self.navigationController popToRootViewControllerAnimated:YES];
in order to pop back to your Main viewController.
Is is possible to have a UIView preloaded so that it will load faster when the user taps on button to load it? Currently I've got a library of informaiton that I'm attempting to load when the user taps a button, and for now it seems to be "ok" , but it makes the navigation to the page choppy, because of all the information in the library it's loading.
Thanks in advance!
It should be possible to split the setup of a View Controller from code that displays the view after a button is pushed. This will eliminate the lag when the button but the task to setup the view controller still need to be done sometime during execution (You can for example put it in the ViewdidAppear method so it is executed while waiting for the button to be pushed.
Take this code for example:
-(IBAction) button_pushed {
/*setup */
NewVC *vc = [[NewVC alloc] init];
vc.var1 = var1;
vc.var2 = x;
[vc setup];
/*display */
[self.navigationController pushViewController:vc animated:NO];
return;
}
You can split the code that setup the view from the code that displays the view
into :
#synthesize vc;
…..
- (NewVC) setup {
//setup
NewVC *vc1 = [[NewVC alloc] init];
vc.var1 = var1;
vc.var2 = x;
[vc setup];
return(vc1);
}
-(void) ViewDidAppear {
if (setupready) {
vc = [self setup];}
return;
}
-(IBAction) button_pushed:(ID) sender {
//display
[self.navigationController pushViewController:vc animated:NO];
return;
}