How to implement a black gradient on an image in iOS using swift - ios

I am working on an iOS project where I have white labels on bright images. The problem is for bright images the white labels are not showing. Here is an example:
Label not showing: https://imgur.com/hKtejHn
Label showing: https://imgur.com/Ef5qJAh
I think if I add a black gradient on all the image then the white labels will be visible. Can anyone help me as to how to implement the solution in Swift?
Thank!

If you want to add gradient on your imageView then you can just implement CAGradientLayer on your imageView.layer.
Try to change some values for your own custom look, but the code below is pretty much it.
let gradientLayer = CAGradientLayer()
gradientLayer.frame = imageView.frame
let colors = [
UIColor(red: 0, green: 0, blue: 0, alpha: 1).cgColor,
UIColor(red: 0, green: 0, blue: 0, alpha: 0).cgColor
]
gradientLayer.startPoint = CGPoint(x: 0.1, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.9, y: 0.5)
gradientLayer.colors = colors
imageView.layer.addSublayer(gradientLayer)
You can change colors, add colors, change start/end-points. You can find a lot of different CAGradientLayer-guides on youtube or google.

try this:
extension UILabel {
func lblShadow(color: UIColor , radius: CGFloat, opacity: Float){
self.textColor = color
self.layer.masksToBounds = false
self.layer.shadowRadius = radius
self.layer.shadowOpacity = opacity
self.layer.shadowOffset = CGSize(width: 1, height: 1)
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
}
}
usage:
label.lblShadow(color: UIColor.white, radius: 3, opacity: 0.75)

Related

Why is CILinearGradient resulting in a very NON-linear gradient?

