How can I set the UINavigationbar with gradient color?

37.4k views Asked by At

I want to set the UINavigationbar backgroundColor to a gradient color where I would like to set it via an array of colors to create a Gradient, ideally, as accessible methods inside UINavigationBar to change its color to this gradient.

Any suggestions? (Aside from setting an image manually as the background image of the navigation bar)

12

There are 12 answers

0
Ashish Kakkad On BEST ANSWER

Create gradient layer and add it as background of navigation bar.

    CAGradientLayer *gradient = [CAGradientLayer layer];
    gradient.frame = self.navigationController.navigationBar.bounds;
    gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor whiteColor] CGColor], (id)[[UIColor blackColor] CGColor], nil];
    [self.navigationController.navigationBar setBackgroundImage:[self imageFromLayer:gradient] forBarMetrics:UIBarMetricsDefault];

For creating image from layer.

- (UIImage *)imageFromLayer:(CALayer *)layer
{
    UIGraphicsBeginImageContext([layer frame].size);

    [layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return outputImage;
}

One more thing, there is one library available in github : CRGradientNavigationBar you can also use this library.

3
Hardik Thakkar On

For Swift 4.2

extension UINavigationBar {
    func setGradientBackground(colors: [Any]) {
        let gradient: CAGradientLayer = CAGradientLayer()
        gradient.locations = [0.0 , 0.5, 1.0]
        gradient.startPoint = CGPoint(x: 0.0, y: 1.0)
        gradient.endPoint = CGPoint(x: 1.0, y: 1.0)

        var updatedFrame = self.bounds
        updatedFrame.size.height += self.frame.origin.y
        gradient.frame = updatedFrame
        gradient.colors = colors;
        self.setBackgroundImage(self.image(fromLayer: gradient), for: .default)
    }

    func image(fromLayer layer: CALayer) -> UIImage {
        UIGraphicsBeginImageContext(layer.frame.size)
        layer.render(in: UIGraphicsGetCurrentContext()!)
        let outputImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return outputImage!
    }
}

How to use

   self.navigationController?.navigationBar.setGradientBackground(colors: [
            UIColor.red.cgColor,
            UIColor.green.cgColor,
            UIColor.blue.cgColor
            ])
3
Can On

This is the solution without using an intermediate CAGradientLayer, and just using CoreGraphics, in Swift 3.0.

Essentially, the method creates a UIImage on the fly with the gradient colors passed and sets it.

extension UINavigationBar
{
    /// Applies a background gradient with the given colors
    func apply(gradient colors : [UIColor]) {
        var frameAndStatusBar: CGRect = self.bounds
        frameAndStatusBar.size.height += 20 // add 20 to account for the status bar

        setBackgroundImage(UINavigationBar.gradient(size: frameAndStatusBar.size, colors: colors), for: .default)
    }

    /// Creates a gradient image with the given settings
    static func gradient(size : CGSize, colors : [UIColor]) -> UIImage?
    {
        // Turn the colors into CGColors
        let cgcolors = colors.map { $0.cgColor }

        // Begin the graphics context
        UIGraphicsBeginImageContextWithOptions(size, true, 0.0)

        // If no context was retrieved, then it failed
        guard let context = UIGraphicsGetCurrentContext() else { return nil }

        // From now on, the context gets ended if any return happens
        defer { UIGraphicsEndImageContext() }

        // Create the Coregraphics gradient
        var locations : [CGFloat] = [0.0, 1.0]
        guard let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: cgcolors as NSArray as CFArray, locations: &locations) else { return nil }

        // Draw the gradient
        context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: [])

        // Generate the image (the defer takes care of closing the context)
        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

The defer statement makes this so much clean than prior versions. Be noted that CGGradient are available since iOS 8.0.

Also, this creates the gradient from left to right, tweaking the parameters of drawLinearGradient (start and end) moves the locations. This is up for your implementation.

1
Denis Kutlubaev On

Objective C solution that works on iPhone X as well:

- (void)addGradientToNavigationBar
{
    CAGradientLayer *gradient = [CAGradientLayer layer];
    CGRect gradientFrame = self.navigationController.navigationBar.bounds;
    gradientFrame.size.height += [UIApplication sharedApplication].statusBarFrame.size.height;
    gradient.frame = gradientFrame;
    gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor colorGradientUp] CGColor], (id)[[UIColor colorGradientDown] CGColor], nil];
    [self.navigationController.navigationBar setBackgroundImage:[self imageFromLayer:gradient] forBarMetrics:UIBarMetricsDefault];
}

