I followed this tutorial to smoothly hide the statusBar smoothly hide statusBar and everything works fine when I use it on practice projects. I use the code in other project's that do not have SplitVC but have a tabBar and uses a navVC & tableView and everything works fine. In those I can successfully make it appear/disappear.
In my actual project I'm using a SplitViewController for iPad. I noticed when I implemented the directions from the link to my SplitViewController the statusBar wouldn't hide. I then made a new project using Apple's default MasterDetailApp to make sure I wasn't doing anything wrong but it doesn't work there either. I kept all of Apple's original code and only added in the necessary methods to make the statusBar appear/disappear
in info.plist I added the View controller-based status bar appearance and set it to YES
in storyboard I added a purple button to the DetailVC to trigger the statusBar disappearance. I also added in the method to make the backBar button disappear/reappear
I added all the methods to make the statusBar disappear/disappear to the DetailVC scene.
I added a tapGesture to the scene to make the statusBar and backButton reappear
I clicked the Plus button on the Master scene, a date appeared, clicked it to get to the DetailVC, pressed the purple buttonPressed to hide the statusBar and backButton but only the backButton gets hidden. I touch the background and the backButton reappears. The statusBar doesn't move.
I kept all the original code from Apple's project's and added mines below it:
class DetailViewController: UIViewController {
//MARK:- Apple's code
#IBOutlet weak var detailDescriptionLabel: UILabel!
func configureView() {
if let detail = detailItem {
if let label = detailDescriptionLabel {
label.text = detail.description
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
configureView()
// make backButton and statusBar reappear when scene is tapped
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(showBackButtonAndStatusBar))
view.addGestureRecognizer(tapGesture)
}
var detailItem: NSDate? {
didSet {
configureView()
}
}
//MARK:- Outside of the tapGesture in viewDidLoad everything below here is what I added
// bool to determine wether to hide the statusBar or not
var statusBarShouldBeHidden = false
// api method to allow the staus bar to be hidden
override var prefersStatusBarHidden: Bool{
return statusBarShouldBeHidden
}
// api method to animate status bar appearance/disappearance
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{
return .slide
}
#IBAction func buttonTapped(_ sender: UIButton) {
// 1. hide backBar button
navigationItem.setHidesBackButton(true, animated: false)
// 2. set bool to true
statusBarShouldBeHidden = true
UIView.animate(withDuration: 0.25){
// 3. api method to allow the statusBar to disappear
self.setNeedsStatusBarAppearanceUpdate()
}
}
//called when background is touched and added to tapGesture in viewDidLoad
#objc func showBackButtonAndStatusBar(){
// 1. set bool to false
statusBarShouldBeHidden = false
UIView.animate(withDuration: 0.25){
// 2. bring statusBar back
self.setNeedsStatusBarAppearanceUpdate()
}
// 3. bring backButton back
navigationItem.setHidesBackButton(false, animated: true)
}
}
How can I get the SplitViewVC to let me hide the statusBar?
It appears that you are trying to hide the status bar through the detail view controller. The status bar in the user interface is controlled only by the split view controller because it is on top of the view controller hierarchy. Therefore, the easiest way to control the behavior of the status bar is to subclass UISplitViewController and then override the prefersStatusBarHidden computed property in the subclass. Also, make sure you go to your storyboard and change the split view controller's custom class field in the Identity inspector to your subclass.
---Updated Answer---
#LanceSamaria Okay, I took your code above and tweaked some things. First of all, I only added the button action and not the tap gesture. Also, I commented out the hiding the back button, because this is important in the UI in order to be able to go back to the master view. Anyway, now when you click the button, the SplitViewController will hide the status bar. If you click the button again, then the status bar will reappear.
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var detailDescriptionLabel: UILabel!
var statusBarShouldBeHidden = false
func configureView() {
// Update the user interface for the detail item.
if let detail = self.detailItem {
if let label = self.detailDescriptionLabel {
label.text = detail.description
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.configureView()
}
/* override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{
return .slide
} */
var detailItem: NSDate? {
didSet {
// Update the view.
self.configureView()
}
}
#IBAction func buttonTapped(_ sender: UIButton) {
// 1. hide backBar button
//navigationItem.setHidesBackButton(true, animated: false)
// 2. set bool to true
statusBarShouldBeHidden = !statusBarShouldBeHidden
UIView.animate(withDuration: 0.25){
// 3. api method to allow the statusBar to disappear
guard let svc = self.splitViewController as? SplitViewController else { return }
svc.statusBarShouldBeHidden = self.statusBarShouldBeHidden
svc.setNeedsStatusBarAppearanceUpdate()
}
}
}
Also, one more really important thing. Below is my code for the split view controller subclass. Note that I use the same variable name "statusBarShouldBeHidden" in both the split view controller and the detail controller.
import UIKit
class SplitViewController: UISplitViewController {
var statusBarShouldBeHidden = false
override func viewDidLoad() {
super.viewDidLoad()
}
override var prefersStatusBarHidden: Bool {
return statusBarShouldBeHidden
}
}
Thank you for posting this question. This has helped my learn a lot trying to solve this problem. Please let me know if you still have a question about what this.
Related
I have a BaseViewController and a SideMenu that uses my MenuViewController. There are many possible "Home" screens that all inherit from this same BaseViewController. MenuViewController also inherits from BaseViewController.
I would like an overlay to be shown on the home screen and then disappear when the Menu is no longer in focus. So far, I can only get the overlay to show, but not disappear.
The overlay disappears if I tap one of the menu items, which performs a segue to the appropriate subclass of BaseViewController (for example, the Home screen or Settings screen). This effectively refreshes the screen, and I think I could keep a reference to the caller and segue back to it if I can't find a better solution.
Things I have tried:
overlay.removeFromSuperview()
view.sendSubview(toBack: overlay)
overlay.isHidden = true
overlay.alpha = 0.0
Moving hideOverlay() into MenuViewController.
Using super.overlay within MenuViewController instead of simply overlay or self.overlay.
I can confirm that all lines of code are hit with breakpoints, but the overlay view does not go away. BaseViewController's viewWillAppear() is not called when I tap to make the menu go away, because its subclass is already in view (just pushed to the side a bit).
Here is a minimal reproducible example:
BASE VIEW CONTROLLER
import UIKit
import SideMenu
class BaseViewController: UIViewController {
let overlay = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// Setup
overlay.frame = self.view.frame
overlay.backgroundColor = UIColor.clear
overlay.alpha = 0.5
overlay.isUserInteractionEnabled = false
overlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(overlay)
}
// WORKS
func showMenu() {
// menuLeftNavigationController is MenuViewController.
self.present(SideMenuManager.menuLeftNavigationController!, animated: true) {
UIView.animate(withDuration: 0.2) {
self.overlay.backgroundColor = Constants.Colors.overlayColor // Already defined.
}
// PROBLEM IS HERE
func hideOverlay() {
UIView.animate(withDuration: 0.2) {
self.overlay.backgroundColor = UIColor.clear
self.overlay.setNeedsLayout()
self.overlay.layoutIfNeeded()
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
}
}
MENU VIEW CONTROLLER
import UIKit
import SideMenu
class MenuViewController: BaseViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Tableview boilerplate
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
// BREAKPOINTS CONFIRM THIS CODE EXECUTES.
override func viewWillDisappear(_ animated: Bool) {
self.hideOverlay()
}
}
In viewWillDisappear when you call self.hideOverlay, you're calling that on your MenuViewController.
When showMenu() is called, you present the MenuViewController and then set the overlay background colour on the presenting view controller.
I guess, what you want to do here is in the completion of the MenuViewController, dismiss method do:
if let presentingViewController = self.presentingViewController as? BaseViewController {
presentingViewController.hideOverlay()
}
Hopefully that makes sense?
I want to edit the status bar style of my application. However, I'm unable to edit it from my main UIViewController. So I think multiple status bars settings are stacked because of embed UIViewControllers.
Here is how I initiate my navigationController in the didFinishLaunchingWithOptions method:
let navigationVC = CustomNavigationController(rootViewController: MenuInstance)
navigationVC.setNavigationBarHidden(true, animated: false)
Then, I move some views of other UIViewControllers in the menu UIViewController (MenuInstance) for any reason like this:
let scannerVC = ScannerViewController()
override func viewDidLoad() {
super.viewDidLoad()
addChildViewController(scannerVC)
scannerVC.didMove(toParentViewController: self)
}
I tried to create class to set prefersStatusBarHidden = true
class ModalViewViewController: UIViewController {
override var prefersStatusBarHidden: Bool {
get {
return true
}
}
override func viewDidLoad() {
super.viewDidLoad()
setNeedsStatusBarAppearanceUpdate()
}
}
I also created a class for the UINavigationController
class CustomNavigationController: UINavigationController {
override var prefersStatusBarHidden: Bool {
get {
return true
}
}
override func viewDidLoad() {
super.viewDidLoad()
setNeedsStatusBarAppearanceUpdate()
}
}
The goal of trying to remove the status bar is to find where the status bar comes from. I want only one status bar in the MenuInstance UIViewController that I can edit. The fact that some views of UIViewControllers are embed in one UIViewController makes me confused.
If the MenuInstance view controller is the root view controller of the navigation controller, then the MenuInstance's implementation of prefersStatusBarHidden is all that matters. No other view controller's preference is consulted. The "embed" stuff is irrelevant (unless you want to make it relevant).
class MenuInstance : UIViewController {
override var prefersStatusBarHidden: Bool {
get {
return true
}
}
}
Note, however, that this will not work on an iPhone X. You cannot hide the status bar on an iPhone X.
Here is what I'm trying to do:
When user clicks the 'Compressors' button, it shows a scroll view with details of compressors (or whatever). And to get back to menu, user clicks the 'More' tab bar item.
I've set up a variable first to store information if this is first time user taps the 'More' item.
class MoreVC: UIViewController {
var first : Bool = true
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.isHidden = true
}
open func hideEverything(_ bool : Bool)
{
print(bool)
if bool == true
{
// First time user taps More - nothing happens
return
}
print("hideEverything()")
}
#IBAction func compressorsButton(_ sender: Any) {
scrollView.isHidden = false
print(first)
}
override func viewDidAppear(_ animated: Bool) {
print("-> \(self.title!)")
first = false
}
override func viewDidDisappear(_ animated: Bool) {
scrollView.isHidden = true
first = true
}
}
Then I have Tab bar delegate in different file:
class myTabBarController: UITabBarController, UITabBarControllerDelegate
{
override func viewDidLoad() {
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, didSelect selectedViewController: UIViewController) {
if selectedViewController is MoreVC
{
let more = MoreVC()
print("more.first = \(more.first)")
if more.first == false
{
more.scrollView.isHidden = true
}
}
}
}
So, when I tap compressors button, view shows up, when I switch tabs, view hides. But when scrollView is up and I press 'More', it doesn't trigger the more.scrollView.isHidden = true because it detects that first = true, which it is not! It's set to false (as it should be), but I still get true there. I think what I need to do is somehow update myTabBarController, but I have no idea how.
Please help, any hint on what is going on will be much appreciated!
Also thanks for reading this far, I hope you understood my problem.
EDIT from future me:
This problem could have been resolved more naturally by using navigation controller through my app (which I ended up doing anyway). Joe's answer is correct, but if someone bumps into same problem as me, there is a second way.
Since you are updating the boolean on the newly created MoreVC, the existing one doesn't change at all.
Instead of:
if selectedViewController is MoreVC
update it to:
if let more = selectedViewController as? MoreVC
then get rid of the line where you create a brand new MoreVC.
EDIT
I think my problem is that I add the views as subviews in the same view, thats why I can't remove it ?
Im trying to learn swiping between views using XIB.
My storyboard contains 3 views
-Login
-Create Account
-View with scrollview that scrolls between a tableview and a blank view. This view has an embedded navigation controller (Editor -> Embed In -> Navigation Controller)
I Don't want the navigation controller to be shown in my blank page.
I have created the tableView Controller and the blank UIControllerView by adding them as "addChildViewController", See code below
import UIKit
class MasterViewForScroll: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
let Inbox : FriendlistTableBarView = FriendlistTableBarView(nibName: "FriendlistTableBarView", bundle: nil)
let Camera : CameraViewController = CameraViewController(nibName: "CameraViewController", bundle: nil)
func creatingSubViews() {
self.addChildViewController(Inbox)
self.scrollView.addSubview(Inbox.view)
Inbox.didMoveToParentViewController(self)
Inbox.navigationController?.navigationBar.hidden = false
var CameraView = Camera.view.frame
CameraView.origin.x = self.view.frame.width
Camera.view.frame = CameraView
self.addChildViewController(Camera)
self.scrollView.addSubview(Camera.view)
Camera.didMoveToParentViewController(self)
self.scrollView.contentSize = CGSizeMake(self.view.frame.width * 2, self.view.frame.height)
}
override func viewDidLoad() {
super.viewDidLoad()
creatingSubViews()
}
So my question is: How do I hide the navigation controller in the "Camera" view.
Thank you
In your CameraViewController class, add the following code:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBarHidden = true
}
Next, in your FriendlistTableBarView class (I guess it is a UIViewController subclass), add the following code:
override func viewWillAppear() {
super.viewWillAppear()
self.navigationController?.navigationBarHidden = false
}
So, when you swipe to the right - navigation bar will hide, and when you swipe to the left - it will appear again.
I have problem with UITabBar. I need to make a custom action for Item (UITabBarItem). What do I need to add to make it working?
import UIKit
class ViewController: UIViewController {
#IBOutlet var TabBar: UITabBarItem!
#IBOutlet var Item: UITabBarItem!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationController?.navigationBarHidden
self.navigationItem.hidesBackButton = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tabBar(tabBar: UITabBar!, didSelectItem item: UITabBarItem!) {
var selectedTag = tabBar.selectedItem?.tag
println(selectedTag)
if selectedTag == 0
{
}
else
{
}
}
}
In each ViewController place this function:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//set inital view
}
Then put your code to execute in here and when the view appears it will execute.
Okay then, what I think you want then is not a UITabBar but instead a UIToolBar. From Apple:
"A tab bar is a control, usually appearing across the bottom of the screen in the context of a tab bar controller, for giving the user one-tap, modal access to a set of views in an app. Each button in a tab bar is called a tab bar item and is an instance of the UITabBarItem class. If you instead want to give the user a bar of buttons that each perform an action, use a UIToolbar object."
For the UIToolBar description see:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIToolbar_Class/index.html#//apple_ref/occ/cl/UIToolbar