UINavigationBar slides away instead of staying on place

给你一囗甜甜゛ 提交于 2019-12-22 08:23:50

问题


I created demo project to show the problem.

We have two view controllers inside UINavigationController.

MainViewController which is the root.

class MainViewController: UIViewController {

    lazy var button: UIButton = {
        let button = UIButton()
        button.setTitle("Detail", for: .normal)
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "Main"
        view.backgroundColor = .blue
        view.addSubview(button)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        button.widthAnchor.constraint(equalToConstant: 150).isActive = true
        button.heightAnchor.constraint(equalToConstant: 42).isActive = true
        button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
    }

    @objc func buttonTapped(_ sender: UIButton) {
        navigationController?.pushViewController(DetailViewController(), animated: true)
    }
}

And DetailViewController which is pushed.

class DetailViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.setNavigationBarHidden(true, animated: animated)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        navigationController?.setNavigationBarHidden(false, animated: animated)
    }
}

As you can see I want to hide UINavigationBar in DetailViewController:

Question

The problem is that, UINavigationBar slides away instead of stay of his place together with whole MainViewController. How can I change that behavior and keep pop gesture?


回答1:


in your MainViewController add that method

override func viewDidAppear(_ animated: Bool) {        
        UIView.animate(withDuration: 0) {
            self.navigationController?.setNavigationBarHidden(false, animated: false)
        }
    }

and replace your method with below method in DetailViewController

 override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    navigationController?.setNavigationBarHidden(true, animated: animated)
}



回答2:


The following code is hacking.

override func viewDidAppear(_ animated: Bool) {        
    UIView.animate(withDuration: 0) {
        self.navigationController?.setNavigationBarHidden(false, animated: false)
    }
}

Do not write this bizarre code, as suggested by @sagarbhut in his post (in this thread).

You have two choices.

  1. Hack

  2. Do not hack.

Use convenience functions like this one

https://developer.apple.com/documentation/uikit/uiview/1622562-transition

Create a custom segue, if you are using storyboards.

https://www.appcoda.com/custom-segue-animations/

Implement the UIViewControllerAnimatedTransitioning protocol

https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning

You can get some great results but I'm afraid you will need to work hard. There are numerous tutorials online that discuss how to implement the above.




回答3:


Twitter's navigation transition where the pushed ViewController's view seems to take the entire screen "hiding the navigationBar", but still having the pop gesture animation and the navigationBar visible in the pushing ViewController even during the transition animation obviously cannot be achieved by setting the bar's hidden property.

Implementing a custom navigation system is one way to do it but I suggest a simple solution by playing on navigationBar's layer and its zPosition property. You need two steps,

  • Set the navigationBar's layer zPosition to a value that'd place it under its siblings which include the current visible view controller's view in the navigation stack: navigationController?.navigationBar.layer.zPosition = -1

    The pushing VC's viewDidLoad could be a good place to do that.

  • Now that the navigationBar is placed behind the VC's view, you'll need to adjust the view's frame to make sure it doesn't overlap with the navigationBar (that'd cause navigationBar to be covered). You can use viewWillLayoutSubviews to change the view's origin.y to start under navigationBar's floor (statusBarHeight + navigationBarHeight).

That'll do the job. You don't need to modify the pushed VC unless you wanna add e.g. a custom back button like in the Twitter's profile screen case. The detail controller's view will be on top of navigation bar while letting you keep the pop gesture transition. Below is your sample code modified with this changes:

class MainViewController: UIViewController {

    lazy var button: UIButton = {
        let button = UIButton()
        button.setTitle("Detail", for: .normal)
        button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)

        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "Main"
        view.backgroundColor = .blue

        // Default value of layer's zPosition is 0 so setting it to -1 will place it behind its siblings.
        navigationController?.navigationBar.layer.zPosition = -1

        // The `view` will be under navigationBar so lets set a background color to the bar
        // as the view's backgroundColor to simulate the default behaviour.
        navigationController?.navigationBar.backgroundColor = view.backgroundColor

        // Hide the back button transition image.
        navigationController?.navigationBar.backIndicatorImage = UIImage()
        navigationController?.navigationBar.backIndicatorTransitionMaskImage = UIImage()

        view.addSubview(button)
        addConstraints()
    }

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        // Place `view` under navigationBar.
        let statusBarPlusNavigationBarHeight: CGFloat = (navigationController?.navigationBar.bounds.height ?? 0) 
          + UIApplication.shared.statusBarFrame.height
        let viewHeight = UIScreen.main.bounds.height - statusBarPlusNavigationBarHeight
        view.frame = CGRect(origin: .zero, size: CGSize(width: view.bounds.width, height: viewHeight))
        view.frame.origin.y = statusBarPlusNavigationBarHeight
    }

    @objc func buttonTapped(_ sender: UIButton) {
        navigationController?.pushViewController(DetailViewController(), animated: true)
    }

    private func addConstraints() {
        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        button.widthAnchor.constraint(equalToConstant: 150).isActive = true
        button.heightAnchor.constraint(equalToConstant: 42).isActive = true
    }
}

