I want to achieve smooth animation between views with a different UINavigationBar background colors. Embedded views have the same background color as UINavigationBar and I want to mimic push/pop transition animation like:
I've prepared custom transition:
class CustomTransition: NSObject, UIViewControllerAnimatedTransitioning {
private let duration: TimeInterval
private let isPresenting: Bool
init(duration: TimeInterval = 1.0, isPresenting: Bool) {
self.duration = duration
self.isPresenting = isPresenting
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
guard
let toVC = transitionContext.viewController(forKey: .to),
let fromVC = transitionContext.viewController(forKey: .from),
let toView = transitionContext.view(forKey: .to),
let fromView = transitionContext.view(forKey: .from)
else {
return
}
let rightTranslation = CGAffineTransform(translationX: container.frame.width, y: 0)
let leftTranslation = CGAffineTransform(translationX: -container.frame.width, y: 0)
toView.transform = isPresenting ? rightTranslation : leftTranslation
container.addSubview(toView)
container.addSubview(fromView)
fromVC.navigationController?.navigationBar.backgroundColor = .clear
fromVC.navigationController?.navigationBar.setBackgroundImage(UIImage.fromColor(color: .clear), for: .default)
UIView.animate(
withDuration: self.duration,
animations: {
fromVC.view.transform = self.isPresenting ? leftTranslation :rightTranslation
toVC.view.transform = .identity
},
completion: { _ in
fromView.transform = .identity
toVC.navigationController?.navigationBar.setBackgroundImage(
UIImage.fromColor(color: self.isPresenting ? .yellow : .lightGray),
for: .default
)
transitionContext.completeTransition(true)
}
)
}
}
And returned it in the UINavigationControllerDelegate method implementation:
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return CustomTransition(isPresenting: operation == .push)
}
While push animation works pretty well pop doesn't.
Questions:
- Why after clearing NavBar color before pop animation it remains yellow?
- Is there any better way to achieve my goal? (navbar can't just be transparent all the time because it's only a part of the flow)
Here is the link to my test project on GitHub.
EDIT
Here is the gif presenting the full picture of discussed issue and the desired effect:



These components are always very difficult to customize. I think, Apple wants system components to look and behave equally in every app, because it allows to keep shared user experience around whole iOS environment.
Sometimes, it easier to implement your own components from scratch instead of trying to customize system ones. Customization often could be tricky because you do not know for sure how components are designed inside. As a result, you have to handle lots of edge cases and deal with unnecessary side effects.
Nevertheless, I believe I have a solution for your situation. I have forked your project and implemented behavior you had described. You can find my implementation on GitHub. See
animation-implementationbranch.UINavigationBar
The root cause of pop animation does not work properly, is that
UINavigationBarhas it's own internal animation logic. WhenUINavigationController'sstack changes,UINavigationControllertellsUINavigationBarto changeUINavigationItems. So, at first, we need to disable system animation forUINavigationItems. It could be done by subclassingUINavigationBar:Then
UINavigationControllershould be initialized withCustomNavigationBar:UINavigationController
Since there is requirement to keep animation smooth and synchronized between
UINavigationBarand presentedUIViewController, we need to create custom transition animation object forUINavigationControllerand useCoreAnimationwithCATransaction.Custom transition
Your implementation of transition animator almost perfect, but from my point of view few details were missed. In the article Customizing the Transition Animations you can find more info. Also, please pay attention to methods comments in
UIViewControllerContextTransitioningprotocol.So, my version of push animation looks as follows:
Pop animation implementation is almost the same. The only difference in
CABasicAnimationvalues offromValueandtoValueproperties.UINavigationBar animation
In order to animate
UINavigationBarwe have to addCATransitionanimation onUINavigationBarlayer:The code above will animate whole
UINavigationBar. In order to animate only background ofUINavigationBarwe need to retrieve background view fromUINavigationBar. And here is the trick: first subview ofUINavigationBaris_UIBarBackgroundview (it could be explored using Xcode Debug View Hierarchy). Exact class is not important in our case, it is enough that it is successor ofUIView. Finally we could add our animation transition on_UIBarBackground's view layer direcly:I would like to note, that we are making prediction that first subview is a background view. View hierarchy could be changed in future, just keep this in mind.
It is important to add both animations in one
CATransaction, because in this case these animations will run simultaneously.You could setup
UINavigationBarbackground color inviewWillAppearmethod of every view controller.Here is how final animation looks like:
I hope this helps.