Gradient in navigation bar differing from view below it . - ios

The targeted design i want to reach is the below image :
I used the code below to create the gradient and set it to the navigation bar with the help of the function image from layer.
func image(fromLayer layer: CALayer) -> UIImage {
UIGraphicsBeginImageContext(layer.frame.size)
layer.render(in: UIGraphicsGetCurrentContext()!)
let outputImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return outputImage!
}
//navbar
let gradient = CAGradientLayer()
let cornerRadiusOfNavBar : CGFloat! = 0
if #available(iOS 10.0, *) {
gradient.colors = [Constants.Colors.violet.cgColor , Constants.Colors.green.cgColor ]
}
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
gradient.frame = self.navigationController!.navigationBar.bounds
gradient.cornerRadius = cornerRadiusOfNavBar
navigationItem.title = NSLocalizedString("Register", comment: "")
self.navigationController!.navigationBar.setBackgroundImage(self.image(fromLayer: gradient), for: .default)
and i have a stack view below the navigation bar , i used this code to set the gradient for it:
//stackview
let gradientView = CAGradientLayer()
let cornerRadiusOfStackView : CGFloat! = 0
if #available(iOS 10.0, *) {
gradientView.colors = [Constants.Colors.violet.cgColor , Constants.Colors.green.cgColor ]
}
gradientView.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientView.endPoint = CGPoint(x: 1.0, y: 0.5)
gradientView.frame = self.stackViewHoldingButtons.bounds
gradientView.cornerRadius = cornerRadiusOfStackView
self.stackViewHoldingButtons.layer.insertSublayer(gradientView, at: 0)
the result is this design below :
then i added these two lines of code to remove the navigation bar bottom border :
self.navigationController!.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
the result is as follows :
can any one help me to make the gradient homogeneous ??
P.S. : - I made sure that the navigationBar.isTranslucent property is set to false , that colors in both cases are set to sRGB , and i tried calling the lines that remove the border in both cases before and after setting the gradient.
thanks in advance.

Make the navigation bar fully transparent so the view's gradient layer will be shown there.
self.navigationController!.navigationBar.backgroundColor = UIColor.clear
self.navigationController!.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.isOpaque = false
self.navigationController?.navigationBar.isTranslucent = true
If you want it to be application-wise set these values in AppDelegate
UINavigationBar.appearance().backgroundColor = UIColor.clear
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
UINavigationBar.appearance().shadowImage = UIImage()
UINavigationBar.appearance().isOpaque = false
UINavigationBar.appearance().isTranslucent = true

Related

iOS 13 Tab Bar Layer attribute is missing swift

I have added shadow on Tab Bar in initial versions on layer, but on iOS 13 we have to use Appearance that don't have layer attribute, how to add Shadow now.
Shadow on top like below image.
if #available(iOS 13, *) {
let appearance = self.self.tabBarController?.tabBar.standardAppearance.copy()
appearance?.backgroundImage = UIImage()
appearance?.shadowImage = UIImage()
appearance?.shadowColor = .clear
//appearance?.layer this is missing now
if let appearance = appearance{
self.tabBarController?.tabBar.standardAppearance = appearance
}
} else {
self.tabBarController?.tabBar.shadowImage = UIImage()
self.tabBarController?.tabBar.backgroundImage = UIImage()
self.tabBarController?.tabBar.layer.shadowOpacity = 0.0
self.tabBarController?.tabBar.layer.borderWidth = 0.0
self.tabBarController?.tabBar.clipsToBounds = true
self.tabBarController?.tabBar.layer.applySketchShadow(color: UIColor(red: 15/255, green: 54/255, blue: 136/255, alpha: 1.0), alpha: 0.1, x: 0, y: 0, blur: 25, spread: 0)
self.tabBarController?.tabBar.clipsToBounds = false
}
Set clipsToBounds False will do the magic for you.
//**self.tabBarController?.tabBar.clipsToBounds = false**
if #available(iOS 13, *) {
let appearance = self.self.tabBarController?.tabBar.standardAppearance.copy()
appearance?.backgroundImage = UIImage()
appearance?.shadowImage = UIImage()
appearance?.configureWithTransparentBackground()
if let appearance = appearance{
self.tabBarController?.tabBar.standardAppearance = appearance
}
} else {
self.tabBarController?.tabBar.shadowImage = UIImage()
self.tabBarController?.tabBar.backgroundImage = UIImage()
self.tabBarController?.tabBar.clipsToBounds = false
}
self.tabBarController?.tabBar.clipsToBounds = false
self.tabBarController?.tabBar.layer.shadowOpacity = 0.0
self.tabBarController?.tabBar.layer.borderWidth = 0.0
self.tabBarController?.tabBar.layer.applySketchShadow(color: UIColor(red: 15/255, green: 54/255, blue: 136/255, alpha: 1.0), alpha: 0.1, x: 0, y: 0, blur: 25, spread: 0)
UIBarAppearance has shadowImage and shadowColor properties. Set them as desired. Right now you are setting them to have no shadow.
Also configureWithTransparentBackground means no shadow. So you are removing the shadow and then asking where the shadow is.
Try the below code in your UITabBarController class
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 13, *) {
let appearance = self.tabBar.standardAppearance.copy()
appearance.backgroundImage = UIImage()
appearance.shadowImage = UIImage()
self.tabBar.standardAppearance = appearance
} else {
self.tabBar.shadowImage = UIImage()
self.tabBar.backgroundImage = UIImage()
}
//Change These values according to your requirement. This will work for all iOS versions
self.tabBar.layer.shadowColor = UIColor.lightGray.cgColor
self.tabBar.layer.shadowOpacity = 0.3
self.tabBar.layer.shadowRadius = 5
}

