CAGradientLayer different between iOS 12 and iOS 13 - ios

I use this code to generate an UIImage from a gradient.
func buildGradientImage() -> UIImage? {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRect(x: 0, y: 0, width: 375, height: 667)
gradientLayer.type = .radial
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.2, y: 1.0)
gradientLayer.colors = [UIColor.red.cgColor, UIColor.green.cgColor]
var gradientImage:UIImage?
UIGraphicsBeginImageContext(gradientLayer.frame.size)
if let context = UIGraphicsGetCurrentContext() {
gradientLayer.render(in: context)
gradientImage = UIGraphicsGetImageFromCurrentImageContext()
}
UIGraphicsEndImageContext()
return gradientImage
}
For some reason the resulting image is different between iOS 12 and iOS 13-14.
Red seems to expand more on the iOS 12 gradient.
Someone know why there is this difference? Shouldn't the code generate the same gradient?

Related

Swift NavigationBar gradient different than on view below

I have gradient on naviBar and View below but colours are displayed different. Somebody know why? I don't have extra tints on view.
code for naviBar:
func addGradientImageView(withColours: [UIColor] = [.amaranth, .dodgerBlue]) {
let cgColours = withColours.map { $0.cgColor }
let gradientLayer = CAGradientLayer()
var updatedFrame = bounds
updatedFrame.size.height += UIApplication.shared.statusBarFrame.height
gradientLayer.frame = updatedFrame
gradientLayer.colors = cgColours
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
setBackgroundImage(image, for: UIBarMetrics.default)
}
code for view:
func applyHorizontalGradient(withColours: [UIColor] = [.amaranth, .dodgerBlue]) {
let cgColours = withColours.map { $0.cgColor }
let grad = CAGradientLayer()
grad.startPoint = CGPoint(x: 0, y: 0)
grad.endPoint = CGPoint(x: 1, y: 0)
grad.frame = self.bounds
grad.colors = cgColours
self.layer.insertSublayer(grad, at: 0)
}
extension UIView {
func applyGradientImage(withColours: [UIColor] = [.amaranth, .dodgerBlue],
startPoint: CGPoint = CGPoint(x: 0, y: 0),
endPoint: CGPoint = CGPoint(x: 1, y: 0)) {
let imageView = ImageView()
insertSubview(imageView, at: 0)
imageView.expandToSuperviewSafearea(withoutBottomSafeArea: false)
imageView.image = gradientImage(withColours: withColours, startPoint: startPoint, endPoint: endPoint)
}
}

How to create a gradient which goes from full color to opacity of 0 in iOS?

