Reassign UIWindow rootViewController from UIViewController to UITabBarController containing UINavigationControllers - ios

I am currently having an issue with my login flow. The logic is all there but the animation from the login view to the main view is weird.
Heres a side note: The main view is a UITabBarController with 5 UINavigationControllers and the login view is just a UIViewController
So first I determine in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions , whether the current user is already logged in (I am using Parse to handle that), if their not then set the UIWindows rootViewController to be the Login controller. Else, set the UIWindows rootViewController to be the main view.
This works fine... Assume that the user is logged in on app launch. Okay cool, it shows the main View. I then sign out and everything is working as expected. I switch the UIWindows rootViewController by running this method:
- (void)showLogin {
self.activity = nil;
self.chat = nil;
self.create = nil;
self.notification = nil;
self.more = nil;
self.loginViewController = nil;
if (!self.loginViewController) { // this check is redundant i feel lol
self.loginViewController = [[PHLoginRegisterViewController alloc] init];
}
[UIView transitionWithView:self.window
duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self.window.rootViewController = self.loginViewController;
} completion:^(BOOL finished) {
self.activity = [[PHActivityViewController alloc] init];
self.chat = [[PHGlobalChatViewController alloc] init];
self.notification = [[PHNotificationsViewController alloc] init];
self.more = [[PHMoreViewController alloc] init];
[self.tabBarController setViewControllers:#[[[UINavigationController alloc] initWithRootViewController:self.activity],
[[UINavigationController alloc] initWithRootViewController:self.chat],
[[UINavigationController alloc] init],
[[UINavigationController alloc] initWithRootViewController:self.notification],
[[UINavigationController alloc] initWithRootViewController:self.more]]];
[self.tabBarController setSelectedIndex:0];
}];
}
Okay, so the login view is now shown with a nice transition... Then when I attempt to login, everything works fine, but when the transition is happening login->main, the entire main view (except for the position of the UITabBar of the UITabBarController) is basically set -20px for a brief moment, then gets reset to its expected position... Heres two screenshots of when the login is transitioning to the main view, then when it is done transitioning...
I've tested this exact implementation without a UITabBarController and it doesn't do this. This sorta GLITCH issue. Any ideas that anyone can pass along?
I want to know if its possible to do this login->main and main->login transition without having to implement the main's viewDidAppear or viewWillAppear methods.
For example, in the main views viewDidAppear,
if (user is logged in)
[self presentViewController:login animated:NO]
This causes the main screen to be seen for a brief moment before the loginview is shown, which I don't want.

I think the problem is that before your transition is completed, the auto layout process is not triggered, so your view is the position it is in your xib design.
I have an idea to work around this, whenever the app is launching, you use you main view controller as the root view controller, and if you need login, in main view controller's viewDidLoad, you load the login view controller and add it's view as the top subview of main view controller, this will solve problem that presentation has.
The only drawback is that your login view need a opaque background.

Related

iOS-Slide-Menu menu does not work when not set as app root controller

When I have a "loading" root view controller when the app starts which will determine to load either the slide menu controller or another controller, the menu does not work. It is visible, and the animations work just fine, only all touches are disabled.
I've distilled my setup to this:
In the app delegate
self.window.rootViewController = [[TestViewController alloc] init];
And in the testViewController viewDidAppear:
SlideNavigationController *slideNavigationController = [[SlideNavigationController alloc] initWithRootViewController:[[ARootViewController alloc] init]];
slideNavigationController.leftMenu = [[UINavigationController alloc] initWithRootViewController:[[MenuViewController alloc] init]];
slideNavigationController.menuRevealAnimator = [[SlideNavigationContorllerAnimatorScaleAndFade alloc] initWithMaximumFadeAlpha:0.6f fadeColor:[UIColor darkGrayColor] andMinimumScale:.8];
slideNavigationController.enableSwipeGesture = NO;
slideNavigationController.view.layer.shouldRasterize = NO;
[self presentViewController:slideNavigationController: animated:YES callback:nil];
But then the menu does not respond to touches. If the slide navigation controller is the root view controller in the app delegate the menu does work correctly. It is really a matter of that the slide navigation controller must be the apps root controller. Is there a workaround or fix for this?
For others viewing this thread, implement SlideNavigationControllerDelegate and add below function in every controller that you do not wish to show side menu icon.
And most importantly it should be the root view controller in app delegate, otherwise it won't work.
Refer to documentation of developer here.
- (BOOL)slideNavigationControllerShouldDisplayLeftMenu
{
//or no based on your preference
return YES;
}

