Search code examples
iosuinavigationcontrolleruinavigationbarcore-animationcalayer

Adding sublayer to navigation bar conflicts with transition


I need to add a border underneath UIInavigationBar and I also want it to be global thing, so I go to my top hierarchy UIViewController 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.name = @"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 ([layer.name 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 viewDidLoad, but it turned out it's not the best idea, since I'm actually modifying global state of UINavigationBar. Next step was putting this method call to viewWillAppear and most of the time it's okay, but when I move from this ComboFeedViewController 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?


Solution

  • 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) {
            super.viewWillAppear(animated)
    
            // 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) {
            super.viewWillDisappear(animated)
    
            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: {
                            work()
                        })
                    } else {
                        work()
                    }
    
                    break
                }
            }
        }
    }