I'm a relatively new Swift developer and I am using the CILinearGradient CIFilter to generate gradients that I can then use as backgrounds and textures. I was pretty happy with the way it was working, until I realized that the gradients coming out of it seem to be heavily skewed towards away from the black end of the spectrum.
At first I thought I was nuts, but then I created pure black-to-white and white-to-black gradients and put them on screen next to each other. I took a screenshot and brought it into Photoshop. then I looked at the color values. You can see that the ends of each gradient line up (pure black over pure white on one end, and the opposite on the other), but the halfway point of each gradient is significantly skewed towards the black end.
Is this an issue with the CIFilter or am I doing something wrong? Thanks to anyone with any insight on this!
Here's my code:
func gradient2colorIMG(UIcolor1: UIColor, UIcolor2: UIColor, width: CGFloat, height: CGFloat) -> CGImage? {
if let gradientFilter = CIFilter(name: "CILinearGradient") {
let startVector:CIVector = CIVector(x: 0 + 10, y: 0)
let endVector:CIVector = CIVector(x: width - 10, y: 0)
let color1 = CIColor(color: UIcolor1)
let color2 = CIColor(color: UIcolor2)
let context = CIContext(options: nil)
if let currentFilter = CIFilter(name: "CILinearGradient") {
currentFilter.setValue(startVector, forKey: "inputPoint0")
currentFilter.setValue(endVector, forKey: "inputPoint1")
currentFilter.setValue(color1, forKey: "inputColor0")
currentFilter.setValue(color2, forKey: "inputColor1")
if let output = currentFilter.outputImage {
if let cgimg = context.createCGImage(output, from: CGRect(x: 0, y: 0, width: width, height: height)) {
let gradImage = cgimg
return gradImage
}
}
}
}
return nil
}
and then I call it in SpriteKit using this code (but this is just so I can see them on the screen to compare the CGImages that are output by the function) ...
if let gradImage = gradient2colorIMG(UIcolor1: UIColor(red: 255.0 / 255.0, green: 255.0 / 255.0, blue: 255.0 / 255.0, alpha: 1.0), UIcolor2: UIColor(red: 0.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0), width: 250, height: 80) {
let sampleback = SKShapeNode(path: CGPath(roundedRect: CGRect(x: 0, y: 0, width: 250, height: 80), cornerWidth: 5, cornerHeight: 5, transform: nil))
sampleback.fillColor = .white
sampleback.fillTexture = SKTexture(cgImage: gradImage)
sampleback.zPosition = 200
sampleback.position = CGPoint(x: 150, y: 50)
self.addChild(sampleback)
}
if let gradImage2 = gradient2colorIMG(UIcolor1: UIColor(red: 0.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0), UIcolor2: UIColor(red: 255.0 / 255.0, green: 255.0 / 255.0, blue: 255.0 / 255.0, alpha: 1.0), width: 250, height: 80) {
let sampleback2 = SKShapeNode(path: CGPath(roundedRect: CGRect(x: 0, y: 0, width: 250, height: 80), cornerWidth: 5, cornerHeight: 5, transform: nil))
sampleback2.fillColor = .white
sampleback2.fillTexture = SKTexture(cgImage: gradImage2)
sampleback2.zPosition = 200
sampleback2.position = CGPoint(x: 150, y: 150)
self.addChild(sampleback2)
}
As another follow-up, I tried doing a red-blue gradient (so purely a change in hue) and it is perfectly linear (see below). The issue seems to be around the brightness.
A red-blue gradient DOES ramp its hue in a perfectly linear fashion
Imagine that black is 0 and white is 1. Then the problem here is that we intuitively think that 50% of black "is" a grayscale value of 0.5 — and that is not true.
To see this, consider the following core image experiment:
let con = CIContext(options: nil)
let white = CIFilter(name:"CIConstantColorGenerator")!
white.setValue(CIColor(color:.white), forKey:"inputColor")
let black = CIFilter(name:"CIConstantColorGenerator")!
black.setValue(CIColor(color:UIColor.black.withAlphaComponent(0.5)),
forKey:"inputColor")
let atop = CIFilter(name:"CISourceAtopCompositing")!
atop.setValue(white.outputImage!, forKey:"inputBackgroundImage")
atop.setValue(black.outputImage!, forKey:"inputImage")
let cgim = con.createCGImage(atop.outputImage!,
from: CGRect(x: 0, y: 0, width: 201, height: 50))!
let image = UIImage(cgImage: cgim)
let iv = UIImageView(image:image)
self.view.addSubview(iv)
iv.frame.origin = CGPoint(x: 100, y: 150)
What I've done here is to lay a 50% transparency black swatch on top of a white swatch. We intuitively imagine that the result will be a swatch that will read as 0.5. But it isn't; it's 0.737, the very same shade that is appearing at the midpoint of your gradients:
The reason is that everything here is happening, not in some mathematical vacuum, but in a color space adjusted for a specific gamma.
Now, you may justly ask: "But where did I specify this color space? This is not what I want!" Aha. You specified it in the first line, when you created a CIContext without overriding the default working color space.
Let's fix that. Change the first line to this:
let con = CIContext(options: [.workingColorSpace : NSNull()])
Now the output is this:
Presto, that's your 0.5 gray!
So what I'm saying is, if you create your CIContext like that, you will get the gradient you are after, with 0.5 gray at the midpoint. I'm not saying that that is any more "right" than the result you are getting, but at least it shows how to get that particular result with the code you already have.
(In fact, I think what you were getting originally is more "right", as it is adjusted for human perception.)
The midpoint of the CILinearGradient appears to correspond to 188, 188, 188, which looks like the “absolute whiteness” rendition of middle gray, which is not entirely unreasonable. (The CISmoothLinearGradient offers a smoother transition, but it doesn’t have the midpoint at 0.5, 0.5, 0.5, either.) As an aside, the “linear” in CILinearGradient and CISmoothLinearGradient refer to the shape of the gradient (to differentiate it from a “radial” gradient), not the nature of the color transitions within the gradient.
However if you want a gradient whose midpoint is 0.5, 0.5, 0.5, you can use CGGradient:
func simpleGradient(in rect: CGRect) -> UIImage {
return UIGraphicsImageRenderer(bounds: rect).image { context in
let colors = [UIColor.white.cgColor, UIColor.black.cgColor]
let colorSpace = CGColorSpaceCreateDeviceGray() // or RGB works, too
guard let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: nil) else { return }
context.cgContext.drawLinearGradient(gradient, start: .zero, end: CGPoint(x: rect.maxX, y: 0), options: [])
}
}
Alternatively, if you want a gradient background, you might define a UIView subclass that uses a CAGradientLayer as its backing layer:
class GradientView: UIView {
override class var layerClass: AnyClass { return CAGradientLayer.self }
var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
func configure() {
gradientLayer.colors = [UIColor.white.cgColor, UIColor.black.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
}
}

Apply CAGradientLayer to layer.borderColor

I'm trying to implement a underlined UITextField with a gradient. Therefore I created a extension with a function underlined().
To get a gradient, I created a CAGradientLayer and made these customizations:
func underlined(){
let color = UIColor(red: 11/255, green: 95/255, blue: 244/255, alpha: 1).cgColor
let sndColor = UIColor(red: 106/255, green: 178/255, blue: 255/255, alpha: 1).cgColor
let gradient: CAGradientLayer = CAGradientLayer()
gradient.colors = [color, sndColor]
gradient.locations = [0.0, 1.0]
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 1, y: 0)
let width = CGFloat(2.0)
gradient.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height: self.frame.size.height)
gradient.borderWidth = width
self.layer.insertSublayer(gradient, at: 0)
self.layer.masksToBounds = true
}
A underline is being displayed, but it solely solid black - I've tried to change the colors, but it remains black (Issue outdated - see edit).
Does anybody see the issue?
Edit:
Adding gradient.borderColor = UIColor.blue.cgColor let me change the color of the line - but how can I apply the gradient on the border color?
Remove the below line from your code:
gradient.borderWidth = width
Screenshot:
Your code was not working because, the borderWidth is covering whole of the gradient frame.Try setting the borderColor, then you'll see the difference.
gradient.borderColor = UIColor.red.cgColor
Let me know if you still face any issues.

