6

I'm trying to create an effect that even though it's close to what I desire but it has some UI glitches which I'll explain.

I have, let's say, my Home navigation controller which I tap a cell that pushes a new view controller.

On that view controller's viewWillAppear(:) I've implemented the following:

self.navigationController?.navigationBar.isTranslucent = true
self.navigationController?.navigationBar.backgroundColor = .clear
self.navigationController?.navigationBar.tintColor = .white
self.navigationController?.navigationBar.barTintColor = .clear
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)

By doing this, the pushed view controller will have its navigationBar transparent, and still keeps the buttons visible (which is what I desire), but on the push animation, it shows a black bar on the parent controller, because it hides the parent's navigationBaras well.

And then on the pushed view controllers viewWillDisappear(_:) I've implemented the following:

self.navigationController?.navigationBar.shadowImage = nil
self.navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.backgroundColor = .white
self.navigationController?.navigationBar.barTintColor = .white

By doing this, I'm trying to reset the parent's navigationBar default properties, but by doing so I see a black bar during the animation, before it completes the animation, which causes a bad UI/UX.

Am I doing something wrong here, or there is any better approach on this?

Thank you.

Ivan Cantarino
  • 3,058
  • 4
  • 34
  • 73
  • Have you looked at this post: https://stackoverflow.com/q/44343355/3900902 – Paulo Nov 24 '17 at 12:28
  • I'm looking into the comment where you've said it helped you. I'll try that implementation and I'll feedback to you. – Ivan Cantarino Nov 24 '17 at 12:35
  • Sounds like a plan :) – Paulo Nov 24 '17 at 12:36
  • @Paulo So I tried setting everything in the `isViewLoaded` but that comes with a bad thing. That functions is being called in almost every frame, by doing so it is resetting the navigationBar properties which makes the device laggy. – Ivan Cantarino Nov 24 '17 at 13:11
  • Well that sucks! Let me have a look at what I ended up doing. – Paulo Nov 24 '17 at 13:23
  • @Paulo yes, it does solve the issue but if you `print()` something inside that function as long as the view is loaded it prints endlessly and it lags the scroll. One other thing I tried was to force it to only fires once with a boolean controller, but doing that, somehow the changes didn't occur. I think the system recursively calls that function and on the first iteration it doesn't apply changes. – Ivan Cantarino Nov 24 '17 at 13:26
  • Okay, sorry it’s been a while since I was last working on this project. Have you looked at using a transition coordinator, and placing it in the viewWillAppear? – Paulo Nov 24 '17 at 13:30
  • Or alternatively, overriding the loadView function, instead of the isViewLoaded. – Paulo Nov 24 '17 at 13:31
  • Hm.. I'll try both of those and I'll feedback with some conclusions ;) – Ivan Cantarino Nov 24 '17 at 13:32
  • @Paulo this is getting frustrating though... overriding `loadView()` creates the same behavior as `isViewLoaded()` it gets called every frame. Adding a transition coordinator in viewDidLoad it works as expected on the first cell selection. When I tap the back button and on the parentViewControllers viewWillAppear I reset the data I then start seeing some weird behaviors and some slight delays in the UI when applying the desired effect. I think I may have to take a different UI approach, which I didn't want to, maybe keep the `navigationBar` always present without transparent background – Ivan Cantarino Nov 24 '17 at 15:17
  • I animated in the viewWillAppear, and then reset in the viewWillDisappear. I am away from my desk at the moment, when I get back I’ll sit and write some code :) – Paulo Nov 24 '17 at 15:35
  • @Paulo I have found a way to do it. I'll post an answer bellow and I'll mention your useful help – Ivan Cantarino Nov 24 '17 at 16:32

2 Answers2

4

So after some digging and some pretty useful hints from @Paulo I have managed to solve this as I wanted to.

This is something that should be way more simple to achieve, and Apple should give developers that simplicity option and not tweaking around some hack to achieve it, but anyway.

I found that one of the secret was that I was abusing the navigationBar.isTranslucent = true / false when navigating through the view controllers.

In order to do this I set the default navigationBar properties in the parentViewController, the one that will push to the view controller with the transparent navigationBar; I've done it as the following:

self.navigationController?.navigationBar.backgroundColor = .white
self.navigationController?.navigationBar.barTintColor = .white
self.navigationController?.navigationBar.shadowImage = nil
self.navigationController?.navigationBar.setBackgroundImage(nil, for: .default)

On the pushedViewController viewWillAppear(_:) you need to implement the following:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    guard self.navigationController?.topViewController === self else { return }
    self.transitionCoordinator?.animate(alongsideTransition: { [weak self](context) in
        self?.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
        self?.navigationController?.navigationBar.shadowImage = UIImage()
        self?.navigationController?.navigationBar.backgroundColor = .clear
        self?.navigationController?.navigationBar.barTintColor = .clear
    }, completion: nil)
}

Here I set the desired navigationBar transparency, but as you notice, no need to use the isTranslucent property, I noticed by forcing it the UI would show some flickering and weird layout on the push animation.

Then on the same view controller (pushed) you need to implement the default, desired, navigationBar properties that you've implemented in the parentViewController:

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

    self.transitionCoordinator?.animate(alongsideTransition: { [weak self](context) in
        self?.navigationController?.navigationBar.setBackgroundImage(nil, for: UIBarMetrics.default)
        self?.navigationController?.navigationBar.shadowImage = nil
        self?.navigationController?.navigationBar.backgroundColor = .white
        self?.navigationController?.navigationBar.barTintColor = .white
        }, completion: nil)
}

And by doing this everything should work as expected.

Hope it helps someone in the future.

Ivan Cantarino
  • 3,058
  • 4
  • 34
  • 73
  • 2
    This does not work for me. When popping the view controller, the navigation bar is showing during the transition. – ullstrm Feb 12 '19 at 14:17
-1

After DAYS of figuring this out I think I've come up with the best (but far from perfect) solution for this. This way you can customize navigation bar when transition takes place and also handle interrupted transitions, like when user cancels popping back.

    func yourCustomizationMethod(bar: UINavigationBar) {
            // Modify the navigation bar
        }
            
    override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    guard navigationController?.topViewController == self else {
        return
    }
    // The coordinator is nil in some cases
    if let coordinator = transitionCoordinator,
        coordinator.animate(alongsideTransition: { context in
            guard let bar = self.navigationController?.navigationBar else {
                return
            }
            self.yourCustomizationMethod(bar: bar)
        }, completion: nil) {
        return
    } else if let bar = navigationController?.navigationBar {
        yourCustomizationMethod(bar: bar)
    }
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    // Customize only if user transition gets canceled to avoid multiple customizations.
    guard transitionCoordinator?.isCancelled == true,
        let bar = navigationController?.navigationBar else {
        return
    }
    yourCustomizationMethod(bar: bar)
}
Adam
  • 1,776
  • 1
  • 17
  • 28