I have looked here, but based on their example, which I tried and show below, did not work. It was not able to accomplish the following. I am looking to create a gradient from full black to a full black with opacity of 0:
#IBDesignable
final class GradientView: UIView {
#IBInspectable var startColor: UIColor = UIColor.clear
#IBInspectable var endColor: UIColor = UIColor.clear
override func draw(_ rect: CGRect) {
let gradient: CAGradientLayer = CAGradientLayer()
// gradient.frame = bounds
gradient.frame = CGRect(x: CGFloat(0),
y: CGFloat(0),
width: superview!.frame.size.width,
height: superview!.frame.size.height)
gradient.colors = [startColor.cgColor, endColor.cgColor]
gradient.zPosition = -1
layer.addSublayer(gradient)
}
}
How can I achieve this, preferably in the interface builder?
Following is my implementation
#IBDesignable
final class GradientView: UIView {
override func draw(_ rect: CGRect) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()!
//// Gradient Declarations
let gradient = CGGradient(colorsSpace: nil, colors: [UIColor.white.cgColor,
UIColor.white.blended(withFraction: 0.5, of: UIColor.black).cgColor,
UIColor.black.cgColor] as CFArray, locations: [0, 0.51, 0.89])!
//// Rectangle Drawing
let rectangleRect = CGRect(x: frame.minX + 11, y: frame.minY + 8, width: 85, height: 54)
let rectanglePath = UIBezierPath(rect: rectangleRect)
context.saveGState()
rectanglePath.addClip()
context.drawLinearGradient(gradient,
start: CGPoint(x: rectangleRect.midX, y: rectangleRect.minY),
end: CGPoint(x: rectangleRect.midX, y: rectangleRect.maxY),
options: [])
context.restoreGState()
}
}
What I ended up doing is the following:
private func setupGradient() {
//Below for container
gradientViewContainer.frame = CGRect(x: 0, y: 152, width: 117, height: 38)
gradientViewContainer.isHidden = true
//Below for actual gradient
gradientLayer.frame = CGRect(x: 0, y: 152, width: gradientViewContainer.frame.width, height: gradientViewContainer.frame.height)
gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
gradientLayer.colors = [UIColor.black.withAlphaComponent(0.0).cgColor, UIColor.black.withAlphaComponent(0.7).cgColor, UIColor.black.withAlphaComponent(0.7).cgColor, UIColor.black.withAlphaComponent(1.0).cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientViewContainer.layer.insertSublayer(gradientLayer, at: 0)
gradientViewContainer.alpha = 0.65
}
Then in viewDidLoad:
setupGradient()
myView.layer.addSublayer(gradientLayer)

How can i add 3 background colour in single UIView??

I Need to add three colour in single background colour
Without using 3 UIView or image.
Create custom UIView and override the draw(_:) function. Then use the current CGContext and draw according to your preferred size. Example based on the alignment from the given image is shown below:
class CustomView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else {
return
}
let firstRect = CGRect(origin: CGPoint(x: rect.origin.x, y: rect.origin.y), size: CGSize(width: rect.size.width / 3, height: rect.size.height))
let middleRect = CGRect(origin: CGPoint(x: firstRect.maxX, y: rect.origin.y), size: CGSize(width: rect.size.width / 3, height: rect.size.height))
let lastRect = CGRect(origin: CGPoint(x: middleRect.maxX, y: rect.origin.y), size: CGSize(width: rect.size.width / 3, height: rect.size.height))
let arrayTuple: [(rect: CGRect, color: CGColor)] = [(firstRect, UIColor.red.cgColor), (middleRect, UIColor.green.cgColor), (lastRect, UIColor.blue.cgColor)]
for tuple in arrayTuple {
context.setFillColor(tuple.color)
context.fill(tuple.rect)
}
}
}
Use this below func
func addSublayers (_ viewCustom : UIView){
let layer1 = CAShapeLayer()
let layer2 = CAShapeLayer()
let layer3 = CAShapeLayer()
layer1.frame = CGRect(origin: viewCustom.bounds.origin,
size: CGSize(width: viewCustom.frame.size.width/3,
height: viewCustom.frame.size.height))
layer2.frame = CGRect(x: layer1.frame.size.width,
y: layer1.frame.origin.y,
width: viewCustom.frame.size.width/3,
height: viewCustom.frame.size.height)
layer3.frame = CGRect(x: layer2.frame.size.width + layer2.frame.origin.x,
y: layer2.frame.origin.y,
width: viewCustom.frame.size.width/3,
height: viewCustom.frame.size.height)
layer1.backgroundColor = UIColor.red.cgColor
layer2.backgroundColor = UIColor.green.cgColor
layer3.backgroundColor = UIColor.blue.cgColor
viewCustom.layer.addSublayer(layer1)
viewCustom.layer.addSublayer(layer2)
viewCustom.layer.addSublayer(layer3)
}
Output:
extension UIView {
func addMultipleColorsHorizontal(colors: [UIColor]) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
var colorsArray: [CGColor] = []
var locationsArray: [NSNumber] = []
for (index, color) in colors.enumerated() {
colorsArray.append(color.cgColor)
colorsArray.append(color.cgColor)
locationsArray.append(NSNumber(value: (1.0 / Double(colors.count)) * Double(index)))
locationsArray.append(NSNumber(value: (1.0 / Double(colors.count)) * Double(index + 1)))
}
gradientLayer.colors = colorsArray
gradientLayer.locations = locationsArray
gradientLayer.startPoint = CGPoint(x: 0, y: 1)
gradientLayer.endPoint = CGPoint(x: 1, y: 1)
self.backgroundColor = .clear
self.layer.addSublayer(gradientLayer)
}
}

