NSGradient drawInBezierPath iOS equivalent - ios

Is there an iOS equivalent to - (void)drawInBezierPath:(NSBezierPath *)path angle:(CGFloat)angle?
I need to draw a gradient inside a UIBezierPath and cannot get it to work. The gradient draws all over the screen.

This example creates a gradient inside a UIBezierPath in Swift 3. It draws a slanting line with a green/white gradient.
import UIKit
class DrawingView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.isOpaque = false
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
let startPoint = CGPoint(x:100, y:100)
let endPoint = CGPoint(x: 300, y:400)
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0);
// create a line
context.move(to: startPoint)
context.addLine(to: endPoint)
context.setLineWidth(4)
// use the line created above as a clipping mask
context.replacePathWithStrokedPath()
context.clip()
// create a gradient
let locations: [CGFloat] = [ 0.0, 0.5 ]
let colors = [UIColor.green.cgColor,
UIColor.white.cgColor]
let colorspace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorspace,
colors: colors as CFArray, locations: locations)
let gradientStartPoint = CGPoint(x: rect.midX, y: rect.minY)
let gradientEndPoint = CGPoint(x: rect.midX, y: rect.maxY)
context.drawLinearGradient(gradient!,
start: gradientStartPoint, end: gradientEndPoint,
options: .drawsBeforeStartLocation)
UIGraphicsEndImageContext()
}
}

Related

How can i add Gradient colour for button tint in swift iOS

is there any solution to apply tint colour as a gradient in swift
my button has a simple white icon I want to change it to a gradient colour
I updated my answer according to Duncan C comment.
You can modify your image before setting it to button.
extension UIImage {
func drawLinearGradient(colors: [CGColor], startingPoint: CGPoint, endPoint: CGPoint) -> UIImage? {
let renderer = UIGraphicsImageRenderer(size: self.size)
var shouldReturnNil = false
let gradientImage = renderer.image { context in
context.cgContext.translateBy(x: 0, y: self.size.height)
context.cgContext.scaleBy(x: 1.0, y: -1.0)
context.cgContext.setBlendMode(.normal)
let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
// Create gradient
let colors = colors as CFArray
let colorsSpace = CGColorSpaceCreateDeviceRGB()
guard let gradient = CGGradient(colorsSpace: colorsSpace, colors: colors, locations: nil) else {
shouldReturnNil = true
return
}
// Apply gradient
guard let cgImage = self.cgImage else {
shouldReturnNil = true
print("Couldn't get cgImage of UIImage.")
return
}
context.cgContext.clip(to: rect, mask: cgImage)
context.cgContext.drawLinearGradient(
gradient,
start: endPoint,
end: startingPoint,
options: .init(rawValue: 0)
)
}
return shouldReturnNil ? nil : gradientImage
}
}
You can use it like that:
guard let image = UIImage(named: "<your_image_name>") else { return }
v.image = image.drawLinearGradient(
colors: [UIColor.blue.cgColor, UIColor.red.cgColor],
startingPoint: .init(x: image.size.width / 2, y: 0),
endPoint: .init(x: image.size.width / 2, y: image.size.height)
)
button.setImage(gradientImage, for: .normal)
This code produces result like this:
I have the same problem too
but for now
if you wanna change the text color it works.
like you convert image into pattern color by this code
/*
class gradiunTitle : UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
let gradient = getGradientLayer(bounds: self.bounds)
self.setTitleColor(gradientColor(bounds: self.bounds, gradientLayer: gradient), for: .normal)
self.tintColor = gradientColor(bounds: self.frame, gradientLayer: gradient)
}
func getGradientLayer(bounds : CGRect) -> CAGradientLayer{
let gradient = CAGradientLayer()
gradient.frame = bounds
//order of gradient colors
gradient.colors = [UIColor.red.cgColor,UIColor.blue.cgColor, UIColor.green.cgColor]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
return gradient
}
func gradientColor(bounds: CGRect, gradientLayer :CAGradientLayer) -> UIColor? {
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIColor(patternImage: image!)
}
}
*/

How to fix inconsistent color-to-clear radial gradient layer