Change navigation bar color in QLPreviewController

I want to change navigation bar color of QLPreviewController in swift 3. I have used below code to change color but its not working
viewQLPreview = QLPreviewController()
viewQLPreview.dataSource = self
viewQLPreview.delegate = self
viewQLPreview.navigationController?.navigationBar.isTranslucent = false
viewQLPreview.navigationController?.navigationBar.tintColor = UIColor.red
I have used below code to Change navigation bar color of QLPreviewController in swift 3.0
UINavigationBar.appearance().barTintColor = UIColor.red
UINavigationBar.appearance(whenContainedInInstancesOf: [QLPreviewController.self]).backgroundColor = UIColor.red
Use below code before present QLPreviewController :
UINavigationBar.appearance().tintColor = UIColor.red
UINavigationBar.appearance().barTintColor = UIColor.blue
UINavigationBar.appearance().titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.red]
UINavigationBar.appearance().setBackgroundImage(fromColor(color: UIColor.blue), for: .default)
UINavigationBar.appearance().isTranslucent = false
func fromColor (color: UIColor) -> UIImage{
let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
UIGraphicsBeginImageContext(rect.size)
let context: CGContext? = UIGraphicsGetCurrentContext()
context?.setFillColor(color.cgColor)
context?.fill(rect)
let image: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image ?? UIImage()
}
Put below code in viewDidLoad of QLPreviewController.
viewQLPreview.navigationController?.navigationBar.isTranslucent = false
viewQLPreview.navigationController?.navigationBar.tintColor = UIColor.red
Also make sure that viewQLPreview.navigationController != nil
If you are pushing QLPreviewController then this code will work....
If you are presenting QLPreviewController then you need to make sure that rootController should be navigation controller, in your case..
let viewQLPreview = QLPreviewController()
let nav = UINavigationController(rootViewController: viewQLPreview)
self.present(nav, animated: true, completion: nil)

custom background image with large titles NavigationBar in iOS 11

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.
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
}
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
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
}
}
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
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()
}
}
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
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

Custom UINavigationController title doesn't show up in subviews

I've created a custom UINavigationController class to style my app's navigation bar easily.
class NavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
configureNavigationBar()
}
}
extension NavigationController {
func configureNavigationBar() {
navigationBar.tintColor = UIColor.white
navigationBar.backgroundColor = UIColor.clear
navigationBar.isTranslucent = false
navigationBar.barStyle = .black
navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationBar.shadowImage = UIImage()
let attributes = [NSForegroundColorAttributeName: UIColor.white, NSFontAttributeName: UIFont(name: "Merriweather-Bold", size:17)!]
navigationBar.titleTextAttributes = attributes
let gradientLayer = CAGradientLayer()
let topColor:UIColor = UIColor(hex: 0xf91e4e)
let bottomColor:UIColor = UIColor(hex: 0xcc4d7f)
gradientLayer.colors = [bottomColor.cgColor, topColor.cgColor]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
gradientLayer.frame = CGRect(x: 0, y: -20, width: navigationBar.frame.size.width, height: navigationBar.frame.size.height + 20)
navigationBar.layer.insertSublayer(gradientLayer, at: 0)
UIApplication.shared.statusBarStyle = .lightContent
}
}
It works great in root viewControllers.
But the navigation bar's title doesn't show up in detail VC's.
I've tried these solutions so far and none of these work;
navigationItem.title = "Test"
2:
self.title = "Test"
3:
navigationController?.navigationBar.topItem?.title = "TEST" // This changes first viewController’s title
4:
navigationController?.navigationItem.title = "TEST"

How to make navigation bar transparent in iOS 10

I have the following code to make the navigation bar transparent but while still displaying the back button, this works on all versions of iOS but its stopped working with the iOS 10 beta
navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
navigationBar.shadowImage = UIImage()
navigationBar.isTranslucent = true
Has something changed with iOS 10 in this area?
Note its not possible to use navigationBar.isHidden as this would result in the navigation bar back button and title etc. disappearing also.
I don't know what has changed in iOS 10 to stop the previous code from working, but to fix it I created a transparent image (it only needs to be one pixel in dimension) and used the following code to make the navigation bar transparent (but still showing the back navigation button).
let transparentPixel = UIImage(named: "TransparentPixel")
navigationBar.setBackgroundImage(transparentPixel, for: UIBarMetrics.default)
navigationBar.shadowImage = transparentPixel
navigationBar.backgroundColor = UIColor.clear()
navigationBar.isTranslucent = true
Incidentally, if you want to change the color of the navigation bar, you can use the same principle:
let redPixel = UIImage(named: "RedPixel")
navigationBar.setBackgroundImage(redPixel, for: UIBarMetrics.default)
navigationBar.shadowImage = redPixel
navigationBar.isTranslucent = false
The solution #Essence provided works perfectly!
This is what I am using even to create the 1px transparent image by code:
class MainClass: UIViewController {
let transparentPixel = UIImage.imageWithColor(color: UIColor.clear)
override func viewWillAppear(_ animated: Bool) {
drawCustomNavigationBar()
}
func drawCustomNavigationBar() {
let nav = (self.navigationController?.navigationBar)!
nav.setBackgroundImage(transparentPixel, for: UIBarMetrics.default)
nav.shadowImage = transparentPixel
nav.isTranslucent = true
}
}
extension UIImage {
class func imageWithColor(color: UIColor) -> UIImage {
let rect = CGRect(origin: CGPoint(x: 0, y:0), size: CGSize(width: 1, height: 1))
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()!
context.setFillColor(color.cgColor)
context.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
Swift 3.x
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.backgroundColor = .clear
self.navigationController?.navigationBar.isTranslucent = true

Resources