I've managed to hide the navbar itself, but I want it all gone - charge icon, connection bars, clock - absolutely everything.
I'm aware this may create a 'dead end' for my app, but that's ok for my purposes.
I read here that it's apparently not legal. Is that still true?
The app is not for distribution, so I'd still be interested to hear solutions even if Apple doesn't like it.
Thank you.
I assume you want to hide status bar.
You can use prefersStatusBarHidden property to hide status bar in Swift 4.2:
class ViewController: UIViewController {
override var prefersStatusBarHidden: Bool {
return hideStatusBar
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Related
I’m designing an iPad/iPhone application in which the interface is designed to look as good in landscape as in portrait, and I’d like the interface to appear at launch as gracefully in one orientation as the other. At the same time, I need the ability to enable/disable autorotations at various times. I think I know how to do both these things, but they don’t seem to play nicely together.
I can set permitted device orientations in the Xcode project, and this seems to create a couple of array items in the project info.plist: "Supported interface orientations (iPad)" and "Supported interface orientations (iPhone)" which list the allowed orientations for each type of device. The presence of these items makes transitions from the launch screen as smooth as silk in all orientations.
I can enable/disable autorotations by overriding shouldAutorotate in my root view controller (there is only one controller at the moment). The problem is that the presence of Supported Interface items in info.plist seem to suppress all calls to shouldAutorotate, thus rendering control of autorotation inoperative.
class RootController: UIViewController
{
var allowAutorotation = true // My autorotation switch.
{ didSet { guard allowAutorotation else { return }
UIViewController.attemptRotationToDeviceOrientation()
/* Do other stuff. */ } }
override var supportedInterfaceOrientations: UIInterfaceOrientationMask
{ NSLog ("RootController: supportedInterfaceOrientations")
return .allButUpsideDown }
override var shouldAutorotate: Bool
{ NSLog ("RootController: shouldAutorotate")
return allowAutorotation }
override func viewDidLoad()
{ allowAutorotation = true
super.viewDidLoad() }
}
I’m using viewDidLoad to call attemptRotationToDeviceOrientation() at the earliest possible moment. If I don’t do this, the app screen appears in Portrait even when the device is in Landscape. (It appears correctly for Portrait, but Portrait isn’t correct.) I’ve tried making a similar call from other places, including didFinishLaunchingWithOptions in the app delegate, which I think may be the most logical place for it, but it doesn’t seem to make a difference.
If I remove the Supported Orientation items from info.plist, and define allowed orientations in code as shown, I can see from my NSLog telltales that calls to shouldAutorotate do occur at appropriate moments, but the launch then looks a trifle awkward in landscape. In the launch screen, the status bar is oriented wrong: along one of the vertical edges rather than at top, and when the transition to the app screen comes, it typically fades in canted about 10-20° off of horizontal. It instantly snaps to horizontal (it’s so quick, in fact, that it’s sometimes difficult to see), and the status bar is then correctly at the top, and from that moment on everything looks good, but the effect still seems a little unprofessional.
Since it only occurs momentarily, and only once at launch, I suppose I could live with it (I can’t live without an autorotation control) but I was hoping someone could suggest a way to get shouldAutorotate calls to work even with orientations defined in info.plist. Or perhaps some other strategy?
I think I have workarounds for these problems. To eliminate the anomalous rotation animation as the Launch Screen fades out, I use CATransactions to disable implicit animations until the app becomes active for the first time:
class RootController: UIViewController {
private var appIsLaunching = true // Set false when app first becomes active. Never set true again.
func appDidBecomeActive()
{ if appIsLaunching { appIsLaunching = false; CATransaction.setDisableActions (false) } }
override func viewWillTransition (to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
{ if appIsLaunching { CATransaction.setDisableActions (true) }
super.viewWillTransition (to: size, with: coordinator) }
}
And in the appDelegate:
func applicationDidBecomeActive ( _ application: UIApplication )
{ let root = window!.rootViewController as! RootController; root.appDidBecomeActive() }
I believe this works because a default CATransaction is always in effect, even without an explicit transaction. The cross-dissolve from Launch Screen to first view seems perfect.
There remains the status bar appearing in the wrong orientation in the Launch Screen. I’ve concluded it’s best to just turn it off, either in the project settings or by setting Status bar is initially hidden in info.plist. Not ideal, but acceptable.
I have to figure out a way to hide the home button for all of my View controllers in the app.
Yes
override var prefersHomeIndicatorAutoHidden: Bool {
return true
}
is an option but what if I have like 100 VCs, is this the only way?
I use navigation controller so I tried to override this property there, but it doesn't seems to have a reflection on the others as well.
Any ideas? Thanks in advance.
Is there a way how to set the default status bar style while keeping the UIViewControllerBasedStatusBarAppearance enabled?
Here is the problem, I'm dealing with:
Nearly the whole app needs to be using UIStatusBarStyle.LightContent as the navigation bar has a dark background. Originally, UIViewControllerBasedStatusBarAppearance was disabled and the following was set in in Info.plist for while text status status bar:
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
This worked just fine until I found out that this .LightContent status bar is shown even for some of the share extensions, like Facebook Messenger, causing it to be unreadable:
This could be solved by using UIViewControllerBasedStatusBarAppearance, but then I would need to add the following method to all of view controllers which I want to avoid as the app is quite large.
Moreover, for the one screen in the app that has light nav bar background, I was switching to dark nav bar using UIApplication.sharedApplication().setStatusBarStyle() but this method in deprecated in iOS 9.
Any ideas how to solve this? Swizzling?
Solution
The easiest and cleanest way how to achieve that is to add the following line in AppDelegate's application:willFinishLaunchingWithOptions method:
UINavigationBar.appearance().barStyle = .Black
This will make .LightContent as the default status bar style thorough the app as long as your app uses UINavigationController.
Don't forget to keep the following setting in app's Info.plist if want to use .LightContent status bar style during launch for splash screen:
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
TL;DR
My current setup, which is very similar to many other apps, uses UITabBarController as the top most controller with UINavigationController stack for each tab.
UINavigationController takes care of the status bar style (as it should) and do not call preferredStatusBarStyle() on its child view controllers. Therefore, implementing the subclassing solution proposed by par does not work in my case.
Further subclassing the custom subclass of UINavigationController I'm using would not be a clean solution either.
Now, since the app has UIViewControllerBasedStatusBarAppearance enabled and correct status bar style everywhere in the app itself, SFSafariViewController and share extension like Messages, Mail, etc use the correct (.Default) status bar style too.
The only exception where the correct status bar style is not used is the Facebook Messenger's share extension mentioned in the question. However, it seems to be a bug in the extension itself as all apps I have tried that use .LightContent status bar style (like Twitter, for example) have the same issue - presented FB Messenger share extension from the app has a status bar with white color text.
A solution I use quite frequently is to create a base view controller class that all view controllers in my app derive from. This has the advantage of allowing use of the view-controller-based status bar style-setting functionality with a default (light or dark) style, which can then be overridden on a per-view-controller basis as necessary.
A base view controller is also really handy once you start getting into trait-collection based changes, custom transition animations that you want for most view controllers, a central point for analytics tracking, and other useful things.
Yes, you have to go through your potentially large source base and change all your UIViewControllers into BaseViewControllers, but this is often as easy as a global search-and-replace.
Here's what the BaseViewController looks like with status-bar related methods:
class BaseViewController: UIViewController {
var statusBarHidden: Bool = false { didSet { setNeedsStatusBarAppearanceUpdate() } }
var statusBarStyle: UIStatusBarStyle = .lightContent { didSet { setNeedsStatusBarAppearanceUpdate() } }
var statusBarUpdateAnimation: UIStatusBarAnimation = .fade { didSet { setNeedsStatusBarAppearanceUpdate() } }
override var preferredStatusBarStyle: UIStatusBarStyle { return statusBarStyle }
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { return statusBarUpdateAnimation }
override var prefersStatusBarHidden: Bool { return statusBarHidden }
}
For all view controllers that use the default light style, you don't need to do anything special:
class ViewController: BaseViewController { }
In the cases where you need a dark status bar, do:
class DarkStatusBarViewController: BaseViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
statusBarStyle = .default
}
}
Note also that you could rename the DarkStatusBarViewController above to DarkStatusBarBaseViewController and derive from it instead of BaseViewController when you need a dark status bar. Then you don't need to duplicate the status bar code in every view controller that needs it and you maintain a nice linear relationship for all your BaseViewController functionality.
I'm using a UINavigationBar ALWAYS hidden (I'm using NavigationBar facilities to push ou pop views but I'm not showing it to final user), the problem is that in one of those views I have a tableView with UISearchBar. When I select the searchBar, make a search and click on it's "Cancel" button the NavigationBar appears, but I want to keep the Navigation hidden as it is.
I've tried to hidden the navigationBar one more time by willDismissSearchController or didDismissSearchController by
func willDismissSearchController(searchController: UISearchController) {
self.navigationController?.navigationBar.hidden = true
}
but it did not worked as I want.
Thank you in advance.
I've found a solution, so as it is a unusual question I'll reply for other people know the solution.
the following code did worked for me:
override func viewDidLayoutSubviews() {
self.navigationController?.navigationBar.hidden = true
}
I want to add a sixth tab bar item in my project. When I try that I am getting the "More" tab but when I am clicking on the "More" tab nothing happens. How can I fix this?
In swift, subclass UITabBarController and implement:
override var traitCollection: UITraitCollection {
let realTraits = super.traitCollection
let lieTrait = UITraitCollection.init(horizontalSizeClass: .regular)
return UITraitCollection(traitsFrom: [realTraits, lieTrait])
}
As of iOS 9 (and possibly 8), there is a simple way to do this. I do not recommend shipping apps using this trick - six tabs is pretty cramped on a 4s or 5 - but it's neat to know.
Notice that iPad tab bars can have more than five tabs just fine without a "more" item.
Apple's new way of determining "tablet or not?" is with size classes.
So, subclass UITabBarController and implement:
-(UITraitCollection *)traitCollection
{
UITraitCollection
*realTraits = [super traitCollection],
*lieTrait = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
return [UITraitCollection traitCollectionWithTraitsFromCollections:#[realTraits, lieTrait]];
}
Add six child view controllers to it, and voila! It looks like Apple practices what they preach, in this case!
(* Note that this will also lie about the horizontal size class to the view controllers in the tabs. You may wish to call - setOverrideTraitCollection:forChildViewController: at an opportune time, possibly in an override of setViewControllers:, to "override" their trait collections back to the real one. That way they won't get confused and try to display inappropriately wide variants of their layout.)
[Edit to add much later:
As of iOS 12 or so this trick has an interesting side effect: for 5 or fewer tabs, it turns the tab bar into iPad-style with the titles to the right of the icons.]
Make a CustomTabBarVC inherit it from UITabBarController and implement following it will make it possible for you.
import UIKit
class CustomTabBarVC: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
}
override var traitCollection: UITraitCollection {
let realTraits = super.traitCollection
let fakeTraits = UITraitCollection(horizontalSizeClass: .regular)
return UITraitCollection(traitsFrom: [realTraits, fakeTraits])
}
}
You may use custom tab bar from here: http://cocoacontrols.com/platforms/ios/controls/infinitabbar
Or even this one:
http://cocoacontrols.com/platforms/ios/controls/ciexpandabletabbar