Reputation: 770
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)
Upvotes: 38
Views: 38137
Reputation: 9612
Swift 5
It's quite easy to achieve this effect.
Simply create / get a gradient image and set it as the background image for a custom appearance, like below.
final class GradientNavBar_VC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
title = "Nav Bar with Gradient"
navigationController?.navigationBar.prefersLargeTitles = true
let appearance = UINavigationBarAppearance()
appearance.backgroundImage = UIImage(
named: "gradient_3")?
.resizableImage(
withCapInsets: .zero,
resizingMode: .stretch
)
navigationController?.navigationBar.scrollEdgeAppearance = appearance
navigationController?.navigationBar.standardAppearance = appearance
}
}
This is how my image and end result look like, solution supports both large title mode and rotation.
Nav bar
Image
This code snippet demonstrates how to create a UIImage
with a gradient effect using an array of colors.
extension UIImage {
static func gradientImage(colors: [UIColor],
size: CGSize = .init(width: 100, height: 100)) -> UIImage? {
let frame = CGRect(origin: .zero, size: size)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = frame
gradientLayer.colors = colors.map { $0.cgColor }
UIGraphicsBeginImageContextWithOptions(size, true, .zero)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
gradientLayer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
So basically you can either create a gradient image programmatically or use a pre-made one as an asset, and then apply it to achieve a custom appearance.
Upvotes: 0
Reputation: 757
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 {
}
}
Upvotes: 1
Reputation: 1664
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)
output Gradient will look like this.
Upvotes: 5
Reputation: 25294
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)
}
}
navigationBar.setGradientBackground(colors: [.lightGray, .red], startPoint: .topLeft, endPoint: .bottomRight)
Upvotes: 79
Reputation: 16032
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!
}
Upvotes: 26
Reputation: 1007
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
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)
Upvotes: 1
Reputation: 1598
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)
}
}
Upvotes: 5
Reputation: 11
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)
}
Upvotes: 0
Reputation: 15951
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
])
Upvotes: 8
Reputation: 16124
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;
}
Upvotes: 1
Reputation: 8571
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.
Upvotes: 10
Reputation: 1331
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!
}
Upvotes: 4
Reputation: 23882
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.
Upvotes: 29