Fill Path on Intersecting UIBezierPath

Any idea on how to fill all the paths in here. What is currently happening is that I draw a rectangle path on my view then add small circles in between but it seems that if the circle and rectangle intersects, the white fill color is showing. What I would want is show still the gradient layer. Any help? My current code is below.
func addGradientLayer() {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
let startColor = UIColor.create(withHexOrName: OurPayStatesViewUX.GradientColorStart)
let endColor = UIColor.create(withHexOrName: OurPayStatesViewUX.GradientColorEnd)
gradientLayer.colors = [startColor.CGColor, endColor.CGColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 0)
layer.insertSublayer(gradientLayer, atIndex: 0)
}
func createOverlay(view: UIView, circleLocations: [CGPoint]) {
maskLayer?.removeFromSuperlayer()
let radius: CGFloat = view.frame.height/2
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: view.bounds.size.width, height: view.bounds.size.height), cornerRadius: 0)
for i in circleLocations {
// Create a circle path in each of the state views
let circlePath = UIBezierPath(roundedRect: CGRect(x: i.x, y: i.y, width: 2 * radius, height: 2 * radius), cornerRadius: radius)
path.appendPath(circlePath)
}
let rect = createRectangle(startPointX: 0, endPointX: view.bounds.size.width)
path.appendPath(rect)
path.usesEvenOddFillRule = true
let fillLayer = CAShapeLayer()
fillLayer.path = path.CGPath
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = backgroundColor?.CGColor ?? UIColor.whiteColor().CGColor
fillLayer.opacity = 1
maskLayer = fillLayer
layer.addSublayer(fillLayer)
}
func createRectangle(startPointX startPointX: CGFloat, endPointX: CGFloat) -> UIBezierPath {
let rectHeight: CGFloat = 6
let path = UIBezierPath(rect: CGRect(x: startPointX, y: frame.height/2 - rectHeight/2, width: endPointX - startPointX, height: rectHeight))
return path
}

Gradient over UIImageView

I've been trying to fill an image view with a gradient layer, but I can't seem to make it work, I tried to create CAGradientLayer, applied 2 colors on it then applied it as a mask to the main image layer.
let gradient = CAGradientLayer()
gradient.frame = (self.imageView?.bounds)!
gradient.colors = [UIColor.blue.cgColor, UIColor.purple.cgColor]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 0.5, y: 1.0)
gradient.locations = [0, 1]
self.imageView?.layer.mask = gradient
This is what I have, and this is what I want as a result:
UPDATE:
Found the solution here.
You can do something like this:
var gradient = CAGradientLayer()
gradient.frame = imageView.bounds
gradient.colors = [UIColor.blue.cgColor, UIColor.purple.cgColor]
imageView.layer.insertSublayer(gradient, at: 0)
I got this answer. Add extension of UIImage and just pass colours list to apply gradient.
import UIKit
extension UIImage {
func tintedWithLinearGradientColors(colorsArr: [CGColor]) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale);
guard let context = UIGraphicsGetCurrentContext() else {
return UIImage()
}
context.translateBy(x: 0, y: self.size.height)
context.scaleBy(x: 1, y: -1)
context.setBlendMode(.normal)
let rect = CGRect.init(x: 0, y: 0, width: size.width, height: size.height)
// Create gradient
let colors = colorsArr as CFArray
let space = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: space, colors: colors, locations: nil)
// Apply gradient
context.clip(to: rect, mask: self.cgImage!)
context.drawLinearGradient(gradient!, start: CGPoint(x: 0, y: 0), end: CGPoint(x: 0, y: self.size.height), options: .drawsAfterEndLocation)
let gradientImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return gradientImage!
}
}

Resources