- (UIImage *)imageFromLayer:(CALayer *)layer
{
    UIGraphicsBeginImageContext([layer frame].size);

    [layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return outputImage;
}
0
shndrs On

This is a framework for different UI components gradient include UINavigation bar: enter link description here

first : then in your root ViewController that it embedded into an UINavigationController

import SHNDStuffs

put this in your ViewDidLoad()

class RootViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        SHNDNavigationBarGradient(firstColor: .darkGray,
                                  secondColor: .white,
                                  tintColor: .black,
                                  isHorizontal: true)
}
0
Ice On

For SwiftUI we can use next code, and this is also should work with UIKit:

private func navigationBarAppearance() {

    let gradientColors = [UIColor.red.cgColor, UIColor.blue.cgColor] // Replace with your desired gradient colors

    // Create a CAGradientLayer with the gradient colors
    let gradientLayer = CAGradientLayer()
    gradientLayer.colors = gradientColors
    gradientLayer.locations = [0, 1] // Change as needed for your desired gradient effect
// Comment code below and the standard direction will be from top to bottom
    gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
    gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)


    // Set the frame of the gradient layer to match the navigation bar's frame
    let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
    let statusBarManager = windowScene?.statusBarManager
    gradientLayer.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: (statusBarManager?.statusBarFrame.height ?? 0) + UINavigationController().navigationBar.frame.height)

    // Convert the gradient layer to a UIImage
    UIGraphicsBeginImageContext(gradientLayer.frame.size)
    gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
    let backgroundImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    // Set the background image of the navigation bar
    let appearance = UINavigationBarAppearance()
    appearance.backgroundImage = backgroundImage
UINavigationBar.appearance().standardAppearance = appearance
        UINavigationBar.appearance().scrollEdgeAppearance = appearance
    
}

And use this in the init on View, not is the onAppear, only in the init of View or another equivalent place

struct ContentView: View {

    init() {

        navigationBarAppearance()
    }
    
    var body: some View {
  }
}
2
pableiros On

In Swift 3, Swift 4 and Swift 5:

let gradient = CAGradientLayer()
let sizeLength = UIScreen.main.bounds.size.height * 2
let defaultNavigationBarFrame = CGRect(x: 0, y: 0, width: sizeLength, height: 64)

gradient.frame = defaultNavigationBarFrame

gradient.colors = [UIColor.white.cgColor, UIColor.black.cgColor]

UINavigationBar.appearance().setBackgroundImage(self.image(fromLayer: gradient), for: .default)

For creating image from layer:

func image(fromLayer layer: CALayer) -> UIImage {
    UIGraphicsBeginImageContext(layer.frame.size)

    layer.render(in: UIGraphicsGetCurrentContext()!)

    let outputImage = UIGraphicsGetImageFromCurrentImageContext()

    UIGraphicsEndImageContext()

    return outputImage!
}

In Swift 2:

let gradient = CAGradientLayer()
let sizeLength = UIScreen.mainScreen().bounds.size.height * 2
let defaultNavigationBarFrame = CGRectMake(0, 0, sizeLength, 64)

gradient.frame = defaultNavigationBarFrame

gradient.colors = [UIColor.whiteColor().CGColor, UIColor.blackColor().CGColor]

