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
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)
}
}
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)
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)
}
}
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
}
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!
}
}