When trying to draw a radial gradient from white to clear, I obtain different results depending on whether I use UIColor(white:alpha:), UIColor.white.withAlpha.Components(alpha:) or UIColor.color. How Can I get the same gradient with a clear color as with a plain color?
I order to draw a radial gradient, I have overridden the draw(context:) method (see below). My code seems to work fine when using plain colours for a gradient but works "mysteriously" when using a clear color.
override func draw(in ctx: CGContext) {
super.draw(in: ctx)
ctx.saveGState()
let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colorFill as CFArray, locations: self.locations as! [CGFloat] )
let startPoint = CGPoint(x: startX, y: startY )
let endPoint = CGPoint(x: endX , y: endY)
let drawingOption : CGGradientDrawingOptions = radius > 0 ? [] : .drawsAfterEndLocation
//self.masksToBounds = true
if gradientType == .radial {
ctx.drawRadialGradient(gradient!, startCenter: startPoint, startRadius: 0.0, endCenter: endPoint, endRadius: self.frame.width / 2, options: drawingOption)
} else {
self.startPoint = startPoint
self.endPoint = endPoint
}
}
Here's the results I obtain depending on the input colours for the gradient:
[UIColor.white.cgColor,UIColor.black.cgColor]
(desired result with clear color instead of black)
[UIColor.white.cgColor, UIColor.clear.cgColor]
[UIColor.white.cgColor, UIColor.white.withAlphaComponent(0.0).cgColor]
[UIColor(white: 0.0, alpha: 1).cgColor, UIColor(white: 1.0, alpha: 0).cgColor]
Could someone can explain why I don't get the same output as with only plain colours (output I wish to obtain)?
Thanks a lot for taking the time to read and reply!
The layer is not drawn only in the draw(in context:) method. This line of code confirms some properties had been set prior to the call of the method, which has priorly triggered the drawing of the layer (self.locations):
CGColorSpaceCreateDeviceRGB(), colors: colorFill as CFArray, locations: self.locations as! [CGFloat] )
By removing the set of self.colors and self.locations properties in the custom initializer and storing the values in Type Properties instead before calling self.setNeedDisplay(), the layer is only being drawn in the draw(in context:) method.
class CustomLayer {
override init() {
super.init()
needsDisplayOnBoundsChange = true
}
override init(layer: Any) {
super.init(layer: layer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var colors = []()
var colorMap = [NSNumber]()
custom init() {
colors = [color,color]
colorMap = [0.0, 1.0]
self.setNeedDisplay()
}
override func draw(in ctx: CGContext) {
super.draw(in: ctx)
ctx.saveGState()
guard let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors as CFArray, locations: colorMap as! [CGFloat] ) else {return}
let startPoint = CGPoint(x: startX, y: startY )
let endPoint = CGPoint(x: endX , y: endY)
self.masksToBounds = true
if gradientType == .radial {
ctx.drawRadialGradient(gradient, startCenter: startPoint, startRadius: 0.0, endCenter: endPoint, endRadius: self.frame.width / 2, options: drawingOption)
} else {
self.locations = colorMap
self.colors = colors
self.startPoint = startPoint
self.endPoint = endPoint
}
}
}
Cheers!

How to make the background color property of a custom view animatable?

I want to animate my custom view's background color.
My drawing code is very simple. The draw(in:) method is overridden like this:
#IBDesignable
class SquareView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
let strokeWidth = self.width / 8
let path = UIBezierPath()
path.move(to: CGPoint(x: self.width - strokeWidth / 2, y: 0))
path.addLine(to: CGPoint(x: self.width - strokeWidth / 2, y: self.height - strokeWidth / 2))
path.addLine(to: CGPoint(x: 0, y: self.height - strokeWidth / 2))
self.backgroundColor?.darker().setStroke()
path.lineWidth = strokeWidth
path.stroke()
}
}
The darker method just returns a darker version of the color it is called on.
When I set the background to blue, it draws something like this:
I want to animate its background color so that it gradually changes to a red color. This would be the end result:
I first tried:
UIView.animate(withDuration: 1) {
self.square.backgroundColor = .red
}
It just changes the color to red in an instant, without animation.
Then I researched and saw that I need to use CALayers. Therefore, I tried drawing the thing in layers:
#IBDesignable
class SquareViewLayers: UIView {
dynamic var squareColor: UIColor = .blue {
didSet {
setNeedsDisplay()
}
}
func setupView() {
layer.backgroundColor = squareColor.cgColor
let sublayer = CAShapeLayer()
let path = UIBezierPath()
let strokeWidth = self.width / 8
path.move(to: CGPoint(x: self.width - strokeWidth / 2, y: 0))
path.addLine(to: CGPoint(x: self.width - strokeWidth / 2, y: self.height - strokeWidth / 2))
path.addLine(to: CGPoint(x: 0, y: self.height - strokeWidth / 2))
self.tintColor.darker().setStroke()
path.lineWidth = strokeWidth
sublayer.path = path.cgPath
sublayer.strokeColor = squareColor.darker().cgColor
sublayer.lineWidth = strokeWidth
sublayer.backgroundColor = UIColor.clear.cgColor
layer.addSublayer(sublayer)
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
}
But the result is horrible
I used this code to try to animate this:
let anim = CABasicAnimation(keyPath: "squareColor")
anim.fromValue = UIColor.blue
anim.toValue = UIColor.red
anim.duration = 1
square.layer.add(anim, forKey: nil)
Nothing happens though.
I am very confused. What is the correct way to do this?
EDIT:
The darker method is from a cocoa pod called SwiftyColor. This is how it is implemented:
// in an extension of UIColor
public func darker(amount: CGFloat = 0.25) -> UIColor {
return hueColor(withBrightnessAmount: 1 - amount)
}
private func hueColor(withBrightnessAmount amount: CGFloat) -> UIColor {
var hue: CGFloat = 0
var saturation: CGFloat = 0
var brightness: CGFloat = 0
var alpha: CGFloat = 0
if getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) {
return UIColor(hue: hue, saturation: saturation,
brightness: brightness * amount,
alpha: alpha)
}
return self
}
You're on the right track with CABasicAnimation.
The keyPath: in let anim = CABasicAnimation(keyPath: "squareColor") should be backgroundColor.
anim.fromValue and anim.toValue require CGColor values (because you're operating on the CALayer, which uses CGColor). You can use UIColor.blue.cgcolor here.
Pretty sure this animation won't persist the color change for you though, so you might need to change that property manually.
Could you stroke the L-shape with a semi-transparent black instead?
Then you don't have to mess around with redrawing and color calculations, and you can use the basic UIView.animate()
Below the "LShadow" is added onto the SquareView, so you can set the SquareView background color as per usual with UIView.animate()
ViewController.swift
import UIKit
class ViewController: UIViewController {
var squareView: SquareView!
override func viewDidLoad() {
super.viewDidLoad()
squareView = SquareView.init(frame: CGRect(x:100, y:100, width:200, height:200))
self.view.addSubview(squareView)
squareView.backgroundColor = UIColor.blue
UIView.animate(withDuration: 2.0, delay: 1.0, animations: {
self.squareView.backgroundColor = UIColor.red
}, completion:nil)
}
}
SquareView.swift
import UIKit
class SquareView: UIView {
func setupView() {
let shadow = LShadow.init(frame: self.bounds)
self.addSubview(shadow)
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
}
class LShadow: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
let strokeWidth = self.frame.size.width / 8
let path = UIBezierPath()
path.move(to: CGPoint(x: self.frame.size.width - strokeWidth / 2, y: 0))
path.addLine(to: CGPoint(x: self.frame.size.width - strokeWidth / 2, y: self.frame.size.height - strokeWidth / 2))
path.addLine(to: CGPoint(x: 0, y: self.frame.size.height - strokeWidth / 2))
UIColor.init(white: 0, alpha: 0.5).setStroke()
path.lineWidth = strokeWidth
path.stroke()
}
}
Using CAKeyframeAnimation you need to animate backgroundColor property:
class SquareView: UIView
{
let sublayer = CAShapeLayer()
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
let frame = self.frame
let strokeWidth = self.frame.width / 8
sublayer.frame = self.bounds
let path = UIBezierPath()
path.move(to: CGPoint(x: frame.width - strokeWidth / 2 , y: 0))
path.addLine(to: CGPoint(x: frame.width - strokeWidth / 2, y: frame.height - strokeWidth / 2))
path.addLine(to: CGPoint(x: 0, y: frame.height - strokeWidth / 2))
path.addLine(to: CGPoint(x: 0 , y: 0))
path.lineWidth = strokeWidth
sublayer.path = path.cgPath
sublayer.fillColor = UIColor.blue.cgColor
sublayer.lineWidth = strokeWidth
sublayer.backgroundColor = UIColor.blue.cgColor
layer.addSublayer(sublayer)
}
//Action
func animateIt()
{
let animateToColor = UIColor(cgColor: sublayer.backgroundColor!).darker().cgColor
animateColorChange(mLayer: self.sublayer, colors: [sublayer.backgroundColor!, animateToColor], duration: 1)
}
func animateColorChange(mLayer:CALayer ,colors:[CGColor], duration:CFTimeInterval)
{
let animation = CAKeyframeAnimation(keyPath: "backgroundColor")
CATransaction.begin()
animation.keyTimes = [0.2, 0.5, 0.7, 1]
animation.values = colors
animation.calculationMode = kCAAnimationPaced
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
animation.repeatCount = 0
animation.duration = duration
mLayer.add(animation, forKey: nil)
CATransaction.commit()
}
}
Note: I have edited the answer so that the shape will always fit the parent view added from storyboard
I had an implementation of changing the background color of view with animation and did some tweak in the source code to add inverted L shape using CAShapeLayer. I achieved the required animation. here is the sample of the view.
class SquareView: UIView, CAAnimationDelegate {
fileprivate func setup() {
self.layer.addSublayer(self.sublayer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup
}
let sublayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
override func layoutSubviews() {
super.layoutSubviews()
let strokeWidth = self.bounds.width / 8
var frame = self.bounds
frame.size.width -= strokeWidth
frame.size.height -= strokeWidth
self.sublayer.path = UIBezierPath(rect: frame).cgPath
}
var color : UIColor = UIColor.clear {
willSet {
self.sublayer.fillColor = newValue.cgColor
self.backgroundColor = newValue.darker()
}
}
func animation(color: UIColor, duration: CFTimeInterval = 1.0) {
let animation = CABasicAnimation()
animation.keyPath = "backgroundColor"
animation.fillMode = kCAFillModeForwards
animation.duration = duration
animation.repeatCount = 1
animation.autoreverses = false
animation.fromValue = self.backgroundColor?.cgColor
animation.toValue = color.darker().cgColor
animation.delegate = self;
let shapeAnimation = CABasicAnimation()
shapeAnimation.keyPath = "fillColor"
shapeAnimation.fillMode = kCAFillModeForwards
shapeAnimation.duration = duration
shapeAnimation.repeatCount = 1
shapeAnimation.autoreverses = false
shapeAnimation.fromValue = self.sublayer.fillColor
shapeAnimation.toValue = color.cgColor
shapeAnimation.delegate = self;
self.layer.add(animation, forKey: "kAnimation")
self.sublayer.add(shapeAnimation, forKey: "kAnimation")
self.color = color
}
public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
self.layer.removeAnimation(forKey: "kAnimation")
self.sublayer.removeAnimation(forKey: "kAnimation")
}
}
You can use the method animation(color: duration:) to change the color with animation on required animation duration.
Example
let view = SquareView(frame: CGRect(x: 10, y: 100, width: 200, height: 300))
view.color = UIColor.blue
view.animation(color: UIColor.red)
Here is the result
We have in the code below, two different CAShapeLayer, foreground and background. Background is a rectangle path drawn to the extent where inverted L actually starts. And L is the path created with simple UIBezierPath geometry. I animate the change to fillColor for each layer after color is set.
class SquareViewLayers: UIView, CAAnimationDelegate {
var color: UIColor = UIColor.blue {
didSet {
animate(layer: backgroundLayer,
from: oldValue,
to: color)
animate(layer: foregroundLayer,
from: oldValue.darker(),
to: color.darker())
}
}
let backgroundLayer = CAShapeLayer()
let foregroundLayer = CAShapeLayer()
func setupView() {
foregroundLayer.frame = frame
layer.addSublayer(foregroundLayer)
backgroundLayer.frame = frame
layer.addSublayer(backgroundLayer)
let strokeWidth = width / 8
let backgroundPathRect = CGRect(origin: .zero, size: CGSize(width: width - strokeWidth,
height: height - strokeWidth))
let backgroundPath = UIBezierPath(rect: backgroundPathRect)
backgroundLayer.fillColor = color.cgColor
backgroundLayer.path = backgroundPath.cgPath
let foregroundPath = UIBezierPath()
foregroundPath.move(to: CGPoint(x: backgroundPathRect.width,
y: 0))
foregroundPath.addLine(to: CGPoint(x: width,
y: 0))
foregroundPath.addLine(to: CGPoint(x: width, y: height))
foregroundPath.addLine(to: CGPoint(x: 0,
y: height))
foregroundPath.addLine(to: CGPoint(x: 0,
y: backgroundPathRect.height))
foregroundPath.addLine(to: CGPoint(x: backgroundPathRect.width,
y: backgroundPathRect.height))
foregroundPath.addLine(to: CGPoint(x: backgroundPathRect.width,
y: 0))
foregroundPath.close()
foregroundLayer.path = foregroundPath.cgPath
foregroundLayer.fillColor = color.darker().cgColor
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
func animate(layer: CAShapeLayer,
from: UIColor,
to: UIColor) {
let fillColorAnimation = CABasicAnimation(keyPath: "fillColor")
fillColorAnimation.fromValue = from.cgColor
layer.fillColor = to.cgColor
fillColorAnimation.duration = 1.0
layer.add(fillColorAnimation,
forKey: nil)
}
}
And here is the result,
Objective-C
view.backgroundColor = [UIColor whiteColor].CGColor;
[UIView animateWithDuration:2.0 animations:^{
view.layer.backgroundColor = [UIColor greenColor].CGColor;
} completion:NULL];
Swift
view.backgroundColor = UIColor.white.cgColor
UIView.animate(withDuration: 2.0) {
view.backgroundColor = UIColor.green.cgColor
}

Swift: rainbow colour circle

Hi i am trying to write colour picker in swift that looks like this.
But so far I managed this.
Draw circle was easy, heres code...
fileprivate func setupScene(){
let circlePath: UIBezierPath = UIBezierPath(arcCenter: CGPoint(x: self.wheelView.frame.width/2, y: self.wheelView.frame.height/2), radius: CGFloat(self.wheelView.frame.height/2), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
//color inside circle
shapeLayer.fillColor = UIColor.clear.cgColor
//colored border of circle
shapeLayer.strokeColor = UIColor.purple.cgColor
//width size of border
shapeLayer.lineWidth = 10
wheelView.layer.addSublayer(shapeLayer)
}
#IBOutlet var wheelView: UIView!
But now I don't know how to insert rainbow colours ... I tried CAGradientLayer but it was not visible. Any good advice?
Details
Xcode 9.1, swift 4
Xcode 10.2.1 (10E1001), Swift 5
Solution
The code was taken from https://github.com/joncardasis/ChromaColorPicker
import UIKit
class RainbowCircle: UIView {
private var radius: CGFloat {
return frame.width>frame.height ? frame.height/2 : frame.width/2
}
private var stroke: CGFloat = 10
private var padding: CGFloat = 5
//MARK: - Drawing
override func draw(_ rect: CGRect) {
super.draw(rect)
drawRainbowCircle(outerRadius: radius - padding, innerRadius: radius - stroke - padding, resolution: 1)
}
init(frame: CGRect, lineHeight: CGFloat) {
super.init(frame: frame)
stroke = lineHeight
}
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
/*
Resolution should be between 0.1 and 1
*/
private func drawRainbowCircle(outerRadius: CGFloat, innerRadius: CGFloat, resolution: Float) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.saveGState()
context.translateBy(x: self.bounds.midX, y: self.bounds.midY) //Move context to center
let subdivisions:CGFloat = CGFloat(resolution * 512) //Max subdivisions of 512
let innerHeight = (CGFloat.pi*innerRadius)/subdivisions //height of the inner wall for each segment
let outterHeight = (CGFloat.pi*outerRadius)/subdivisions
let segment = UIBezierPath()
segment.move(to: CGPoint(x: innerRadius, y: -innerHeight/2))
segment.addLine(to: CGPoint(x: innerRadius, y: innerHeight/2))
segment.addLine(to: CGPoint(x: outerRadius, y: outterHeight/2))
segment.addLine(to: CGPoint(x: outerRadius, y: -outterHeight/2))
segment.close()
//Draw each segment and rotate around the center
for i in 0 ..< Int(ceil(subdivisions)) {
UIColor(hue: CGFloat(i)/subdivisions, saturation: 1, brightness: 1, alpha: 1).set()
segment.fill()
//let lineTailSpace = CGFloat.pi*2*outerRadius/subdivisions //The amount of space between the tails of each segment
let lineTailSpace = CGFloat.pi*2*outerRadius/subdivisions
segment.lineWidth = lineTailSpace //allows for seemless scaling
segment.stroke()
//Rotate to correct location
let rotate = CGAffineTransform(rotationAngle: -(CGFloat.pi*2/subdivisions)) //rotates each segment
segment.apply(rotate)
}
context.translateBy(x: -self.bounds.midX, y: -self.bounds.midY) //Move context back to original position
context.restoreGState()
}
}
Usage
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let rainbowCircle = RainbowCircle(frame: CGRect(x: 50, y: 50, width: 240, height: 420), lineHeight: 5)
rainbowCircle.backgroundColor = .clear
view.addSubview(rainbowCircle)
}
}
Result

