I have a UICollectionView like this:
On each cell, I add a button. When I click that button, it will push to an other view controller. It works fine. But when I try to click 2 button at the same time(eg: Cell 1 and Cell 7). It call "push" twice. And I receive warning:
nested push animation can result in corrupted navigation bar.
Finishing up a navigation transition in an unexpected state. Navigation Bar
subview tree might get corrupted.
Here is my code:
AppDelegate.m
+ (AppDelegate *)shareInstance{
return (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
- (UIViewController *)currentVisibleController{
id rootController = self.window.rootViewController;
if ([rootController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navigationController = (UINavigationController *)rootController;
return navigationController.topViewController;
}
if ([rootController isKindOfClass:[UITabBarController class]]) {
UITabBarController *tabbarController = (UITabBarController *)rootController;
id topViewController = [tabbarController.viewControllers objectAtIndex:tabbarController.selectedIndex];
if ([topViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navi = (UINavigationController *)topViewController;
return navi.topViewController;
}
return topViewController;
}
return self.window.rootViewController;
}
When I press on a cell:
CustomCell.m
- (IBAction)pressOnCell:(id)sender {
SecondViewController *secondViewController = [[SecondViewController alloc] initWithNibName:NSStringFromClass([SecondViewController class]) bundle:nil];
[[AppDelegate shareInstance].currentVisibleController.navigationController pushViewController:secondViewController animated:YES];
}
You can set animated option in pushViewController to NO. Or you can disable user interaction on collection view on viewWillDisAppear and then enable it in viewWillAppear. Or you can call pressOnCell in didSelectItemAtIndexPath and set allowsMultipleSelection property of collection view to NO.
Related
I have multiple viewControllers on my implementation for example:
ViewControllerA
ViewControllerB
ViewControllerC
ViewControllerD
But the deeplinks I need to load them in ViewControllerC but I don't know if that viewcontroller is been load it (initialized) yet or if is present.
I have tried this from appDeelegate:
ViewControllerC *rootViewController = [[ViewControllerC alloc] init];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
But it seems like is creating a new instance of the viewController.
My question to you guys, how can I grab the instance ViewControllerC load it in the app or how can I detect if ViewControllerC is not load it yet?
I'll really appreciate your help or work around.
As you pointed out, allocating a view controller in order to determine if it is presented makes no sense. Will your app always have a navigation controller at its root? If so, you can get it this way...
// in the app delegate
AppDelegate *appDelegate = self;
// or, if not in the app delegate
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
// either way
UINavigationController *navController = (UINavigationController *)[[appDelegate window] rootViewController];
Notice the potentially reckless cast of the root vc as a UINavigationController. That's reckless only if some other sort of VC can sometimes be at the root. If that's the situation in your app, then you need to test...
UIViewController *vc = [[appDelegate window] rootViewController];
if ([vc isKindOfClass:[UINavigationController self]]) {
UINavigationController *navController = (UINavigationController *)vc;
// carry on from here
} else {
// decide what your "deep link" function does when the wrong root vc is present. maybe start over?
}
Finally, and I think the problem you're getting at, how do we determine if a ViewControllerC is present, and how do we present it if not? The first part is easy because navigation controllers have a viewControllers property. That's an array representing the "stack", where the first item is the root and the last item is on top. So...
NSInteger index = NSNotFound;
for (UIViewController *vc in navController.viewControllers) {
if ([vc isKindOfClass:[UIViewController self]]) {
index = [navController.viewControllers indexOfObject:vc];
}
}
if (index != NSNotFound) {
// it's on the stack
}
Here's the way to ask if it's at the top of the stack...
[navController.viewControllers.lastObject isKindOfClass:[ViewControllerC self]]
What to do if its not on the stack is up to you. One idea is to just push one. Do that the way you do it in your app already. What if it is on the stack, but not on top? If you wanted animation to get there, you'd pop to it (animating the last pop). Since this is a deep link, you probably don't care about the animation. Just truncate the nav controllers view controller list...
if (index != NSNotFound) {
// it's on the stack
navController.viewControllers = [navController.viewControllers subarrayWithRange:NSMakeRange(0, index+1)];
}
For check if root view controller is a ViewControllerC
Swift:
if type(of: UIApplication.shared.keyWindow?.rootViewController) == ViewControllerC.self{
debugPrint("RootViewController is a ViewControllerC")
}
Objective-C:
if ([[[[UIApplication sharedApplication] keyWindow] rootViewController] class] == [ViewControllerC class]){
NSLog(#"RootViewController is a ViewControllerC");
}
For check if ViewControllerC is presented on root view controller
Swift:
if let rootViewController = UIApplication.shared.keyWindow?.rootViewController{
if type(of: rootViewController.presentedViewController) == ViewControllerC.self{
debugPrint("ViewControllerC is presented on rootViewController")
}
}
Objective-C:
UIViewController *viewController = [[[UIApplication sharedApplication] keyWindow] rootViewController];
if (viewController != nil){
if ([viewController.presentedViewController class] == [ViewControllerC class]){
NSLog(#"ViewControllerC is presented on rootViewController");
}
}
Set root view controller as ViewControllerC
Swift:
if UIApplication.shared.keyWindow != nil{
let viewController:ViewControllerC = ViewControllerC()
//You can get above instance from Storyboard if you wanna
UIApplication.shared.keyWindow!.rootViewController = viewController
}
Objective-C:
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
if (window != nil){
ViewControllerC *viewController = [[ViewControllerC alloc] init];
//You can get above instance from Storyboard if you wanna
window.rootViewController = viewController;
}
For push view controller on navigation controller from root if exists
Swift:
if UIApplication.shared.keyWindow != nil{
if let navigationController = UIApplication.shared.keyWindow!.rootViewController as? UINavigationController{
let viewController:ViewControllerC = ViewControllerC()
//You can get above instance from Storyboard if you wanna
navigationController.pushViewController(viewController, animated: true)
}
}
Objective-C:
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
if (window != nil){
UINavigationController *navigationController = (UINavigationController*)window.rootViewController;
if (navigationController != nil){
ViewControllerC *viewController = [[ViewControllerC alloc] init];
[navigationController pushViewController:viewController animated:true];
}
}
Now you can do a lot of stuff, for example get the instance of ViewControllerC from navigation controller if exists
Objective-C:
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
if (window != nil){
UINavigationController *navigationController = (UINavigationController*)window.rootViewController;
if (navigationController != nil){
UIViewController *viewController = [navigationController topViewController];
if ([viewController class] == [ViewControllerC class]){
NSLog(#"Do what you want with viewControllerC instance");
}
}
}
I have a UINavigationController in my storyboard and two viewControllers which perform the following function:
InitialViewController: this would be the application's home screen.
FirstTimeViewController: is the screen that appears when the user open
the app for the first time.
My UINavigationController has a class that have the following code:
- (void)viewDidLoad {
[super viewDidLoad];
if ([[ReadPlist initWithPlist:#"Configuration.plist" key:#"initialConfiguration"] boolValue]){
FirstTimeViewController *firstTimeController = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"firstTimeView"]; //or the homeController
[self.navigationController pushViewController:firstTimeController animated:NO];
}else{
InitialViewController *initialController = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"initialView"]; //or the homeController
[self.navigationController pushViewController:initialController animated:NO];
}
}
Basically this code verify the .plist file if a particular field is active, if YES means that the application is running for the first time, in this case it calls the corresponding viewController.
But this code is not working and I see a NavigationController with a black view. All I would do is the same thing we do in the interface builder, simply drag a line from the UINavigationController inside a UIViewController and set as "Root View Controller", but in my case I'm trying to do this programmatically.
How I can do this?
When you push to FirstTimeViewController Set Bool (User Default) in controller ViewDidload Or in your Success Code.then after set in your AppDelegate below code.
if(Bool value = Yes)
{
FirstTimeViewController *FS=[[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"FirstTimeViewController"];
UINavigationController *navController=[[UINavigationController alloc]initWithRootViewController:FS];
[navController setNavigationBarHidden:YES];
self.window.rootViewController=navController;
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
}
My answer is
- (BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)options
{
UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
FirstTimeViewController *firstTimeVC = [navController.storyboard instantiateViewControllerWithIdentifier:#" FirstTimeViewController"];
InitialViewController *initialVC = [navController.storyboard instantiateViewControllerWithIdentifier:#" InitialViewController"];
if ([[ReadPlist initWithPlist:#"Configuration.plist" key:#"initialConfiguration"] boolValue])
{
// FirstTime
navController.viewControllers = [NSArray arrayWithObject:firstTimeVC];
}
else
{
// Initial
navController.viewControllers = [NSArray arrayWithObject:initialVC];
}
[self.window makeKeyAndVisible];
return YES;
}
I am showing custom tab bar using tab bar controller.
And Creating Separate navigationController for view controllers.
First *firstViewController = [[First alloc]init];
UINavigationController *firstNavController = [[UINavigationController alloc]initWithRootViewController:firstViewController];
Second *secondViewController = [[Second alloc]init];
UINavigationController *secondNavController = [[UINavigationController alloc]initWithRootViewController:secondViewController];
Third *thirdViewController = [[Third alloc]init];
UINavigationController *thirdNavController = [[UINavigationController alloc]initWithRootViewController:thirdViewController];
tabBar.viewControllers = [[NSArray alloc] initWithObjects:firstNavController, secondNavController, thirdNavController, nil];
tabBar.delegate=self;
tabBar.selectedIndex=0;
but when i am trying to pop to root on tab click, Only 3rd navigation controller is accessible.
So its working for only 3rd tab, First and second is not working.
If you're loading a view controller on top of a tabBarController this is how you would dismiss your loaded view controller.
[self.presentingViewController dismissViewControllerAnimated:self completion:nil];
UINavigationController * navController = (UINavigationController *) [[[tabbarcontroller viewControllers] objectAtIndex: tabbarcontroller.selectedIndex] rootViewController];
If you want all those viewcontrollers to be popped, you may find this delegate useful.
- (BOOL)tabBarController:(UITabBarController *)tabBarController
shouldSelectViewController:(UIViewController *)viewController {
//iterate though tabbar-viewcontrollers to pop all of them
//return NO, if you want this to be handled like an action
return NO;
//return YES, if you want this to be handled like a normal selection
return YES;
}
Hope it helps
It will pop to rootViewController when tap on the tab.
-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController{
[(UINavigationController*)viewController popToRootViewControllerAnimated:YES];
return YES;
}
I am using a UISplitViewController, where the Master View has two levels of hierarchy. Each node in the Master View has its own Detail View Controller, which I get from the storyboard like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController *vc = [segue destinationViewController];
if ([vc isKindOfClass:[MKTableViewController class]]) {
MKTableViewController *mkVC = (MKTableViewController*) vc;
mkVC.mkController = self.mkController;
if ([vc isMemberOfClass:[ForumTableViewController class]]) {
mkVC.detailViewController = [self switchToDetailViewControllerWithIdentifier:#"DiscussionTableViewController"];
}
if ([vc isMemberOfClass:[UserTableViewController class]]) {
mkVC.detailViewController = [self switchToDetailViewControllerWithIdentifier:#"UserViewController"];
}
if ([vc isMemberOfClass:[GroupTableViewController class]]) {
mkVC.detailViewController = [self switchToDetailViewControllerWithIdentifier:#"GroupViewController"];
}
}
}
- (UIViewController*) switchToDetailViewControllerWithIdentifier:(NSString*)identifier {
UIStoryboard *storyBoard;
storyBoard = [UIStoryboard storyboardWithName:#"Main_iPad" bundle:nil];
UIViewController* vc = [storyBoard instantiateViewControllerWithIdentifier:identifier];
UINavigationController *navCon = self.splitViewController.viewControllers[1];
UIViewController* detailViewController = navCon.viewControllers[0];
for (UIViewController *child in detailViewController.childViewControllers) {
[child willMoveToParentViewController:nil];
[child.view removeFromSuperview];
[child removeFromParentViewController];
}
[detailViewController addChildViewController:vc];
[detailViewController.view addSubview:vc.view];
[vc didMoveToParentViewController:detailViewController];
return vc;
}
This all works, but the frame settings do not match. If I print the frame sizes of the different views, I get this:
Size of SplitViewController.view: X:768.0 Y:1024.0
MasterNavigationViewController.view: X:320.0 Y:768.0
Size of DetailNavigationViewController: X:703.0 Y:768.0
Size of DiscussionViewController: X:768.0 Y:1024.0
If I manually set the size of the DiscussionViewController.view using the following statement, it looks OK, but somehow I have to arbitrarily subtract 20 from the height. This feels wrong... What would be the correct way of doing this? The size of the DiscussionViewController is set to 'inferred', is there a way to have the view.frame automatically set correct when moving it to the Detail Navigation Controller?
frame.size.height = detailNavController.view.frame.size.height - detailNavController.navigationBar.frame.size.height - 20.0;
(The Detail Navigation Bar Controller has a Navigation Bar and no Toolbar)
Version Information:
iPad App for iOS7
Xcode 5.1.1
I have a TabBarController with three tabs. The views for all tabs were embedded in their own navigation controller, except one, the Map view.
To navigate from one of the other views to the Map view and pass data I used:
- (IBAction)mapButton:(id)sender {
MapViewController *destView = (MapViewController *)[self.tabBarController.viewControllers objectAtIndex:0];
if ([destView isKindOfClass:[MapViewController class]])
{
MapViewController *destinationViewController = (MapViewController *)destView;
destinationViewController.selectedTruck = _truck;
}
[self.tabBarController setSelectedIndex:0];
}
And it was working. Now I need to embed the Map view in a navigation controller as well, to add a detail view, but when I do no data is passed, it only goes to the Map view.
Can anyone see what am I missing?
[self.tabBarController.viewControllers objectAtIndex:0] is no longer an instance of MapViewController. It is a UINavigationController with a MapViewController as its root view controller.
You could access the MapViewController via the UINavigationController like so but all these type casting assumptions are messy -
- (IBAction)mapButton:(id)sender {
UINavigationController *navigationController = (UINavigationController *)[self.tabBarController.viewControllers firstObject];
if ([navigationController isKindOfClass:[UINavigationController class]])
{
MapViewController *rootViewController = (MapViewController *)[navigationController.viewControllers firstObject];
if ([rootViewController isKindOfClass:[MapViewController class]])
{
MapViewController *destinationViewController = (MapViewController *)rootViewController;
destinationViewController.selectedTruck = _truck;
}
}
[self.tabBarController setSelectedIndex:0];
}
Instead, a better design would be to keep a reference to the MapViewController (as a property) when you set up the tab bar. That way you would simply call self.destinationViewController.selectedTruck = _truck; instead.