Adding/removing tabs to UITabBarController programatically gives console warning - ios

In my application, I have a custom UITabBarController that is set as the rootViewController. During the life span of the application, I programmatically add/remove additional tabs based on user interaction, and when doing so I see the following message various times in the console:
Two-stage rotation animation is deprecated. This application should use the smoother single-stage animation.
Any ideas how to prevent this from happening?
EDIT
Here's a simplified example of how I'm adding the additional tabs (example just has 1, but there's actually 4 being added).
UIViewController *viewController = [[MyCustomViewController alloc] init];
viewController.tabBarItem.image = [UIImage imageNamed:#"icon"];
viewController.tabBarItem.title = #"Title";
UINavigationController *navViewController = [[UINavigationController alloc] initWithRootViewController:viewController];
[self setViewControllers:#[ navViewController ] animated:NO];

Is your MyCustomViewController implementing one of the deprecated two-phase rotation methods?
– willAnimateFirstHalfOfRotationToInterfaceOrientation:
- didAnimateFirstHalfOfRotationToInterfaceOrientation:
- willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:
As the error message says, these have been deprecated (since iOS 5.0). Have a look at their documentation to see what the replacement mechanism is (basically, use
willAnimateRotationToInterfaceOrientation:duration: instead).

Related

New iOS popoverPresentationController much slower to appear than UIPopoverController

I've recently been updating an application to replace instances of the deprecated UIPopoverController with UIViewControllers presented modally, and when I create one, it takes about 250% as long to appear. Also, setting animated:NO in the presentViewController method also seems to have no effect.
Does anyone know why this might be?
Here's some of the relevant code I've written (it's in an IBAction from a UIButton being pressed):
UIViewController *popover = [[MyViewControllerForPopover alloc] initWithNibName:nibName bundle:nil];
popover.modalPresentationStyle = UIModalPresentationPopover;
popover.preferredContentSize = popover.view.frame.size;
popover.popoverPresentationController.sourceView = parentView;
popover.popoverPresentationController.sourceRect = location;
popover.popoverPresentationController.permittedArrowDirections = arrows;
[parentViewController presentViewController:popover animated:NO completion:nil];
I tried setting modalTransitionStyle, but it didn't seem to have any effect on the style or duration of animation. What I see is a delay, followed by the animation, so it looks like there's an extended amount of time spent setting up the view controller.
This is the same view controller that used to be put inside the UIPopover, via the initWithContentViewController init method, so I can't understand why this is slower.
Thanks

iOS ViewController Hierarchy changed with Unity 4.6.5 and Vuforia 4.2 update

I have an app that consists of Xcode Storyboard UI and a hosted Unity3D/Vuforia project for one of the views. I was previously using Unity 4.6.2 and Vuforia 3.0.9 and had implemented an UnityAppDelegate subclass with the following method that allowed me to do this.
-(void)createViewHierarchyImpl
{
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"AR" bundle:nil];
PPARStartViewController * helloVC = (PPARStartViewController *)[sb instantiateViewControllerWithIdentifier:#"StartController"];
self.navController = [[UINavigationController alloc] initWithRootViewControllier:helloVC];
self.navController.navigationBarHidden = YES;
_rootController = self.navController;
_rootView = self.navController.view;
}
I also wrote an Extension to UINavigationController to handle rotation changes further down my view stack (loading different images depending on orientation).
However, I have had to update my app to use Unity3D 4.6.5 and Vuforia 4.2 due to the 64bit requirement for submitting apps to the App Store. This has caused a couple of issues.
The above createViewHierarchyImpl method no longer works in that state. It was throwing a runtime error:
Terminating app due to uncaught exception 'UIViewControllerHierarchyInconsistency', reason: 'child view controller:PPARStartViewController: 0x17dc3070 should have parent view controller:UnityDefaultViewController: 0x1c083200 but actual parent is:UINavigationController: 0x17dc3820’
I have had to change it to the following:
-(void)createViewHierarchyImpl
{
_rootController = [[UIViewController alloc] init];
_rootView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
_rootController.view = _rootView;
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"AR" bundle:nil];
PPARStartViewController * helloVC = (PPARStartViewController *)[sb instantiateViewControllerWithIdentifier:#"StartController"];
self.navController = [[UINavigationController alloc] initWithRootViewController:helloVC];
[_rootView addSubview:self.navController.view];
self.navController.navigationBarHidden = YES;
}
Doing the above changes the Hierarchy from before, and my UINavigationController extension class no longer catches the rotation calls. They are now being caught by the UnityDefaultViewController. I tried extending this class in the same way, but at run time that view controller appears to have no children, parents or any relationship with the views currently loaded.
Finally, and possibly unrelated but I'm sure it may be, the Vuforia view is not rotating correctly. Portrait and LandscapeLeft are fine, but in LandscapeRight and PortraitUpsidedown the camera feed is flipped.
Solutions I'm hoping for:
Ideally I'm hoping someone can tell me that my original code for question 1 is possible and I'm just missing something that fixes the parental relationship of my view controllers.
If not, then I need to find out how to pass the Rotation notifications down from the UnityDefaultViewController to my UINavigationController.
And finally, is there a way to stop the UnityPlayer view rotating? There is nothing in it other than camera feed and augmented content, so the actual view doesn't even need to rotate.
Looking into the UnityAppController+ViewHandling.mm and comparing it with the old version from pre Unity 4.6 I was able to come up with a solution.
In VuforiaNativeRendererController.mm I also implemented the createAutorotatingUnityViewController method. In here I return a new instance of UnityDefaultViewController (this is what createViewHierarchyImpl used to do).
-(UIViewController*)createAutorotatingUnityViewController
{
return [[UnityDefaultViewController alloc] init];
}
Then in UnityAppController.mm I removed the code from the new checkOrientationRequest method, and in onForcedOrientation swapped the
[self transitionToViewController:[self createRootViewController]];
to the old way of:
OrientView(_rootController, _rootView, orient);
[_rootView layoutSubviews];
After all this I was able to use my original code for createViewHierarchyImpl and retain control over my rotations.
The AR camera view is still flipped in some rotations, but I now believe that to be a separate issue.
[EDIT]
I figured out why the AR camera view was not orientating. I needed to pass the rotation notifications to the viewcontroller that held the UnityView as a sub view and then call
[subView willRotateTo:ConvertToUnityScreenOrientation(toInterfaceOrientation, 0)];
now all rotations are working as intended. :)

UINavigationController stacks UITabBarControllers which stack other UINavigationControllers?

I read that it is bad to have such structure in an iOS application. But what if an application has a lot of UINavigationControllers and UITabBarControllers. But one UINavigationBar and one UITabBar are always displayed only? Other UINavigationBars and UITabBars are hidden.
EDITED
For example, in navigation-based application I call this code:
- (IBAction)openTabsController:(id)sender {
tabOneController *tabOneViewContr = [[[tabOneController alloc] initWithNibName:#"tabOneController" bundle:nil] autorelease];
UINavigationController *tabOneNavContr = [[UINavigationController alloc] initWithRootViewController:tabOneViewContr];
tabTwoController *tabTwoViewContr = [[[tabTwoController alloc] initWithNibName:#"tabTwoController" bundle:nil] autorelease];
UINavigationController *tabTwoNavContr = [[UINavigationController alloc] initWithRootViewController:tabTwoViewContr];
UITabBarController *tabContr = [[[UITabBarController alloc] init] autorelease];
tabContr.viewControllers = [NSArray arrayWithObjects:tabOneNavContr,tabTwoNavContr, nil];
sel.navigationController.navigationBar.hidden = YES;
[self.navigationController pushViewController:tabContr animated:YES];
}
After calling of this method I have two UINavigationControllers and one UITabBarController. At the same time I have one UINavigationBar and one UITabBar on a screen.
EDITED
Approximate scheme.
From The beginning we have an UINavigationController which allows to navigate between the views (circles). Then after pushing an UITabBar appears and allows to switch between the views. A rectangle with two little rects is a view with a UITabBar with 2 UITabBarItem s. When we presss any UITabBarItem another UIView appears. In this UIView we can press some button which calls another view with another UITabBar. Current UITabBar is visible after pushing if it is not hidden with another UITabBar.
is it more clear now?
The code above works almost perfect (except of some animations and not including Apple's limitations)
Gargo,
I'm not sure I understood your question but the apple documentation is clear. If you use - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated it says that viewController:
cannot be an instance of tab bar controller and it must not already be
on the navigation stack.
Since you do
[self.navigationController pushViewController:tabContr animated:YES];
you are pushing a tab bar controller instance within the navigation stack.
If you add the structure that you would achieve maybe I can help you to find another solution.
Hope that helps.
An app should only have one working tabBarController at any one time.
A tabBarController should also be the root view controller. Always. (If you need a login view or similar before the tabBarController, then remove the login view, create the tabBarController and then make that the root).
This is Apple' advice spoken to me personally by Apple engineers.
Remember, apps should be small applications that are quick and easy to use/navigate. If you feel the need for more than one tabBarController then your app design is likely very wrong from a UI/Usability perspective.

pushViewController does not cause new controller to draw view

Preface: I am not using *.xib files.
I instantiate a UINavigationController in a class that effectively serves as my 'rootViewController'. This 'rootViewController' also has two UITableViewController members that are drawn on different sections of the iPad screen. One of which is set as the root view for the navigation controller. Let's call it tableViewControllerA.
The problem is, when I invoke pushViewController on a valid UINavigationController, I see no effect:
[tableViewControllerA.navigationController pushViewController:tableViewControllerX animated:YES];
I've gathered from the posts I've searched today, that this push method should in turn cause the screen to redraw the top of stack controller.view. This is not what I'm seeing.
It seemed there was a disconnect in my implementation, and it was time to reference a working example in my environment (xcode 4.0). Assuming the canned templates would provide a working basis, I created a new navigation-based applications. I simply modified didFinishLaunchingWithOptions: as follows.
UIViewController *view1 = [[UIViewController alloc] init];
UIViewController *view2 = [[UIViewController alloc] init];
view1.title = #"view1";
view2.title = #"view2";
[self.navigationController pushViewController:view1 animated:YES];
[self.navigationController pushViewController:view2 animated:YES];
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:view1];
[view1 release];
[view2 release];
I found similar results. When I launch the simulator the screen title reads the title of whatever the self.window.rootViewController is pointing at. With the code as is, the title of the resulting top screen reads "view1". When I initWithRootViewController:view2, the resulting top screen reads "view2".
So please tell me I'm stupid cuz xyz...
Thanks.
Here are some references and suggestions:
Simple tutorial for navigation based application:
http://humblecoder.blogspot.com/2009/04/iphone-tutorial-navigation-controller.html
Here is another one to create the step by step navigation controller and adding the views:
http://www.icodeblog.com/2008/08/03/iphone-programming-tutorial-transitioning-between-views/
and here a bit advance with navigation + tab bar controller:
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/CombiningToolbarandNavigationControllers/CombiningToolbarandNavigationControllers.html
Without seeing your code, I have 2 theories:
Your syntax and calls are wrong when you do the push. Use this as a model:
-(void)Examplemethod {
AnotherClassViewController *viewController = [[[AnotherClassViewController alloc] initWithNibName:#"AnotherClassView" bundle:nil] autorelease];
[self.navigationController pushViewController:viewController animated:YES];
}
You are never adding the navigation controller to the view hierarchy which never adds the view either. Take a look at this.

How to use a UISplitViewController as a Modal View Controller?

I am trying to display a UISplitViewController presenting it as a Modal View Controller in my iPad app. I manage to have it display, but for some reason there is a gap to the left of the modal view the size of a Status Bar which is also preserved when the orientation is changed.
Does anybody know why this is happening? Or if this is even possible? Maybe I'm just digging myself a huge hole.
Like for many of you, I needed a 'modal way' to use the UISplitViewController. This seems to be an old issue, but all I found in StackOverflow was at best an explanation why the problem happens when you attempt to do so (like the accepted answer above), or 'hack-arounds'.
However, sometimes it is also not very convenient to change much of your code-base and make a UISplitViewController the initial object just to get it's functionality up and running.
In turns out, there's a way to make everybody happy (including Apple guidelines). The solution that I found best, was to use the UISplitViewController normally, but when needed to be shown/dismissed, use the following approach:
-(void)presentWithMasterViewController: (UIViewController *) thisMasterViewController
andDetailViewController: (UIViewController *) thisDetailViewController
completion:(void(^)(void))completion
{
masterViewController = thisMasterViewController;
detailViewController = thisDetailViewController;
[self setViewControllers:[NSArray arrayWithObjects:masterViewController, detailViewController, nil]];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
self.window.rootViewController = self;
[self.window makeKeyAndVisible];
if(completion)
completion();
}
-(void)dismissViewControllerWithCompletion:(void (^)(void))completion
{
self.window = nil;
masterViewController = nil;
detailViewController = nil;
if(completion)
completion();
}
Where "window", is a property of your UISplitViewController subclass. And the system will take care of the rest!
For convenience/reference, I uploaded this as a UISplitViewController subclass to gitHub:
ModalSplitViewController
--EXAMPLE ON HOW TO USE --
mySplitViewController = [[ModalSplitViewController alloc] init];
mySplitViewController.delegate = self;
[mySplitViewController presentWithMasterViewController:masterViewController andDetailViewController:detailViewController completion:nil];
// when done:
[mySplitViewController dismissViewControllerWithCompletion:nil];
mySplitViewController = nil;
Side-note: I guess most of the confusion originates from the fact that
the UISplitView usage example from Apple documentation uses the window
created in the appDelegate, and for the fact that most people are not
so familiar with the window concept - because we normally don't need
to (they are created once in StoryBoards or boilerplate code).
Additionally, if you are doing state restoration, one should not
forget that programmatically-created UIViewControllers won't
automatically be restored by the system.
The stock UISplitViewController was designed for use as the root view controller only. Presenting one modally goes against the Apple Human Interface Guidelines and has a high probability of getting rejected by the App Review Team. In addition, you may receive the error:
Application tried to present Split View Controllers modally
Technically, this is what I did:
1/ Subclass a UIViewController ie. #interface aVC: UIViewController
2/ In the viewDidLoad, set up a splitViewController, ie. aSplitVC
3/ Then self.view = aSplitVC.view
After all, present aVC as modalViewController
I agree with Evan that this is slightly off-color for Apple, but I was able to complete a working version of this with the following solution:
UISplitViewController *splitVC = [[UISplitViewController alloc] init];
splitVC.delegate = VC2;
splitVC.viewControllers = [NSArray arrayWithObjects:navcon1, navcon2, nil];
UINavigationController *splitNavCon = [[UINavigationController alloc] init];
splitNavCon.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[splitNavCon.view addSubview:splitVC.view];
VC2.splitParentViewController = splitNavCon;
[self presentViewController:splitNavCon animated:YES completion:nil];
This allowed me to have a working back button in the new UISplitViewController that was presented modally on the screen.
You'll notice that I actually pass the VC2 (the delegate of the UISplitViewController) its parent UINavigationController. This was the best way that I found I could dismiss the UISplitViewController from within the VC2:
[splitParentViewController dismissViewControllerAnimated:YES completion:nil];
I believe one can do the other way around: instead of custom controller presenting split controller, one can set up the split controller as the root window controller in storyboard, and on top of its view you can add your custom controller (ie, login screen) and remove it from the screen (removeFromSuperview for example) when it is needed.
That answer is not actually correct, because it not valid any longer since iOS8 and if you need to support even iOS7 you can do that like you put actually modally UIViewController which has a container as SplitView.
let mdSplitView = self.storyboard?.instantiateViewControllerWithIdentifier("myDataSplitView") as! MyData_SplitVC
self.addChildViewController(mdSplitView)
mdSplitView.view.bounds = self.view.bounds
self.view.addSubview(mdSplitView.view)
mdSplitView.didMoveToParentViewController(self)

Resources