Swift - Poor gradient quality in UIView size animation

I have the following structure in place:
main view contains subview1; subview1 is laid out using constraints
subview1 contains subview2; subview2 also has constraints and I need to animate the size of subview2
in subview2 I call CGContextDrawRadialGradient to draw a circle with a gradient
The code in subview1 is:
let circle = GradientCircle(frame: CGRect(origin: CGPointZero, size: frame.size), withColor: color)
addSubview(circle)
circle.centerXAnchor.constraintEqualToAnchor(centerXAnchor).active = true
circle.centerYAnchor.constraintEqualToAnchor(centerYAnchor).active = true
let widthConstraint = circle.widthAnchor.constraintEqualToConstant(frame.width * 3)
let heightConstraint = circle.heightAnchor.constraintEqualToConstant(frame.width * 3)
circle.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activateConstraints([heightConstraint, widthConstraint])
layoutIfNeeded()
UIView.animateWithDuration(3.0, delay: 0, options: [.Repeat], animations: {
widthConstraint.constant = 2
heightConstraint.constant = 2
self.layoutIfNeeded()
}, completion: nil)
Here's a gif to show what the problem is: http://i.stack.imgur.com/LWP7d.gif
Why does this drop in quality occur and how can I fix it? Obviously, I'm new in Swift and I don't fully understand the whole layout and animation thing, so please help if you can, it's slowly driving me insane :)
EDIT:
Here's the GradientCircle class (which is subview2 in the description above), maybe the problem is with how the gradient is drawn:
class GradientCircle : UIView {
var color: UIColor
init(frame: CGRect, withColor: UIColor) {
color = withColor
super.init(frame: frame)
backgroundColor = UIColor.clearColor()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
let locations: [CGFloat] = [0.0, 1.0]
let colorspace = CGColorSpaceCreateDeviceRGB()
let colors = [color.CGColor, GraphicsUtils.darkGrayColor().CGColor]
let gradient = CGGradientCreateWithColors(colorspace, colors, locations)
let startPoint = CGPoint(x: rect.width / 2, y: rect.height / 2)
let endPoint = CGPoint(x: rect.width / 2, y: rect.height / 2)
let startRadius: CGFloat = 0
let endRadius: CGFloat = rect.width / 2
CGContextClearRect(context, rect)
CGContextDrawRadialGradient(context, gradient, startPoint, startRadius, endPoint, endRadius, CGGradientDrawingOptions.DrawsAfterEndLocation)
}

Resources