UINavigationBar.appearance().setBackgroundImage(self.image(fromLayer: gradient), forBarMetrics: .Default)

For creating image from layer:

func image(fromLayer layer: CALayer) -> UIImage {    
    UIGraphicsBeginImageContext(layer.frame.size)

    layer.renderInContext(UIGraphicsGetCurrentContext()!)

    let outputImage = UIGraphicsGetImageFromCurrentImageContext()

     UIGraphicsEndImageContext()

    return outputImage!
}
0
Niklas On

In Swift 3

let gradient = CAGradientLayer()
let sizeLength = UIScreen.main.bounds.size.height * 2
let defaultNavigationBarFrame = CGRect(x: 0, y: 0, width: sizeLength, height: 64)
gradient.frame = defaultNavigationBarFrame
gradient.colors = [UIColor.white.cgColor, UIColor.black.cgColor]
UINavigationBar.appearance().setBackgroundImage(self.image(fromLayer: gradient), for: .default)

func image(fromLayer layer: CALayer) -> UIImage {
    UIGraphicsBeginImageContext(layer.frame.size)
    layer.render(in: UIGraphicsGetCurrentContext()!)
    let outputImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return outputImage!
}
12
Vasily  Bodnarchuk On

Details

  • Xcode 11.4 (11E146), swift 5
  • Tested on iOS 13.1, 12.2, 11.0.1

Solution

class UINavigationBarGradientView: UIView {

    enum Point {
        case topRight, topLeft
        case bottomRight, bottomLeft
        case custom(point: CGPoint)

        var point: CGPoint {
            switch self {
                case .topRight: return CGPoint(x: 1, y: 0)
                case .topLeft: return CGPoint(x: 0, y: 0)
                case .bottomRight: return CGPoint(x: 1, y: 1)
                case .bottomLeft: return CGPoint(x: 0, y: 1)
                case .custom(let point): return point
            }
        }
    }

    private weak var gradientLayer: CAGradientLayer!

    convenience init(colors: [UIColor], startPoint: Point = .topLeft,
                     endPoint: Point = .bottomLeft, locations: [NSNumber] = [0, 1]) {
        self.init(frame: .zero)
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = frame
        layer.addSublayer(gradientLayer)
        self.gradientLayer = gradientLayer
        set(colors: colors, startPoint: startPoint, endPoint: endPoint, locations: locations)
        backgroundColor = .clear
    }

    func set(colors: [UIColor], startPoint: Point = .topLeft,
             endPoint: Point = .bottomLeft, locations: [NSNumber] = [0, 1]) {
        gradientLayer.colors = colors.map { $0.cgColor }
        gradientLayer.startPoint = startPoint.point
        gradientLayer.endPoint = endPoint.point
        gradientLayer.locations = locations
    }

    func setupConstraints() {
        guard let parentView = superview else { return }
        translatesAutoresizingMaskIntoConstraints = false
        topAnchor.constraint(equalTo: parentView.topAnchor).isActive = true
        leftAnchor.constraint(equalTo: parentView.leftAnchor).isActive = true
        parentView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        parentView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        guard let gradientLayer = gradientLayer else { return }
        gradientLayer.frame = frame
        superview?.addSubview(self)
    }
}

extension UINavigationBar {
    func setGradientBackground(colors: [UIColor],
                               startPoint: UINavigationBarGradientView.Point = .topLeft,
                               endPoint: UINavigationBarGradientView.Point = .bottomLeft,
                               locations: [NSNumber] = [0, 1]) {
        guard let backgroundView = value(forKey: "backgroundView") as? UIView else { return }
        guard let gradientView = backgroundView.subviews.first(where: { $0 is UINavigationBarGradientView }) as? UINavigationBarGradientView else {
            let gradientView = UINavigationBarGradientView(colors: colors, startPoint: startPoint,
                                                           endPoint: endPoint, locations: locations)
            backgroundView.addSubview(gradientView)
            gradientView.setupConstraints()
            return
        }
        gradientView.set(colors: colors, startPoint: startPoint, endPoint: endPoint, locations: locations)
    }
}