How to add gradients on custom class buttons?

I am beginner in Swift/Xcode. I made a custom class in my Xcode project to handle buttons appearance (color, shape etc) that works find.
However, I can't succeed to add gradients to these buttons.
I tried the below code, but it adds a new squared gradient onto my rounded buttons instead of applying the intended gradients to those buttons. Here is what I already gave many tries:
- add this code to my custom button class (inheriting from UIButton)
- add this code to my custom button class (inheriting from UIView)
- above 2 approaches and removing the backgroungColor settings
- Using a separate function called from viewController
func ConfigMenuButtons() {
// Set button shape color
layer.cornerRadius = 37
// Set button size
translatesAutoresizingMaskIntoConstraints = false
widthAnchor.constraint(equalToConstant: 74).isActive = true
heightAnchor.constraint(equalToConstant: 74).isActive = true
// Set button text
setTitleColor(.white, for: .normal)
titleLabel?.font = UIFont.boldSystemFont(ofSize: 15)
// Set button shadow
layer.shadowRadius = 2
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 2)
layer.shadowOpacity = 0.8
// Set button gradient colors
let lightBlue = UIColor(red: 122/255, green: 127/255, blue: 249/255, alpha: 1)
let darkBlue = UIColor(red: 74/255, green: 88/255, blue: 205/255, alpha: 1)
// Set gradients
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [lightBlue.cgColor, darkBlue.cgColor]
gradientLayer.locations = [0.0,1.0]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
clipsToBounds = true
// insert gradients to button
layer.insertSublayer(gradientLayer, at: 0)
}
But I always end up with this "square shaped gradient layer" appearing onto the targeted buttons, instead of having these gradients applied directly to these buttons.
If someone had any piece of advice, it would be fantastic.
Thank you!
Here is the screen shot of the button with its partial expected effect
Here is a screenshot of fixed issue
I finally fixed the issue as below adding a CGRect size.
func ConfigMenuButtons() {
// Set button shape
layer.cornerRadius = 37
// Set button size
translatesAutoresizingMaskIntoConstraints = false
widthAnchor.constraint(equalToConstant: 74).isActive = true
heightAnchor.constraint(equalToConstant: 74).isActive = true
// Set button text
setTitleColor(.white, for: .normal)
titleLabel?.font = UIFont.boldSystemFont(ofSize: 15)
// Set button shadow
layer.shadowRadius = 2
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 2)
layer.shadowOpacity = 0.8
// Set button gradient colors
let lightBlue = UIColor(red: 112/255, green: 117/255, blue: 239/255, alpha: 1)
let darkBlue = UIColor(red: 70/255, green: 84/255, blue: 201/255, alpha: 1)
// Set gradients
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [lightBlue.cgColor, darkBlue.cgColor]
gradientLayer.locations = [0.0,1.0]
gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
// THAT IS THE LINE THAT COULD FIX THE ISSUE
gradientLayer.frame = CGRect(x: 0, y: 0, width: 74, height: 74)
// Set rounded shape
gradientLayer.cornerRadius = 37
// insert gradients to button
layer.insertSublayer(gradientLayer, at: 0)
}

