Reputation: 2908
How do you set a custom background image for the large title NavigationBar in iOS 11? I'm using a custom subclass which I've assigned to the navigationControllers in the storyboard.
This is how I create my custom NavBar:
class CustomNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationBar.tintColor = UIColor(red:1, green:1, blue:1, alpha:0.6)
self.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
if #available(iOS 11.0, *) {
self.navigationBar.prefersLargeTitles = true
self.navigationItem.largeTitleDisplayMode = .automatic
self.navigationBar.largeTitleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
self.navigationBar.barTintColor = UIColor.green
}
self.navigationBar.isTranslucent = false
self.navigationBar.setBackgroundImage(#imageLiteral(resourceName: "navigationBarBackground"), for: .default)
self.navigationBar.shadowImage = #imageLiteral(resourceName: "navigationBarShadow")
}
}
Strangely the setBackgroundImage(image, for: .default)
doesn't work for the large titles. It worked before with iOS 10 and also if I rotate the iPhone (and activate the small NavBar) the background is back?
Edit:
The backgroundImage
is still rendered but somehow hidden. Only if you start scrolling and the "normal" Navigation Bar appears, the backgroundImage is visible. Also the barTintColor
is completely ignored in this case.
Upvotes: 26
Views: 13491
Reputation: 108
Finally I found solution!
Edit: Works on iOS 13 and higher
You can use it before view appears, eg: in viewDidLoad() method:
override func viewDidLoad()
{
super.viewDidLoad()
let largeTitleAppearance = UINavigationBarAppearance()
largeTitleAppearance.configureWithOpaqueBackground()
largeTitleAppearance.backgroundImage = UIImage(named: "BackgroundImage.png")
self.navigationBar.standardAppearance = largeTitleAppearance
self.navigationBar.scrollEdgeAppearance = largeTitleAppearance
}
All that you need is:
Create UINavigationBarAppearance instance:
let largeTitleAppearance = UINavigationBarAppearance()
Apple documentation:
UINavigationBarAppearance - An object for customizing the appearance of a navigation bar.
Configure it:
largeTitleAppearance.configureWithOpaqueBackground()
"Opaque" here because we want to set colorised image (but in practice it doesn't matter, what configure will you set)
Set background image:
largeTitleAppearance.backgroundImage = UIImage(named: "BackgroundImage.png") // Set here image that you need
Assign our largeTitleAppearance object to both standardAppearance and scrollEdgeAppearance navigationBar's fields:
self.navigationBar.standardAppearance = largeTitleAppearance // For large-navigationBar condition when it is collapsed
self.navigationBar.scrollEdgeAppearance = largeTitleAppearance // For large-navigationBar condition when it is expanded
Apple documentation:
.standardAppearance - The appearance settings for a standard-height navigation bar.
.scrollEdgeAppearance - The appearance settings to use when the edge of any scrollable content reaches the matching edge of the navigation bar.
This helped to me: https://sarunw.com/posts/uinavigationbar-changes-in-ios13/#going-back-to-old-style
Upvotes: 8
Reputation: 684
Changing the barTint didn't work for me so I change the layer inside navigationBar
navigationBar.layer.backgroundColor = UIColor(patternImage:
UIImage(named: "BG-Roof1")!.resizableImage(withCapInsets:
UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0), resizingMode: .stretch)).cgColor
Upvotes: 0
Reputation: 844
Piggybacking on oldrinmendez's answer - that solution works perfect for a horizontal gradient.
For a VERTICAL gradient, I was able to use the same function from oldrinmendez's answer by calling it again in scrollViewDidScroll. This continually adjusts the height of the gradient image as the user scrolls.
Start with the function from oldrinmendez :
func imageWithGradient(startColor:UIColor, endColor:UIColor, size:CGSize, horizontally:Bool) -> UIImage? {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
if horizontally {
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
} else {
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
}
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
Create an update function to call it with the options you want:
func updateImageWithGradient() {
let navBarHeight = self.navigationController?.navigationBar.frame.size.height
let statusBarHeight = UIApplication.shared.statusBarFrame.height
let heightAdjustment: CGFloat = 2
let gradientHeight = navBarHeight! + statusBarHeight + heightAdjustment
let bgimage = imageWithGradient(startColor: UIColor.red, endColor: UIColor.orange, size: CGSize(width: UIScreen.main.bounds.size.width, height: gradientHeight), horizontally: false)
navigationController?.navigationBar.barTintColor = UIColor(patternImage: bgimage!)
}
Finally add the update function to scrollViewDidScroll & ViewDidApper: Use ViewDidAppear so the correct navigation bar height is returned
override func viewDidAppear(_ animated: Bool) {
updateImageWithGradient()
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
DispatchQueue.main.async {
updateImageWithGradient()
}
}
Upvotes: 1
Reputation: 51
Try this code (Swift 4.0):
in viewDidLoad()
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.black]
if #available(iOS 11.0, *) {
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationItem.largeTitleDisplayMode = .automatic
self.navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.black]
} else {
//iOS <11.0
}
self.title = "Title"
self.navigationController?.navigationBar.barTintColor = UIColor(patternImage: #imageLiteral(resourceName: "nav_bg"))
self.navigationController?.navigationBar.isTranslucent = false
Upvotes: 3
Reputation: 459
In Xamarin it would be like this:
this.NavigationBar.BackgroundColor = UIColor.Clear;
var gradientLayer = new CAGradientLayer
{
Frame = new CGRect(0, 0, UIApplication.SharedApplication.StatusBarFrame.Width,
UIApplication.SharedApplication.StatusBarFrame.Height + this.NavigationBar.Frame.Height),
Colors = new CGColor[]
{Constants.Defaults.Navigation.RealBlueColor.ToCGColor(), Constants.Defaults.Navigation.RealBlueColor.ToCGColor()}
};
UIGraphics.BeginImageContext(gradientLayer.Bounds.Size);
gradientLayer.RenderInContext((UIGraphics.GetCurrentContext()));
UIImage image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
this.View.Layer.InsertSublayer(gradientLayer, 0);
this.NavigationBar.BarTintColor = UIColor.FromPatternImage(image);
The this.View.Layer.Insert is optional. I need it when I'm "curling" up and down an image on the NavigationBar
Upvotes: 0
Reputation: 186
In iOS 11 you no more need set BackgroundImage(Remove its declaration) if you use large titles. Instead you need use BarTintColor.
class CustomNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationBar.tintColor = UIColor(red:1, green:1, blue:1, alpha:0.6)
self.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
if #available(iOS 11.0, *) {
self.navigationBar.prefersLargeTitles = true
self.navigationItem.largeTitleDisplayMode = .automatic
self.navigationBar.largeTitleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
self.navigationBar.barTintColor = UIColor(red:1, green:1, blue:1, alpha:1)
}
else {
self.navigationBar.setBackgroundImage(#imageLiteral(resourceName: "navigationBarBackground"), for: .default)
}
self.navigationBar.shadowImage = #imageLiteral(resourceName: "navigationBarShadow")
self.navigationBar.isTranslucent = false
}
}
Upvotes: 6
Reputation: 991
I had the same issue, fixed it by
Remove setBackgroundImage and use barTint color with pattern image
let bgimage = imageWithGradient(startColor: UIColor.red, endColor: UIColor.yellow, size: CGSize(width: UIScreen.main.bounds.size.width, height: 1))
self.navigationBar.barTintColor = UIColor(patternImage: bgimage!)
Get image with gradient colors
func imageWithGradient(startColor:UIColor, endColor:UIColor, size:CGSize, horizontally:Bool = true) -> UIImage? {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
if horizontally {
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
} else {
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
}
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
Upvotes: 20