I have two view controllers - the first has a UIStatusBarStyleDefault, the second has a UIStatusBarStyleLightContent.
VC1 is presenting VC2 as a modal form sheet. So when presenting in regular trait collection, VC2 is presented as UIModalPresentationFormSheet and VC1 sets the status bar to Default.
But in compact trait collection, VC2 is fullscreen and sets the status bar style to Light Content.
The problem is when switching between regular to compact (full screen to form sheet) the status bar is not updating.
Trying -
[self setNeedsStatusBarAppearanceUpdate];
after trait collection change did not solve the issue.
Any help will be much appreciated!
// This controls whether this view controller takes over control of the status bar's appearance when presented non-full screen on another view controller. Defaults to NO.
#available(iOS 7.0, *)
public var modalPresentationCapturesStatusBarAppearance: Bool
Usage:
navigationController.modalPresentationStyle = .FormSheet
navigationController.modalPresentationCapturesStatusBarAppearance = true
Once that's set the root view controller of that navigation controller can override the preferredStatusBarStyle()
Related
I want to make a custom side bar by adding a new view to the view controller, the side bar will be in the yellow color background. I want my side bar also to overlap the navigation bar/item (green background color) in my view controller. but the navigation bar/item seems can't be overlapped by my side bar view, it seems only overlap the main view.
I tried to find the answer in stackoverflow, I find this Overlap navigation bar on ios 6 with other view, but the answer is on the Objective-C, I can't read Objective-C :(
What should I do to overlap navigation bar/item ? here is the screenshot of my view controller
I embed the navigation controller like this
There are plenty of implementations of slide-over or drawer containers.
What you need to do to get above the navigation bar is CONTAIN the navigation controller inside another view controller.
The stack would look like this.
MasterViewController
UINavigationController
RootViewController
Menu
See this one here:
Swift version of MMDrawerController
You can do this by changing your UIViewController hierarchy. For this you'll need three view controllers. First will contain everything, let's call it MasterViewController; second—your main content with navigation bar; and third—drawer.
In MasterViewController instantiate child view controllers and add them to your view controller hierarchy in viewDidLoad().
final class MasterViewController: UIViewController {
override func viewDidLoad() {
let drawerViewController = DrawerViewController()
let mainViewController = MainContentViewController()
let navigationController = UINavigationController(rootViewController: mainViewController)
addChildViewController(drawerViewController)
addChildViewController(navigationController)
view.addSubview(navigationController.view)
view.addSubview(drawerViewController.view)
}
}
Now you have navigationController.view that you can place or animate anywhere within view.
I have multiple storyboards in my project. I have a home page view controller in one storyboard, and I have a Setup view controller embedded in a navigation controller in a separate storyboard. Now when I Present the setup view controller navigation controller from the homepage view controller, the status bar won't hide. But when I set the setup view controllers storyboard as the main storyboard file base in the info.plist and the setup view controller navigation controller is the first view presented then the status bar will hide. I'm using the code below to hide the status bar. Can someone show me how to hide the status bar when the status view controller is presented by another storyboard view controller instead of being set as the first view controller. Here is the code I'm using to hide the status bar,
override var prefersStatusBarHidden: Bool {
return true
}
You can hide status bar in a condition.. You need to add another Window Object over the status bar.
let stautsBarWindow = UIWindow(frame: UIScreen.main.bounds)
stautsBarWindow.backgroundColor = UIColor.clear
//Instead of Presenting just assign your viewController in below line it will hide your statusBar as well.
stautsBarWindow.rootViewController = yourSideMenuViewController
stautsBarWindow.windowLevel = UIWindowLevelStatusBar
stautsBarWindow.isHidden = false
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.
Set Target/General/Deployment info to Hide status bar.
Set None for Status Bar in VCs in storyboards.
Added the following code to all VCs.
override var prefersStatusBarHidden: Bool {
return true
}
Briefly hides status bar but immediately reappears.
Only the prefersStatusBarHidden of the root-level view controller matters — here, the split view controller. The split view controller wants a status bar; it gets a status bar. That is all that matters.
You could try subclassing UISplitViewController, setting prefersStatusBarHidden in your subclass, and using that subclass in the app.
I have UITableViewController (its name is News) and UIViewController (its name is DetailViewController) and UISplitViewController. I want it to show a back button when I use an iPad in portrait orientation. I made the button but I cannot name it. I wrote following code
detailController.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
detailController.navigationItem.leftItemsSupplementBackButton = true
detailController.navigationItem.leftBarButtonItem?.title = navigationController?.topViewController.title
But it doesn't show the name of the button. I see only the arrow (the arrow works).
I also tried the following in my UITableViewController(News) but it didn't help me
I use two segues for different devices with this code.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
var screen = UIScreen.mainScreen().currentMode?.size.height
if (UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Pad) || screen >= 2000 && UIDevice.currentDevice().orientation.isLandscape == true && (UIDevice.currentDevice().userInterfaceIdiom == .Phone){
performSegueWithIdentifier("showDetailParse", sender: nil)
self.dismissViewControllerAnimated(true, completion: nil)
} else if (UIDevice.currentDevice().userInterfaceIdiom == .Phone) {
performSegueWithIdentifier("showParse", sender: nil)
}
}
My result on an iPad
My result on an iPhone
Thanks to Paul Hegarty and his invaluable lectures at Stanford University and available on iTunes U... in this case his 2013 lectures under the title "Developing iOS 7 Apps for iPhone and iPad" and specifically "Lecture 11 Table View and the iPad".
If you're using storyboards, then:
Open your main storyboard and select the Navigation Controller that links to the Master View Controller in your Split View Controller group;
Open the Inspector;
Under the heading View Controller, against the property Title, enter the words that you would like to appear alongside the "Back" button chevron.
See screenshot of Master Detail Xcode template set up with a Split View Controller...
If you're instantiating views in code, then:
obtain a reference to the Navigation Controller for the Master View controller;
set the title property of that Navigation Controller with the NSString of words that you would like to appear alongside the "Back" button chevron.
As an aside, I would highly recommend implementation of Auto Layout and Size Classes, that you remove the text for the Back Button property and let size classes determine the appropriate words for your Back Button.
For example, as per the question...
The Solution:
Here is the way to fix the issue with the detail view controller's back button:
For any view controller that gets pushed onto the primary navigation controller's stack, set that view controller's title. (Either in its viewDidLoad: method or in the pushing view controller's prepareForSegue:sender: method.)
Set the primary navigation controller's title in the child view controller's viewDidLoad: method.
For example, in MasterViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
[self setTitle:#"Foo"];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[self navigationController] setTitle:[self title]];
}
This will keep the detail view controller's back button title in sync with the top primary view controller's title.
What Is Going On:
UINavigationController, its rootViewController, and UINavigationItem each have a title property.
Note that the back button shown for a current view controller is actually the previous view controller's backButtonItem. (See Figure 1-7 Navigation bar structure)
A UINavigationController will automatically inherit the value of the title of its root view controller, but will not automatically inherit the title of any other controller that gets pushed onto its stack. This is why, by default, the back button of the detail view controller will always show the title of the primary navigation controller's root view controller. You might allocate, initialize, and push multiple child view controllers, but only one navigation controller is allocated and initialized for each side of a standard split view controller.
Additionally, a view controller's navigationItem's title property (whose value will appear in the label in the center of the navigation bar) does not inherit its value from the navigation controller, but from the view controller itself. If you set the view controller's title property to "Bar", and the containing navigation controller's title to "Foo", the label displayed in the center of the navigation bar will say "Bar".