Custom UINavigationBar gradient background and image in logo - ios

I have some problems with inheriting UINavigationBar.
First of all, I have to use the same class in multiple UINavigationBar, since everything is embedded in UITabBar. So I have a UITabBar connected to 5 different UINavigationBar.
All these UINavigationBars should have a gradient background and an image as title. I tried inheriting and setting this class to each of them, but it does not work.
class NavBar: UINavigationBar {
func setupNavBar(){
self.topItem?.titleView = UIImageView(image: UIImage(named: "image"))
self.topItem?.titleView?.contentMode = .scaleAspectFit
let gradient = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = [UIColor.blue.cgColor, UIColor.blue.cgColor]
gradient.locations = [0.0, 1.0]
self.layer.insertSublayer(gradient, at: 0)
self.setNeedsLayout()
}
override func didMoveToSuperview() {
self.setupNavBar()
}
}
I have tried different combinations, but in most of them I cannot see the image. Also it happened to have a different behavior by calling the same class in different UINavigationBar, for instance in the first tab I can see the gradient, but if I change it then it is not there.
Please help me to solve this by using the subclass, I do not want to put the code in controllers.
Extra: I would also like to fill the status bar with gradient, but I can only fill static color

Solved by inheriting UITabBarController instead and putting gradient as backgroundImage
class NavBarController: UINavigationController {
override func viewDidLayoutSubviews() {
self.navigationBar.topItem?.titleView = UIImageView(image: UIImage(named: "image"))
self.navigationBar.topItem?.titleView?.contentMode = .scaleAspectFit
let gradient = CAGradientLayer()
var bounds = self.navigationBar.bounds
bounds.size.height += self.view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
bounds.origin.y -= self.view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
gradient.frame = bounds
gradient.colors = [UIColor.black, UIColor.white]
gradient.locations = [0.0, 1.0]
self.navigationBar.setBackgroundImage(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!
}
}

Related

Set UINavigationBar height proportionally to screen size

I have 5 tabs in UITabBarController and each of them is embedded in a UINavigationController. I would like to set the height of this tab to 8% of the screen, but I cannot set constraints to StoryBoard, also if I try to set it from code it turns black and break UI controls.
The code for my UINavigationController is this:
class NavBarController: UINavigationController {
override func viewDidLayoutSubviews() {
self.navigationBar.topItem?.titleView = UIImageView(image: UIImage(named: "image"))
self.navigationBar.topItem?.titleView?.contentMode = .scaleAspectFit
let gradient = CAGradientLayer()
var bounds = self.navigationBar.bounds
bounds.size.height += self.view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
bounds.origin.y -= self.view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
gradient.frame = bounds
gradient.colors = [UIColor.black, UIColor.white]
gradient.locations = [0.0, 1.0]
self.navigationBar.setBackgroundImage(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!
}
}
I tried by setting:
self.navigationBar.frame.size.height = (self.view.superview?.frame.height)! * 0.08
self.navigationBar.layoutIfNeeded()
at the beginning of viewDidLayoutSubviews, but it does not work and also breaks the gradient background. Can someone help me? The ideal solution would be through storyboard, but I cannot simply set the UINavigationItem height constraint to its view height.
After spending some time looking into this, I found that UINavigationBar is given a height of 44pts several times throughout the transition process. Since UIKit is insisting on a height of 44, I decided to employ some trickery with a custom setter.
class NavigationBar: UINavigationBar {
private var _frame: CGRect!
override var frame: CGRect {
set {
_frame = CGRect(origin: newValue.origin, size: CGSize(width: newValue.width, height: 22))
}
get {
_frame
}
}
}
Here I'm forcing the height of the navigation bar to 22 pts. However, after compiling and running the nav bar is still 44. Why? Because UIKit is also setting a constraint, forcing the height to 44. This means that AutoLayout is reverting the height to 44 upon layout. Seems that this is something Apple really doesn't want you to change.

Adding a gradient to UIImageView

I'm trying to add a gradient to UIImageView.
viewDidLoad() {
self.bookImageBig.sd_setImage(with: URL(string: self.book.image), placeholderImage: nil)
self.bookImage.sd_setImage(with: URL(string: self.book.image), placeholderImage: nil)
let view = UIView(frame: bookImageBig.frame)
let gradient = CAGradientLayer()
gradient.frame = view.frame
gradient.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]
gradient.locations = [0.0, 1.0]
view.layer.insertSublayer(gradient, at: 0)
bookImageBig.addSubview(view)
bookImageBig.bringSubviewToFront(view)
I'm trying to add a gradient to bookImageBig UIImageView.
However, the gradient only partially covers the image. 30px of the image is not covered by the gradient. The image was added to the story board and is being referenced in the view controller.
Can someone please help?
Instead of frame use bounds
gradient.frame = self.bookImageBig.bounds
For frame and bounds difference look at this post

Bottom part of tab bar does not get properly colored

I want to custom color my tab bar element in my custom UITabController sub-class and it works fine when I'm doing it with:
tabBar.barTintColor = .blue (With any system or custom color)
But when I'm trying to add a gradient using my custom UIImage extension
extension UIImage {
static func gradientImageWithBounds(bounds: CGRect, colors: [CGColor]) -> UIImage {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = colors
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
gradientLayer.masksToBounds = true
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
tabBar.barTintColor = UIColor(patternImage: UIImage.gradientImageWithBounds(bounds: tabBar.bounds, colors: [Colors.tabBarTopGradient, Colors.tabBarBottomGradient]))
I having trouble with gradient not getting properly applied to the bottom part of the tab bar around the phone's safe area. What am I missing here? It looks like that:
Please try setting the gradient tint in viewWillLayoutSubviews(). Hope that helps!
I think you create the gradient based on tab bar height, what you need is to add the additional height to the tab bar height
override func viewDidLoad() {
super.viewDidLoad()
var frame = tabBar.bounds
let safeAreaHeight = safeAreaInsets.bottom
frame.size.height = frame.size.height + safeAreaHeight
tabBar.barTintColor = UIColor(patternImage: UIImage.gradientImageWithBounds(bounds: frame, colors: [UIColor.red.cgColor, UIColor.blue.cgColor]))
}
public var safeAreaInsets: UIEdgeInsets {
guard let window: UIWindow = UIApplication.shared.windows.first else {
return .zero
}
if #available(iOS 11.0, *),
UIWindow.instancesRespond(to: #selector(getter: window.safeAreaInsets)) {
return window.safeAreaInsets
}
return .zero
}

How do you make a navigation bar colored and translucent (iOS)?

How can I make the navigation bar be both translucent while also having a tint to it, as illustrated in the image below:
I also want it to keep the default blur effect of a translucent nav bar (so I don't want it to look exactly like the picture, cause I want the blur effect too.)
I feel like this should be pretty easy but I've spent an hour looking for a solution and nothing works the way I want it to.
Also, I would prefer an Interface Builder solution, but if there isn't one than swift is fine too.
The colour changing part comes from here. I just added the blur part from here. I do not know if it is the best solution for blur, but it is working. You will need to subclass your navigation bar, but nothing painful. Found it better if blur view had slightly dropped alpha, you will have to play with this a little.
extension UIColor {
func toImage() -> UIImage? {
return toImageWithSize(size: CGSize(width: 1, height: 1))
}
func toImageWithSize(size: CGSize) -> UIImage? {
UIGraphicsBeginImageContext(size)
if let ctx = UIGraphicsGetCurrentContext() {
let rectangle = CGRect(x: 0, y: 0, width: size.width, height: size.height)
ctx.setFillColor(self.cgColor)
ctx.addRect(rectangle)
ctx.drawPath(using: .fill)
let colorImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return colorImage
} else {
return nil
}
}
}
extension UIImage {
func imageWithAlpha(alpha: CGFloat) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, scale)
draw(at: CGPoint.zero, blendMode: .normal, alpha: alpha)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
class CustomNavBar: UINavigationBar {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setBackgroundImage(UIColor.blue.toImage()?.imageWithAlpha(alpha: 0.5), for: .default)
addBlurEffect()
}
func addBlurEffect() {
let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
var frame = bounds
frame.origin.y -= 20
frame.size.height += 20
visualEffectView.frame = frame
visualEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
visualEffectView.alpha = 0.9
insertSubview(visualEffectView, at: 0)
sendSubview(toBack: visualEffectView)
}
}
Just set the colour of the Navigation Bar and then the transparency.
navigationController?.navigationBar.barTintColor = UIColor.green
navigationController?.navigationBar.alpha = 0.5
That should do it.
Just select "Navigation Bar" of UINavigationController (not NavigationItem of other ViewControllers) in the interface builder and change "barTintColor"

ImageView Gradient behaving abnormally

I have gradient(clear - black) applied to an imageview and on top of it I have a float button. Everything works fine. The only issue is, whenever there is a tap on float button, the gradient start increasing to black more and more. my gradient is clear to black from top to bottom. But on interaction, It start to slowely blacken towards upside.
I am really unable to solve this error.
This image view is in a UIcollectionResuableView. Below is the code for yhe following.
func addBlackGradientLayerprof(frame: CGRect){
let gradient = CAGradientLayer()
gradient.frame = frame
let black = UIColor.init(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.65).cgColor
gradient.colors = [UIColor.clear.cgColor, black]
gradient.locations = [0.0, 1.0]
self.layer.addSublayer(gradient)
}
Header View with floating button:
override func layoutSubviews() {
super.layoutSubviews()
profilePic.addBlackGradientLayerprof(frame: profilePic.bounds)
}
override func awakeFromNib() {
super.awakeFromNib()
layoutFAB()
}
func layoutFAB() {
floaty.openAnimationType = .slideDown
floaty.addItem("New Post", icon: UIImage(named: "photo-camera")) { item in
self.delegate.fabUploadClicked()
}
floaty.addItem("Settings", icon: UIImage(named: "settingsB")) { item in
self.delegate.fabSettingsClicked()
}
floaty.paddingY = (frame.height - 30) - floaty.frame.height/2
floaty.fabDelegate = self
floaty.buttonColor = UIColor.white
floaty.hasShadow = true
floaty.size = 45
addSubview(floaty)
}
layoutSubviews is bad place to do anything other than adjust a few frames as needed. It can be called many times in the lifecycle of a view. You should not be adding layers from layoutSubviews.
You should call addBlackGradientLayerprof from a place guaranteed to only be called once in the lifetime of the object. awakeFromNib would be one possible place.

Resources