class DetailViewController: UIViewController {

    // Some giant button to replace the navigationBar's back button item :)
    lazy var button: UIButton = {
        let b: UIButton = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 80, height: 40)))
        b.frame.origin.y = UIApplication.shared.statusBarFrame.height
        b.backgroundColor = .darkGray
        b.setTitle("back", for: .normal)
        b.addTarget(self, action: #selector(DetailViewController.backButtonTapped), for: .touchUpInside)
        return b
    }()

    @objc func backButtonTapped() {
        navigationController?.popViewController(animated: true)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white

        view.addSubview(button)
    }
}



回答4:


This might be what you're looking for...

Start the NavBar hide / show animations before starting the push / pop:

class MainViewController: UIViewController {

    lazy var button: UIButton = {
        let button = UIButton()
        button.setTitle("Detail", for: .normal)
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "Main"
        view.backgroundColor = .blue
        view.addSubview(button)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        button.widthAnchor.constraint(equalToConstant: 150).isActive = true
        button.heightAnchor.constraint(equalToConstant: 42).isActive = true
        button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
    }

    @objc func buttonTapped(_ sender: UIButton) {
        navigationController?.setNavigationBarHidden(true, animated: true)
        navigationController?.pushViewController(DetailViewController(), animated: true)
    }
}

class DetailViewController: UIViewController {

    lazy var button: UIButton = {
        let button = UIButton()
        button.setTitle("Go Back", for: .normal)
        button.backgroundColor = .red
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        view.addSubview(button)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        button.widthAnchor.constraint(equalToConstant: 150).isActive = true
        button.heightAnchor.constraint(equalToConstant: 42).isActive = true
        button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
    }

    @objc func buttonTapped(_ sender: UIButton) {
        navigationController?.setNavigationBarHidden(false, animated: true)
        navigationController?.popViewController(animated: true)
    }

}



回答5:


Use the custom push transition from this post stackoverflow.com/a/5660278/7270113. The in order to eliminate the back gesture (that's what I understand is what you want to do), just kill the navigation stack. You will have to provide an alternative way to exit the DetailViewController, as even if you unhide the navigation controller, the backbitten will be gone since the navigation stack is empty.

@objc func buttonTapped(_ sender: UIButton) {

    let transition = CATransition()
    transition.duration = 0.5
    transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    transition.type = kCATransitionFade
    navigationController?.view.layer.add(transition, forKey: nil)

    let storyboard = UIStoryboard(name: "NameOfYourStoryBoard", bundle: .main)
    let viewController = storyboard.instantiateViewController(withIdentifier: "IdentifierOfDetailViewController") as! DetailViewController
    navigationController?.setViewControllers([viewController], animated: true) // This method will perform a push
}

Your navigation controller will from now on use this transition animation, if you want to remove it you could use

navigationController?.view.layer.removeAllAnimations()


来源:https://stackoverflow.com/questions/47223164/uinavigationbar-slides-away-instead-of-staying-on-place

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!