iOS State restoration and UINavigationController modal views

I am trying to incorporate State Restoration in my app. I have it working fine for the most part, but presenting a navigation controller for a modal view on top of another navigation controller seems challenging.
For testing, I created a new split-view app on the iPad, with navigation controllers for both sides of the split view, and a Master and Detail view controller for each side, the roots of their respective navcontrollers. In the master view, you can click on a button to push a new TestViewController onto the navController stack programatically. I hook up the splitView in the storyboard, add restorationIDs to everything, opt-in to the delegate, provide a restoration class and adhere to the UIViewControllerRestoration protocol for TestViewController (since it's created programmatically) and everything works fine. If I close the app and retort it, it will start the TestViewController pushed onto the master's navcontroller. So far so good.
I then change the button handler to present the TestViewController inside a new UINavigationController, present it onto the master's navigation controller, to show a modal view (instead of pushing it on the nav stack). Now, when I relaunch the app, there is no modal view there anymore. TestModalViewController's viewControllerWithRestorationIdentifierPath:coder: is actually called correctly as before, but the modal view is never presented for some reason.
Here is the code for what I'm talking about
MasterViewController.h:
- (void)pushButton:(id)sender
{
TestModalViewController *test = [[TestModalViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
test.restorationIdentifier = #"testid";
test.restorationClass = [TestModalViewController class];
UINavigationController *modal = [[UINavigationController alloc] initWithRootViewController:test];
modal.modalPresentationStyle = UIModalPresentationFormSheet;
modal.restorationIdentifier = #"ModalTestID";
[self.navigationController presentViewController:modal animated:YES completion:nil];
return;
}
TestModalViewController.m:
+ (UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder {
TestModalViewController *test = [[TestModalViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
test.restorationClass = [TestModalViewController class];
test.restorationIdentifier = [identifierComponents lastObject];
return test;
}
Perhaps the UINavigationController that is created to display modally is never preserved? Not sure why, because it does have a restorationIdentifier.
Edit:
After further testing, it turns out if I remove the UINavigationController from the the pushButton: code, and present the TestModalViewController instance directly, it gets restored correctly. So something about the UINavigationController being presented from another UINavigationController?
This works (though not what I really want):
- (void)pushButton:(id)sender
{
TestModalViewController *test = [[TestModalViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
test.restorationIdentifier = #"testid";
test.restorationClass = [TestModalViewController class];
//UINavigationController *modal = [[UINavigationController alloc] initWithRootViewController:test];
//modal.modalPresentationStyle = UIModalPresentationFormSheet;
//modal.restorationIdentifier = #"ModalTestID";
[self.navigationController presentViewController:test animated:YES completion:nil];
return;
}
EDIT:
Attached link to test project: dropbox.com/sh/w8herpy2djjl1kw/vw_ZWqimgt
It's basically the Core Data master-detail template; run it on the iPad simulator. The + button in Master invokes the TestModalVC; if you then press the Home button, then kill debugger and launch again, you see the snapshot contains the TestModalVC but when the app is launched, it doesn't get restored
You can either create your own restoration class to handle this, or add the following to your app delegate:
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
coder:(NSCoder *)coder
{
NSString *lastIdentifier = [identifierComponents lastObject];
if ([lastIdentifier isEqualToString:#"ModalTestID"])
{
UINavigationController *nc = [[UINavigationController alloc] init];
nc.restorationIdentifier = #"ModalTestID";
return nc;
}
else if(...) //Other navigation controllers
{
}
return nil;
}
More information in the documentation.

Loading UIViewController from UISplitViewController

I have a UISplitViewController from which I need to load a UIViewController. I know I can do this as a modal, but that's not what I'm after. I need for the UIViewController to load like a normal VC occupying the full screen and when the DONE button is touched for it to return to the UISplitViewController. Thus far, when I've tried to do this it loads into one half of the UISplitViewController instead of replacing it.
So, my question is, "Is this even possible?" and if so, how? Currently I'm using the modal (PageSheet), which would be fine in Portrait orientation but just looks bad in Landscape.
Currently, my Split View is set up in the App Delegate via a tab bar as follows:
AdminMasterViewController *adminMasterVC = [[AdminMasterViewController alloc] init];
adminMasterNav.viewControllers = [NSArray arrayWithObjects:adminMasterVC, nil];
AdminDetailViewController *adminDetailVC = [[AdminDetailViewController alloc] init];
adminDetailNav.viewControllers = [NSArray arrayWithObjects:adminDetailVC, nil];
UISplitViewController *adminSplitVC = [[UISplitViewController alloc] init];
adminSplitVC.viewControllers = [NSArray arrayWithObjects: adminMasterNav, adminDetailNav, nil];
adminSplitVC.delegate = self;
adminSplitVC.title = #"Admin";
Then loaded with the tab bar.
I don't know why I just can't grasp the relationships of VCs to one another, apparently a missing chunk of brain matter.
why you don't want to use modal? you can have a full screen, just set up modal presentation style
YourUIViewController *viewController = (YourUIViewController*)[yourStoryboard instantiateViewControllerWithIdentifier:#"YourUIViewController"];
[viewController setModalPresentationStyle:UIModalPresentationFullScreen];
/*some additional logic if needed*/
[yourSplitViewController presentViewController:viewController animated:NO completion:nil];

Moving between XIBs iOS

I have a view-based application with three xib files, each with its own view controllers. How do I change from one to another? I use this to move from xib 1 to xib 2, but when I use the same code to move from xib 2 to xib 1, i get a EXC_BAD_ACCESS on the [self presentModal....] line.
MapView *controller = [[MapView alloc] initWithNibName:#"MapView" bundle:nil];
controller.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:controller animated:YES];
How can I freely move from one xib to another?
What I think you are trying to do is is present a modal view and then dismiss it, right? If that is the case then you put the code below in the method that you use to dismiss it(e.g. -(IBAction)dissmissModalView)
[self.parentViewController dismissModalViewControllerAnimated:YES];
Hopefully that works. Let me know.
initWithNibName isn't really necessary... you can change that to nil.
So, here is the correct code (without animation):
MapView *mapView = [[MapView alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:mapView animated:NO];
You should not be receiving EXC_BAD_ACCESS when trying to go back to view 1 using present. If you cannot resolve it, just use this instead:
[self dismissModalViewControllerAnimated:YES];
The second view controller will disappear and the first view controller will be visible again.
Note that presenting modal view controllers like the other answers here will mean that you have an ever-accumulating stack of view controllers. Use the application long enough and it will crash.
Instead, you can swap out the view from the application's window. Here's one way of doing that:
Add a data member to your app delegate to store the current view:
#class MyAppDelegate : NSObject <...>
{
UIViewController* currentVC;
}
and add a message there to swap VCs:
-(void)setCurrentVC:(UIViewController*)newVC
{
if (newVC==currentVC) return;
if (currentVC!=nil)
[currentVC.view removeFromSuperview];
currentVC = newVC;
if (newVC!=nil)
[self.window addSubview:newVC.view];
}
and to swap from one screen to another:
MapView* mapView = [[MapView alloc] init];
[[[UIApplication shared] delegate] setCurrentVC:mapView];

Landscape UISplitViewController when using a UINavigationController in the detail

I am developing an iPad/Universal application, and I am facing the problem of handling a UINavigationController as the main detail view in a UISplitViewController. What I want to know is how to add the default side controller button to the detail controller when in landscape orientation.
Definition
The split view controller has been defined this way:
splitViewController = [[UISplitViewController alloc] init];
SideController *root = [[[SideController alloc] init] autorelease];
DetailController *detail = [[DetailController alloc] init];
InserisciDatiController *calcolo = [[[InserisciDatiController alloc]
initWithNibName:#"InserisciDatiNuovi"
bundle:[NSBundle mainBundle]]
autorelease];
UINavigationController *rootNav = [[[UINavigationController alloc] initWithRootViewController:root]autorelease];
UINavigationController *calcoloNav = [[[UINavigationController alloc] initWithRootViewController:calcolo] autorelease];
splitViewController.viewControllers = [NSArray arrayWithObjects:rootNav, calcoloNav, nil];
splitViewController.delegate = detail;
and later on I release all the releasable objects.
I am using the SideController as a sort of index for the detail controller. It has a table view, and clicking each row I update the main view controller of the detail. Each new controller is always an instance of UINavigationController.
Update the detail
myAppDelegate *delegate = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
UINavigationController *nav = (UINavigationController *)[delegate.splitViewController.viewControllers objectAtIndex:1];
[nav setViewControllers:[NSArray arrayWithObjects:controller, nil]];
I use this piece of code to update the detail controller, when the user taps a row in the side view controller.
Handle landscape orientation
I'd like to know how to handle the landscape orientation, when I want to add the default toolbar button to display the hidden side view controller. What I am not able to do is getting the toolbar of the detail navigation controller to add the button using this method:
- (void)splitViewController:(UISplitViewController*)svc
willHideViewController:(UIViewController *)aViewController
withBarButtonItem:(UIBarButtonItem*)barButtonItem
forPopoverController:(UIPopoverController*)pc
And also, when I push a new controller to the navigation controller the default back button will appear in the toolbar. In this case how should I handle the button creation?
Update
Please, do not tell me that no-one has ever had this kind of problem! How do you develop iPad apps? Sob ...
I found a way that could be useful for someone, even though I do not really think this is a clean way of handling the navigation controller.
In the split view controller delegate I implemented the splitViewController:willHideViewController:withBarButtonItem:forPopoverController: method this way:
- (void)splitViewController:(UISplitViewController*)svc
willHideViewController:(UIViewController *)aViewController
withBarButtonItem:(UIBarButtonItem*)barButtonItem
forPopoverController:(UIPopoverController*)pc
{
barButtonItem.title = NSLocalizedString(#"menu", nil);
myAppDelegate *app = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
UINavigationController *nav = [app.splitViewController.viewControllers objectAtIndex:1];
UIViewController *ctrl = [nav.viewControllers objectAtIndex:0];
if (!ctrl.navigationItem.backBarButtonItem && !ctrl.navigationItem.leftBarButtonItem) {
ctrl.navigationItem.leftBarButtonItem = barButtonItem;
}
}
Here I present the bar button item that will show the popover view representing the side view controller if and only if the navigation controller is not showing the back button or another custom left button item.
Hope this helps someone!
I believe that Slava Bushtruk of Alterplay has worked out what you're looking for and used it in his APSplitViewController library.
I think the reason you haven't gotten any good answers is that it's really hard to understand your problem, i.e., your question could be clearer.
So first let me see if I've understood the problem correctly. Is the problem that you don't know how to handle the case when you've pushed something on your navigation controller and is supposed to show both the back button and the split view button?
If so, the way we've solved that problem is that the split view button is only visible in the root of the navigation controller. The side controller is usually only relevant for the detail view's root view controller anyway.
If I've misunderstood your problem or my solution isn't applicable to your project, please let me know.
EDIT: So if you insist on doing this, here's a way to do it:
toolbar = [[UIToolbar alloc] initWithFrame:(UIInterfaceOrientationIsPortrait(interfaceOrientation)) ? CGRectMake(0, 0, 768, 44) : CGRectMake(0, 0, 703, 44)];
toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self.navigationController.navigationBar addSubview:toolbar];
On viewWillDisappear:
[self setToolbarHidden:YES animated:animated];
and on viewWillAppear:
[self setToolbarHidden:NO animated:animated];
Now you have a toolbar that you can add anything to. Well, anything that can be added to a UIToolbar anyway, which is almost anything.

Resources