UIVisualEffectView with CAGradientLayer not working on iOS 10

I'm trying to create an effect whereby a photo is blurry at the top, but not at the bottom, and the blurriness 'fades off' gradually. I achieved this with the code below, which worked fine in iOS9, but does not in iOS10.
I'm aware of a known bug, as described in this question, that prevents a layer having a mask and a blur on the same layer.
The difference between my question and the one linked, is I'm not interested in using a CAShapeLayer as my mask, but rather a CAGradientLayer. I've tried fiddling with adding views/masks/layers in different orders, but am not having much luck.
var visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
visualEffectView.frame = CGRect(x: 0.0, y:0.0, width: photo.bounds.width, height: photo.bounds.height)
photo.addSubview(visualEffectView)
let maskStartColour = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
let maskEndColour = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
let gradient = CAGradientLayer()
gradient.frame = CGRect(x: 0, y: 0, width: visualEffectView.bounds.width, height: visualEffectView.bounds.height)
let colors: [AnyObject] = [maskStartColour.cgColor, maskEndColour.cgColor]
gradient.colors = colors
gradient.startPoint = CGPoint(x: 0.0, y: 0.0)
gradient.endPoint = CGPoint(x: 0.0, y: 1.0)
visualEffectView.layer.mask = gradient

Swift: Adding gradient layer to button. Layer length error

let gradient: CAGradientLayer = CAGradientLayer()
let colorTop = UIColor(red: 112.0/255.0, green: 219.0/255.0, blue: 155.0/255.0, alpha: 1.0).CGColor
let colorBottom = UIColor(red: 86.0/255.0, green: 197.0/255.0, blue: 238.0/255.0, alpha: 1.0).CGColor
gradient.colors = [colorTop, colorBottom]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
gradient.frame = loginButton.bounds
gradient.cornerRadius = 5
loginButton.layer.addSublayer(gradient)
The resulting gradient runs off goes beyond the button's frame. Why does this happen?
Probably you are setting gradient layer in viewDidLoad() or viewWillAppear(). At that time controller did not calculated views sizes. At time you add this sublayer button size was not calculated yet, so sublayer size is wrong. So you should fix next things:
First of all you should add gradient at viewDidAppear(), at this point all view's sizes are calculated.
Second, you should use layer.insertSublayer(layer, atIndex:index) instead of addSublayer(layer). Because in your case sublayer will hide buttons native layer (title, background...)
You should recalculate your sublayer size in viewDidLayoutSubviews(). Because when your button will change it's size (while rotating for example), sublayer will not change it's frame, so you should change it by yourself.
Add clipsToBounds and cornerRadius to loginbutton. That should fix the problem.
loginButton.clipsToBounds = true
loginButton.layer.cornerRadius = 5;
Is your Login Button's frame correct?It seems correct when I reproduce
let loginButton = UIButton(frame: CGRect(x: 10, y: 50, width: 300, height: 30))
self.view.addSubview(loginButton)
let gradient:CAGradientLayer = CAGradientLayer()
let colorTop = UIColor(red: 112.0/255.0, green: 219.0/255.0, blue: 155.0/255.0, alpha: 1.0).CGColor
let colorBottom = UIColor(red: 86.0/255.0, green: 197.0/255.0, blue: 238.0/255.0, alpha: 1.0).CGColor
gradient.colors = [colorTop, colorBottom]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
gradient.frame = loginButton.bounds
gradient.cornerRadius = 5
loginButton.layer.addSublayer(gradient)
And it appear like below

Resources