I wonder if its OK by iOS Human Interface Guidelines to style a UIView so it looks and acts like a navbar.
My problem is that I want to hide my current navbar once the user scrolls.
I have tried both self.navigationController?.setNavigationBarHidden(true, animated: true) and navigationController?.hidesBarsOnSwipe = true but the animation looks odd, once the navigation bar gets hidden I still have about 20px space under the status bar: You can look at my other question
So to make things easier, can I just init my view tih the navbar hidden and style my own and add the proper animation?
Try this out :
extension YourViewController {
override func prefersStatusBarHidden() -> Bool {
return barsHidden // Custom property
}
override func preferredStatusBarUpdateAnimation() -> UIStatusBarAnimation {
return .Slide
}
}
You have to update barsHidden somewhere in the code and call setNeedsStatusBarAppearanceUpdate() method.
Related
I have code that enters full screen mode by hiding the UINavigationController's navigation bar. I want a smooth animated zooming effect when entering full screen. I use setNavigationBarHidden(_:animated:). This has all worked fine up to now, even on iOS 11, but on iPhone X the animation is not working well. On hiding, there is no animation and the nav bar just disappears. On unhiding, it does animate but the nav bar appears at a slower rate than the navigation controller's content area reduces, so an ugly black background shows through the navigation bar area during the animation.
I can recreate this in a simple test app. I have a UIViewController embedded in a UINavigationController.
Storyboard
UINavigationController Navigation Bar: Style == Black; Translucent OFF
UIViewController: Extend Edges: all options OFF.
I have tried all the combinations of Adjust Scroll View Insets and Extend Edges that I can think of but they made no difference.
Code
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setFullScreen(on: fullScreen, animated: animated)
}
override var prefersStatusBarHidden: Bool
{
return fullScreen
}
override var preferredStatusBarStyle: UIStatusBarStyle
{
return .lightContent
}
#IBAction func onToggleNavBarVisibility(_ sender: Any) {
if let navBarHidden = self.navigationController?.isNavigationBarHidden {
// Toggle the state
fullScreen = !navBarHidden
setFullScreen(on: fullScreen, animated: true)
}
}
private func setFullScreen(on : Bool, animated : Bool) {
self.navigationController?.setNavigationBarHidden(on, animated: animated)
self.setNeedsStatusBarAppearanceUpdate()
}
In your case you are using both barTintColor & navigationBarStyle with Show Hide animation.
barTintColor overrides the value implied by the Style attribute
You should select either barTintColor or navigationBarStyle
In below code i have just used barTintColor & navigationBarStyle is default with Transulent.
var fullScreen = false{
didSet{
self.setNeedsStatusBarAppearanceUpdate()
}
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Navigation Bar"
navigationController?.navigationBar.barTintColor = .red
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
setFullScreen(on: fullScreen, animated: animated)
}
#IBAction func onToggleNavBarVisibility(_ sender: Any) {
if let navBarHidden =
self.navigationController?.isNavigationBarHidden {
// Toggle the state
fullScreen = !navBarHidden
setFullScreen(on: fullScreen, animated: true)
}
}
private func setFullScreen(on : Bool, animated : Bool) {
self.navigationController?.setNavigationBarHidden(on, animated: animated)
self.setNeedsStatusBarAppearanceUpdate()
}
EDIT:
If you want to hide status bar-
use prefersStatusBarHidden with the bool value. & use setNeedsStatusBarAppearanceUpdate
override var prefersStatusBarHidden: Bool {
return fullScreen
}
https://developer.apple.com/documentation/uikit/uinavigationbar
That's clearly a UIKit bug. I've filed FB8980917:
When hiding the navigation bar simultaneously with the status bar
using a slide animation, the navigation bar hides without animation.
In the opposite direction, the status bar appears with a fade
animation instead of the specified slide animation.
To reproduce, run the attached sample project. Use Simulator's slow
animations or record the device's screen and step through the frames.
I've also attached a "Screen video.mp4" for your reference.
Note 1: As a workaround, we could resort to the deprecated
UIApplication.setStatusBarHidden(_:with:) API (see "Screen video
legacy.mp4"). This mostly works except that the status bar animation
duration is longer than the navigation bar animation duration.
However, it requires setting
UIViewControllerBasedStatusBarAppearance=NO in Info.plist so it's an
all or nothing approach which opts out the whole app of the modern
API.
Note 2: Returning .fade for preferredStatusBarUpdateAnimation doesn't
work either. First, it's ugly because the navigation bar still slides
out (and can't be configured to fade out), second, the problem of the
missing hide animation of the navigation bar persists.
Note 3: Using UINavigationController's hidesBarsOnTap property doesn't
work either. The problem remains. The sample app also has
hidesBarsOnTap enabled.
Sample code:
class ViewController: UIViewController {
var fullScreen = false
override var prefersStatusBarHidden: Bool {
return navigationController!.isNavigationBarHidden
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .slide
}
#IBAction func toggleFullscreen(_ sender: Any) {
fullScreen = !fullScreen
navigationController?.setNavigationBarHidden(fullScreen, animated: true)
setNeedsStatusBarAppearanceUpdate()
}
}
While the workaround described in Note 1 kind of works, I can't recommend it since the API is deprecated since iOS 9.0. So really, it's at the folks #Apple to fix this. The fact that apps such as Photos implement a similar behavior without that bug show that there is a way to do it, albeit with private API or ugly hacks.
I added a scroll view to my view which is hover the status bar (I hid it). The scroll view is working fine, but when I'm scrolling to the top, I have a white space which disappears when I tap on my screen, and appears again when I scroll down then top.
I noticed that the scroll bar is not going to the top of my view, but stopped at the status bar.
Here are screenshots which show you what I mean.
Here I'm at the top of my view but the scroll bar isn't:
Here is the same view with the white status bar which appears when I scroll top again:
It disappear when I tap on my screen or scroll down.
Here are my constraints:
I think it's a problem of Layout Margin or something like that, but I don't what I should change?
I hide the status bar like that in my view controller:
override func viewWillAppear(_ animated: Bool) {
UIApplication.shared.keyWindow?.windowLevel = UIWindowLevelStatusBar
super.viewWillAppear(animated)
}
EDIT: Even if I comment the line which hides the status bar, I still have the same problem with my scroll view. So the problem doesn't come from how I hide it.
As Sam said, I changed the content insets to "Never" on the scroll view and it works.
While unrelated to your question, I have to react to the way you hide the status bar - the proper way is to override prefersStatusBarHidden in your view controller and call self.setNeedsStatusBarAppearanceUpdate() in your viewWillAppear:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.setNeedsStatusBarAppearanceUpdate()
}
override var prefersStatusBarHidden: Bool {
return true
}
UPDATE
Since your view controller is inside of a UINavigationViewController, you need to override childViewControllerForStatusBarHidden in UINavigationViewController to use visibleViewController as the controller to determine status bar hidden (I added override to childViewControllerForStatusBarStyle for the consistence):
extension UINavigationController {
open override var childViewControllerForStatusBarStyle: UIViewController? {
return visibleViewController
}
open override var childViewControllerForStatusBarHidden: UIViewController? {
return visibleViewController
}
}
I have code that enters full screen mode by hiding the UINavigationController's navigation bar. I want a smooth animated zooming effect when entering full screen. I use setNavigationBarHidden(_:animated:). This has all worked fine up to now, even on iOS 11, but on iPhone X the animation is not working well. On hiding, there is no animation and the nav bar just disappears. On unhiding, it does animate but the nav bar appears at a slower rate than the navigation controller's content area reduces, so an ugly black background shows through the navigation bar area during the animation.
I can recreate this in a simple test app. I have a UIViewController embedded in a UINavigationController.
Storyboard
UINavigationController Navigation Bar: Style == Black; Translucent OFF
UIViewController: Extend Edges: all options OFF.
I have tried all the combinations of Adjust Scroll View Insets and Extend Edges that I can think of but they made no difference.
Code
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setFullScreen(on: fullScreen, animated: animated)
}
override var prefersStatusBarHidden: Bool
{
return fullScreen
}
override var preferredStatusBarStyle: UIStatusBarStyle
{
return .lightContent
}
#IBAction func onToggleNavBarVisibility(_ sender: Any) {
if let navBarHidden = self.navigationController?.isNavigationBarHidden {
// Toggle the state
fullScreen = !navBarHidden
setFullScreen(on: fullScreen, animated: true)
}
}
private func setFullScreen(on : Bool, animated : Bool) {
self.navigationController?.setNavigationBarHidden(on, animated: animated)
self.setNeedsStatusBarAppearanceUpdate()
}
In your case you are using both barTintColor & navigationBarStyle with Show Hide animation.
barTintColor overrides the value implied by the Style attribute
You should select either barTintColor or navigationBarStyle
In below code i have just used barTintColor & navigationBarStyle is default with Transulent.
var fullScreen = false{
didSet{
self.setNeedsStatusBarAppearanceUpdate()
}
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Navigation Bar"
navigationController?.navigationBar.barTintColor = .red
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
setFullScreen(on: fullScreen, animated: animated)
}
#IBAction func onToggleNavBarVisibility(_ sender: Any) {
if let navBarHidden =
self.navigationController?.isNavigationBarHidden {
// Toggle the state
fullScreen = !navBarHidden
setFullScreen(on: fullScreen, animated: true)
}
}
private func setFullScreen(on : Bool, animated : Bool) {
self.navigationController?.setNavigationBarHidden(on, animated: animated)
self.setNeedsStatusBarAppearanceUpdate()
}
EDIT:
If you want to hide status bar-
use prefersStatusBarHidden with the bool value. & use setNeedsStatusBarAppearanceUpdate
override var prefersStatusBarHidden: Bool {
return fullScreen
}
https://developer.apple.com/documentation/uikit/uinavigationbar
That's clearly a UIKit bug. I've filed FB8980917:
When hiding the navigation bar simultaneously with the status bar
using a slide animation, the navigation bar hides without animation.
In the opposite direction, the status bar appears with a fade
animation instead of the specified slide animation.
To reproduce, run the attached sample project. Use Simulator's slow
animations or record the device's screen and step through the frames.
I've also attached a "Screen video.mp4" for your reference.
Note 1: As a workaround, we could resort to the deprecated
UIApplication.setStatusBarHidden(_:with:) API (see "Screen video
legacy.mp4"). This mostly works except that the status bar animation
duration is longer than the navigation bar animation duration.
However, it requires setting
UIViewControllerBasedStatusBarAppearance=NO in Info.plist so it's an
all or nothing approach which opts out the whole app of the modern
API.
Note 2: Returning .fade for preferredStatusBarUpdateAnimation doesn't
work either. First, it's ugly because the navigation bar still slides
out (and can't be configured to fade out), second, the problem of the
missing hide animation of the navigation bar persists.
Note 3: Using UINavigationController's hidesBarsOnTap property doesn't
work either. The problem remains. The sample app also has
hidesBarsOnTap enabled.
Sample code:
class ViewController: UIViewController {
var fullScreen = false
override var prefersStatusBarHidden: Bool {
return navigationController!.isNavigationBarHidden
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .slide
}
#IBAction func toggleFullscreen(_ sender: Any) {
fullScreen = !fullScreen
navigationController?.setNavigationBarHidden(fullScreen, animated: true)
setNeedsStatusBarAppearanceUpdate()
}
}
While the workaround described in Note 1 kind of works, I can't recommend it since the API is deprecated since iOS 9.0. So really, it's at the folks #Apple to fix this. The fact that apps such as Photos implement a similar behavior without that bug show that there is a way to do it, albeit with private API or ugly hacks.
Background: I implemented a Custom UIViewController Transition where the first view controller (VC1) has a visible status bar
override func prefersStatusBarHidden() -> Bool {
return false
}
while the second presented view controller (VC2) has a hidden status bar:
override func prefersStatusBarHidden() -> Bool {
return true
}
Transitioning is controlled by the user since I implemented a pull to open transition with gesture controllers.
Objective: I want the status bar to be hidden during appearance transition AND disappearance transition (essentially like the Google Maps Slide Out Menu).
Problem: The status bar is correctly hidden during the entire appearance transition of ViewController VC2. But during the entire disappearance transition the status bar is visible. Any suggestions on how to properly implement this for iOS 9?
Just try to set status bar hidden on viewWillAppear and viewWillDisappear function.
You can create an instance var to hold the status bar hidden state and return this boolean from prefersStatusBarHidden(). When this value changes, call setNeedsStatusBarAppearanceUpdate().
For example:
var statusBarHidden = true {
didSet {
if oldValue != statusBarHidden {
setNeedsStatusBarAppearanceUpdate()
}
}
}
override var prefersStatusBarHidden: Bool {
return statusBarHidden
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
statusBarHidden = false
}
Looking through all the solutions given to similar questions, I have been trying to get the statusBar to show/hide with a tap gesture.
I have set View controller-based status bar appearance = NO in the plist.
I have tried the following code in my DataViewController (page view controller) AND in the RootViewController:
let app = UIApplication.sharedApplication()
app.setStatusBarHidden(true, withAnimation: UIStatusBarAnimation.Fade)
and it doesn't work.
This is embedded in a UITabBarController, would that make a difference?
Also, I was able to get the following to hide the statusBar from the RootViewController:
override func prefersStatusBarHidden() -> Bool {
return true
}
But the DataViewController does not even call this function, and was only able to hide it permanently this way, rather than toggle it on/off.
Any ideas?
I have tried it in code, everything works fine for me. Make sure that the View controller-based status bar appearance is Set to NO. And there is no needs to override prefersStatusBarHidden().
if you using UIPageViewController then you should use this code in the RootViewController
if you have a navigationController it will hide it too
on ViewDidLoad()
self.navigationController?.hidesBarsOnTap = true
and use this method to hide or show the status Bar based on if the navigationBar is hidden or not
override func prefersStatusBarHidden() -> Bool {
if self.navigationController?.navigationBarHidden == true {
return true
}
else
{
return false
}
}