I currently have two view controllers, a CameraViewController that uses the imagePicker to take photos, and a PhotoInboxViewController that shows all the photo messages a person has received. PhotoInboxViewController, as well as my root view controller, is a Tab Bar Controller.
When I present the imagePicker in CameraViewController , as well as the image preview screen that follows it, I disable the TabBar by setting self.tabBarController.tabBar.hidden = YES. My issue is, when PhotoInboxViewController is then shown again (for example, if the user cancels taking a photo), I would want the Tab Bar to be shown again. In my viewWillAppear method in I have the following:
//In PhotoInboxViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if ([[[self tabBarController] tabBar] isHidden]){
self.tabBarController.tabBar.hidden = NO;
}
}
In debugging, I see that the if statement is indeed evaluated as tabBar as isHidden, and therefore the next line is executed as well. However, my Tab Bar remains hidden.
What am I doing incorrectly? Your help is appreciated - thanks!
You shouldn't need to hide the tab bar. When presenting modally you should present from the full screen / root view controller. In this case the tab bar controller, not the view controller 'in' one of the tabs. This allows the presentation to work properly without any strange side effects.
Related
I have an app with view controller based status bar appearance set to YES. Some of my views have dark, some of my views have light content, and the app has a pretty complex view controller hierarchy, but it works perfectly with subclassing and overriding the appropriate methods combined with modal views capturing presentation styles etc).
However, I need a global way to view a specific item at top (behind status bar, inside my app bounds), just like the bar like personal hotspot/ GarageBand recording/in call etc bar at the top. Because of the bar's background color, I want to override the status bar appearance while displaying the bar (which can be displayed anywhere in the app so I subclassed UIWindow and put its presentation code and view directly there). The bar displays exactly as I wanted on screens with light content status bar (as my bar's text is white and background is dark) but looks terrible on dark content status bar (and no, I can't change the colors of the bar).
How can I override the "whatever the currently presented view controller is"'s preferred status bar style globally (of course, without traversing all instances of the status bar methods in all view controllers), while still using view controller based status bar appearance? My app targets iOS 8.0+.
I've ended up in a very hacky (but working) way. It might not work in every scenario, but it worked in mine. I've kept the view as it is, and haven't touched a single view or controller.
First, I've got the topmost view controller currently being displayed. I've used the code from iPhone -- How to find topmost view controller and modified it a little to handle navigation controller and tab bar controller cases too:
+ (UIViewController*) topmostControllerForViewController:(__kindof UIViewController*)topController
{
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
if([topController isKindOfClass:[UINavigationController class]]){
UINavigationController *navController = topController;
return [self topmostControllerForViewController:navController.visibleViewController];
}
if([topController isKindOfClass:[UITabBarController class]]){
UITabBarController *tabController = topController;
return [self topmostControllerForViewController:tabController.selectedViewController];
}
return topController;
}
+ (UIViewController*) topmostController
{
__kindof UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
return [self topmostControllerForViewController:topController];
}
Then I've created a view controller without a view (view is nil). In it's init method (does not work in first call if put in viewDidLoad: as it's called inside the transition process and it's too late), I've added the following:
self.modalPresentationCapturesStatusBarAppearance = YES;
self.modalPresentationStyle = UIModalPresentationOverCurrentContext;
That code allowed my "dummy" view(less) controller to handle all the presentation context, including status bar appaearance and what happens to the other views controllers when it's presented. When presented over current context, the view controller at the back is not removed from view hierarchy. If I don't do that, it will be removed and screen will be black (as I don't have any view and I want the previous view controller to be shown).
So far so good. Then, I've displayed my bar normally, but simultaneously, presented that view controller modally without any view. Because the view controller didn't have any view and was presented over the current context, it visually didn't appear in any way, but since it was a modal presentation and the dummy view controller was set to capture presentation style, it triggered iOS to ask my app for status bar style. I've simply set up my status bar style as I've wanted in the view controller methods.
There was a little problem. When I've presented the new view controller, system added a UITransitionView on top of my previous view controller. If there was an actual view, it would be on top of the transition view. The transition view is completely transparent, but it has user interaction enabled and captured all the touch events, making my app unresponsive until I've dismissed the controller. I needed my previous view controller to receive touch events. I've dug deeper and found where modal presentation adds the transition view, and removed it when presenting the view controller after transition animation is complete:
for (UIView *view in self.subviews) {
NSString *className = NSStringFromClass([view class]);
if([className hasPrefix:#"UIT"] && className.length == 16){
//this must be UITransitionView, but I'm not using it directly since it may interfere with private API usage and get app rejected by Apple.
//now, we need to find another transition view inside this and remove it
for (UIView *innerView in view.subviews) {
className = NSStringFromClass([innerView class]);
if([className hasPrefix:#"UIT"] && className.length == 16){
//this is the transition view that we need to remove
[innerView removeFromSuperview];
}
}
}
}
Since UITransitionView is a private view type and I'm not sure if it causes a problem with App Store, I've done a heuristic check of UITransitionView by checking the first letters UIT and checking the length of the class name. It's not bulletproof, but it seems to work and unlikely to return false positive.
Everything now works as expected. It's hacky, and may break in the future, especially if modal presentation changes under the hood. But rest assured, it works.
I know this question has been asked several times and the solutions I have seen have been very helpful. But since i have 2 conflicting requirements, I am a little stranded and hoping to find some help.
So here are the requirements:
We have multiple View controllers out of which only one needs to be full screen (without status bar on the top).
The other view controllers need to show a black status bar with a dark gray navigation bar
The First View controller is embedded in a navigation controller.
As recommended in some of the other posts, I did the following
Set UIViewControllerBasedStatusBarAppearance to NO
Added this code in app delegate
CGRect frame = [[UIScreen mainScreen] bounds];
self.window.frame = CGRectMake(0,20,frame.size.width, frame.size.height-20);
self.window.bounds = self.window.frame;
It works fine if I only stay in those View controllers that have the status bar.
The moment I open the FULL screen view controller, that VC is cut off on the top as shown here.
Additionally when I come back to the Main view controller, now thats shifted up as well and the title bar is where the status bar was showing.
I have tried to push the views back down by resetting the view.frame and requesting layout but it doesnt take effect.
Any suggestions on how to resolve this?
Don't change self.window.bounds in app delegate. Instead, in your view controllers try something like this:
-(void)viewWillAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES/NO animated:YES];
[self setNeedsStatusBarAppearanceUpdate]; // For showing/hiding status bar
[super viewWillAppear:animated];
}
- (BOOL)prefersStatusBarHidden {
return YES/NO;
}
You will have different frames for the view in ViewDidLoad according to whether status bar and navigation bar are there.
I'm hiding the navigationbar on a certain view, and when the user presses a button on the view, i'm pushing it to the next view.
In the next view, I am not longer hiding the nav bar and as expected it becomes visible. When hitting back however, the navbar on the first view also becomes (somehow) visible.
I'm hiding the top navbar like this:
self.navigationController.navigationBar.hidden = YES;
And I'm making it visible like this:
self.navigationController.navigationBar.hidden = NO;
I wonder what could be wrong with this, as it's quite basic but somehow has a glitch.
In Parent VC's viewWillAppear method hide the navigation bar.
-(void)viewWillAppear:(BOOL)animated {
self.navigationController.navigationBar.hidden = YES;
}
So iOS 7 introduced this new feature that you can pop a view controller by panning on the left edge. Here is my problem: I have two view controllers, A and B, that are connected by a push segue. Both of the controllers have navigation bars (by embedding A in a navigation controller). The navigation bar in B will be hidden once the user enters B's scene, and can be shown if the user taps on the scene. If the user pans on the left edge of B while the navigation bar is hidden, the navigation bar in A will be hidden as well, which means that there is no way for the user to return further back from A. So is there a way to enforce A to always show the navigation bar regardless of B has hidden the bar or not? Or is there a easy way to prevent the pan gesture from taking effect? I read this post which suggested a way of preventing the pan, but I can't locate the property in storyboard.
EDIT: So I disabled the interactive pop gesture recognizer but that only solved half of the problem. The other half is that if I click the back button on the child view controller navigation bar when the navigation bar is disappearing, I am navigated back to the parent view controller without a navigation bar. I tried calling [self.navigationController setNavigationBarHidden:NO] in viewWillAppear and then viewDidLoad but it does not work. Is this some sort of bug in the SDK or am I missing something?
Here is the code for hiding the navigation bar in the child view controller
- (void)hideNavigationBar
{
if (self.navigationBarHidden == NO)
{
[UIView animateWithDuration:UINavigationControllerHideShowBarDuration animations:^{
self.navigationController.navigationBar.alpha = 0.0;
self.previewCollectionView.alpha = 0.0;
} completion:^(BOOL finished) {
self.navigationBarHidden = YES;
}];
}
}
Yes, you can enforce the navigation bar's appearance in the A viewController's -viewWillAppear method.
Also, since you cannot find the interactivePopGestureRecognizer property in the storyboard, you can use this line in the A viewController's -viewDidLoad method:
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
EDIT:
In the viewWillAppear method, you will have to call:
[self.navigationController setNavigationBarHidden:NO];
self.navigationController.navigationBar.alpha = 1.0;
I see a couple problems with your situation:
You disable the interactive pop gesture and you hide the nav bar from view controller B. How is the user supposed to intuitively go back?
The animation that hides your navbar in B may be causing the issue. If it's anything longer than a split second, that animation may not complete in time before you hit the back button and -viewWillAppear fires on A.
Your code in B hides the navigation bar for the navigation controller. The navigation controller that holds view controller A is the same instance that holds view controller B. If you hide the navigation bar when B loads, then you go back to A (not sure how you're doing that without a back button or a edge pan gesture), it should still be hidden.
You probably want NOT disable the gesture (so the user can intuitively go back) and turn the navigation bar back on in view controller A's -viewWillAppear, to cover the case where you turned it off in B:
if (self.navigationBarHidden == NO)
{
self.navigationController.navigationBar.alpha = 1.0;
self.previewCollectionView.alpha = 1.0;
self.navigationBarHidden = NO;
}
I have almost done this in all the application but I have 3 views stacked in navigationController and I need to jump from the third view to the first view.
As I understand I can do this via viewWillDisappear only. But if I try this "jump" I will get the navigationController panel from the second View which with a navigation buttons which cause exceptions/errors.
P.S. Do not advice me to make leftBarButtonitem looking like backBarButtonItem. It is too difficult and I don't know where to find an appropriate image for it.
To my knowledge, you have no choice but to provide your own UIBarButtonItem. You are not permitted from interrupting how UINavigationController works by default. That is, you cannot override the behavior of the back button. You must provide a custom bar button item and set it as the navigation item's left bar button item.
(As a side note, the sort of behavior you're looking for may be an indication of a poor navigation pattern. Back buttons should almost always back out of a navigation hierarchy sequentially.)
Let's say in navigation order your views stacked like top -> 3 -> 2 -> 1 . When you are in this position you can have a flag in your application delegate that shows you will doublePop when backButton pressed as below: ( You are doing this whenever third view appears in the order you mentioned)
MyApplicationDelegate * del = [[UIApplication sharedApplication]delegate];
del.doublePopEnabled = YES;
[del release];
In view 2 :
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
MyApplicationDelegate * del = [[UIApplication sharedApplication]delegate];
if(del.doublePopEnabled){
//Asssuming you have a reference to your navigationController in your view 2
del.doublePopEnabled = NO;
[del.release]
//Use animated as no if you don't want user to see doublePopping.
self.navigationController popViewControllerAnimated:NO];
}
}
Hope it helps.