Usage

navigationBar.setGradientBackground(colors: [.lightGray, .red], startPoint: .topLeft, endPoint: .bottomRight)
0
Kévin Renella On

This amazing tutorial from Lawrence Tan  shows how to set a gradient using barTintColor and no backgroundImage: https://medium.com/swift2go/add-gradient-to-navigation-bar-in-swift-9284fe91fea2

Summary

CAGradientLayer extension

extension CAGradientLayer {

    class func primaryGradient(on view: UIView) -> UIImage? {
        let gradient = CAGradientLayer()
        let flareRed = UIColor(displayP3Red: 241.0/255.0, green: 39.0/255.0, blue: 17.0/255.0, alpha: 1.0)
        let flareOrange = UIColor(displayP3Red: 245.0/255.0, green: 175.0/255.0, blue: 25.0/255.0, alpha: 1.0)
        var bounds = view.bounds
        bounds.size.height += UIApplication.shared.statusBarFrame.size.height
        gradient.frame = bounds
        gradient.colors = [flareRed.cgColor, flareOrange.cgColor]
        gradient.startPoint = CGPoint(x: 0, y: 0)
        gradient.endPoint = CGPoint(x: 1, y: 0)
        return gradient.createGradientImage(on: view)
    }

    private func createGradientImage(on view: UIView) -> UIImage? {
        var gradientImage: UIImage?
        UIGraphicsBeginImageContext(view.frame.size)
        if let context = UIGraphicsGetCurrentContext() {
            render(in: context)
            gradientImage = UIGraphicsGetImageFromCurrentImageContext()?.resizableImage(withCapInsets: UIEdgeInsets.zero, resizingMode: .stretch)
        }
        UIGraphicsEndImageContext()
        return gradientImage
    }
}

Apply the gradient

guard
    let navigationController = navigationController,
    let flareGradientImage = CAGradientLayer.primaryGradient(on: navigationController.navigationBar)
    else {
        print("Error creating gradient color!")
        return
    }

navigationController.navigationBar.barTintColor = UIColor(patternImage: flareGradientImage)
1
Azharhussain Shaikh On

SWIFT 5

To make the horizontal gradient background of the navigation bar along with status bar too.. just put this code in your viewcontroller's viewDidLoad() method.

    self.navigationItem.title = "Gradiant Back Ground"
    let gradientLayer = CAGradientLayer()
    var updatedFrame = self.navigationController!.navigationBar.bounds
    updatedFrame.size.height += UIApplication.shared.statusBarFrame.size.height
    gradientLayer.frame = updatedFrame
    gradientLayer.colors = [UIColor.green.cgColor, UIColor.blue.cgColor] // start color and end color
    gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0) // Horizontal gradient start
    gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0) // Horizontal gradient end
    UIGraphicsBeginImageContext(gradientLayer.bounds.size)
    gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    self.navigationController!.navigationBar.setBackgroundImage(image, for: UIBarMetrics.default)

Gradient will look like this

output Gradient will look like this.

2
Jimmy_m On

Swift 5

  • Includes status bar in gradient.
  • Doesn't use an image.
  • Uses transparency so content is visible through nav bar.
extension UINavigationBar {

    func addGradient(_ toAlpha: CGFloat, _ color: UIColor) {
        let gradient = CAGradientLayer()
        gradient.colors = [
            color.withAlphaComponent(toAlpha).cgColor,
            color.withAlphaComponent(toAlpha).cgColor,
            color.withAlphaComponent(0).cgColor
        ]
        gradient.locations = [0, 0.8, 1]
        var frame = bounds
        frame.size.height += UIApplication.shared.statusBarFrame.size.height
        frame.origin.y -= UIApplication.shared.statusBarFrame.size.height
        gradient.frame = frame
        layer.insertSublayer(gradient, at: 1)
    }

}