0

I've got some subviews in my navigationBar. A black square, some titles and a blue bar. When pushed to a detail viewController, I want my subviews to hide (animated). I added the subways to the navigation bar inside the main view controller using self.navigationController?.navigationBar.addsubView(mySubview).

Currently, it looks like his:

navigationbar

What is the right way to hide (animated) those subviews in the detail viewController?

WalterBeiter
  • 2,021
  • 3
  • 23
  • 48

1 Answers1

1

Your question is very interesting. I never thought about the navigation bar.

When UINavigationController is used as the root controller, all UIViewControllers are stored in its stack, meaning that all UIViewControllers share a navigationBar. You added mySubView to navigationBar in the first UIViewController. If you want to hide it on the details page, you can search for subviews directly.

The first step, you need to give mySubView a tag, which can be a tag, or it can be a custom type, which is convenient for later judgment.

On the first page

   import SnapKit 

   mySubView = UIView()
   mySubView?.tag = 999
   mySubView?.backgroundColor = .red
   mySubView?.translatesAutoresizingMaskIntoConstraints = false

   let navBar = navigationController?.navigationBar
   navBar!.addSubview(mySubView!)

   mySubView!.snp.makeConstraints { (make) in
       make.left.equalTo(navBar!).offset(100)
       make.centerY.equalTo(navBar!)
       make.width.height.equalTo(50)
   }

On the details page, I deleted isHidden and saved navigationBar with the attribute because navigationBar = nil during the gesture. If SnapKit is unfamiliar, take a few more minutes to learn.

   var mySubView: UIView? = nil
   var navBar: UINavigationBar?

   override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        // Do any additional setup after loading the view.

    navigationController?.interactivePopGestureRecognizer?.addTarget(self, action: #selector(backGesture))
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        homeNavigationBarStatus()
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        homeNavigationBarStatus()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        if mySubView == nil {
            for view: UIView in (navigationController?.navigationBar.subviews)! {
                if view.tag == 999 {
                    mySubView = view
                }
            }
        }

        UIView.animate(withDuration: 1, delay: 0, options: .curveEaseOut, animations: {
            self.detailNavigationBarStatus()
        }, completion: nil)
    }

    func detailNavigationBarStatus(){
        changingNavigationBarStatus(progress: 0)
    }

    func homeNavigationBarStatus(){
        changingNavigationBarStatus(progress: 1.0)
    }

    func changingNavigationBarStatus(progress:CGFloat){
        mySubView?.alpha = progress
        mySubView?.snp.updateConstraints({ (make) in
           make.left.equalTo(navBar!).offset(100 * progress)
        })
    }

    @objc func backGesture(sender: UIScreenEdgePanGestureRecognizer) {
        switch sender.state {
            case .changed:
                let x = sender.translation(in: view).x
                progress =  x / view.frame.width
                changingNavigationBarStatus(progress: progress)

            default:
                break
        }
    }

However, using tag values is not elegant enough, you can create a specific class for mySubView, which can also be judged by class.

Nullable
  • 761
  • 5
  • 17
  • is there a way to make this interactive? For instance, I want the animation to depend on the swipe back gesture in iOS, when you swipe to the right from the left edge of the screen – WalterBeiter Oct 18 '19 at 07:00
  • Yes, `navigationController?.interactivePopGestureRecognizer` can do it, has been updated – Nullable Oct 18 '19 at 07:23
  • if I use the code of the updated answer, the subview will only fade in again, if I use the edgePanGesture. If I tap on the back button, the subview will not fade in again. And it will stay hidden. To be clear: the first solution worked, and this solution works. But maybe you should update the answer to support both modes: interactively go back to the previous screen and tapping the back button – WalterBeiter Oct 18 '19 at 11:37
  • Sorry, I accidentally deleted it, now I added it. – Nullable Oct 18 '19 at 15:24
  • Is there a way to animate the constraints of those subviews? I want my subviews to fade out of the screen in the left direction. All of those subviews are sized and positioned using autolayout in the main view controller. – WalterBeiter Oct 22 '19 at 08:02
  • Of course, you need to save the changed constraints as properties and change the constraints before `UIView.animate`. Then add `self.navigationController?.navigationBar.layoutIfNeeded()` to `UIView.animate` to refresh the entire constraint. – Nullable Oct 22 '19 at 08:25
  • but how do I find the corrects constraints in the `detailViewController`? I've got something like this in the `main controller`: `NSLayoutConstraint.activate([ logoView.bottomAnchor.constraint(equalTo: (self.navigationController?.navigationBar.bottomAnchor)!, constant: -10),)]. How do I reference this constraint in the `detailViewController`? – WalterBeiter Oct 22 '19 at 08:39
  • You first need to create a `mySubView` class, use the property of `mySubView` to save the constraint value, get `mySubView` in `detailViewController`, you can get the constraint update, update the code like this https://stackoverflow.com/questions/54097046/how-to-update-anchor-constraint-in-swift – Nullable Oct 22 '19 at 09:08
  • is there a way to get notified when the UIScreenEdgePanGestureRecognizer canceled? Because I cannot finish an animation when I don't know if I should fade in my subview or fade it out. It has to fade out, when you cancel that gesture and the detailViewController appears again and fade in the subview, when the main view controller is the one that I navigate to. – WalterBeiter Oct 23 '19 at 13:59
  • Yes, you can get all the status of the gesture in `UIScreenEdgePanGestureRecognizer.state`, including cancellation. The `changed` state used in the above example – Nullable Oct 23 '19 at 15:12
  • cancellation state does not fire. seems like it is not implemented. – WalterBeiter Oct 24 '19 at 08:23
  • I am very sorry, I tested it again. There is really no cancellation status. At present, there is only a change and an end state. However, you can also update constraints based on progress. I updated the code again, the code for `autolayout` is a bit more complicated, and I usually use `Snapkit`. – Nullable Oct 24 '19 at 11:02
  • problem is, progress is dependent on the finger position. And if the user flings, I don't know, if the UIEdgeGesture works and which vc the navigationController pushed to. It could be that the user cancelled and I don't really know exactly how apple determines if a user swiped back to the main viewcontroller or not... – WalterBeiter Oct 24 '19 at 16:40
  • The flag that returns success is `viewDidDisappear`. If it does not return successfully, the gesture will be canceled and will call `viewWillAppear` again. So i suggest that you care about the three states of the navigation bar, the status of the home page, the status of the details page, and the status being changed. – Nullable Oct 25 '19 at 01:45
  • I found an answer here: https://stackoverflow.com/questions/20639006/getting-interactivepopgesturerecognizer-dismiss-callback-event (see the answer with `navigationController.topViewController?.transitionCoordinator { ... } ` – WalterBeiter Oct 25 '19 at 09:00
  • Seems to be, try it out – Nullable Oct 25 '19 at 09:05
  • Congratulations – Nullable Oct 25 '19 at 09:19