cojoj cojoj - 5 months ago 15
iOS Question

Adding sublayer to navigation bar conflicts with transition

I need to add a border underneath

and I also want it to be global thing, so I go to my top hierarchy
and put there a method similiar to this one:

- (void)setupNavigationBarBorder {
if (![self isKindOfClass:[CombinedViewController class]]) {
CGRect borderRect = CGRectMake(0, self.navigationController.navigationBar.frame.size.height-0.5f, self.navigationController.navigationBar.frame.size.width, 0.5f);
CALayer *border = [CALayer layer];
border.frame = borderRect; = @"border";
[border setBackgroundColor:[UIColor greenColor].CGColor];
[self.navigationController.navigationBar.layer addSublayer:border];
} else {
NSArray* sublayers = [NSArray arrayWithArray:self.navigationController.navigationBar.layer.sublayers];
for (CALayer *layer in sublayers) {
if ([ isEqualToString:@"border"]) {
[layer removeFromSuperlayer];

Which is a simple trigger for drawing and removing this sublayer. Pretty straightforward. So at first I've decided I'll put this code in
, but it turned out it's not the best idea, since I'm actually modifying global state of
. Next step was putting this method call to
and most of the time it's okay, but when I move from this
to the one that should have this border... Well, it gets drawn instantly.

I want it to be shown just when the transition is over or in the best case scenario, appear with transition. How Can I achieve this?

ksm ksm

Please have a look at Apple's Customizing UINavigationBar sample code. The example's there cover a lot of ground in terms of customizing the navigation bar.

If you want to visually modify a navigation bar, but only have that modification visible in one of the screens in a navigation stack, then your best bet is to hook into these two methods:

  • viewWillAppear: for modifying something in the bar
  • viewWillDisappear: for hiding that modification

The didAppear or didDisappear methods could also work for you. But I find that hooking into the willAppear/willDisappear variants and respecting the animated parameter worked wonders for me.

Here's sample code taken out of one of my projects that hides the hairline (the border) below the navigation bar (useful if you want to stick a UIToolbar below the UINavigationBar in one of the screens, to have a double sized bar):

class ViewController: UIViewController {
    override func viewWillAppear(animated: Bool) {

        // We need to hide the hairline, so the bar appears
        // continous with the toolbar below it.
        navigationController?.navigationBar.setHairlineEnabled(false, animated: animated)

    override func viewWillDisappear(animated: Bool) {

        navigationController?.navigationBar.setHairlineEnabled(true, animated: animated)

extension UINavigationBar {
    /// Hides the hairline below the navigation bar by walking
    /// the subview hierarchy and finding a 0.5 or 1 pt tall view.
    func setHairlineEnabled(enabled: Bool, animated: Bool) {
        guard subviews.count > 0 else { return }

        let firstSubview = subviews[0]

        for subview in firstSubview.subviews {
            let height = subview.bounds.height

            if height == CGFloat(0.5) || height == CGFloat(1.0) {
                let work = {
                    subview.alpha = enabled ? CGFloat(1) : CGFloat(0)

                if animated {
                    UIView.animateWithDuration(NSTimeInterval(UINavigationControllerHideShowBarDuration), animations: {
                } else {