Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
779 views
in Technique[技术] by (71.8m points)

swift - iOS - Circle shaped gradient

I am trying to draw a circular shaped gradient.

let backgroundView:UIView = UIView()
let backgroundLayer:CAShapeLayer = CAShapeLayer()
let gradient:CAGradientLayer = CAGradientLayer()
...

backgroundLayer.frame = CGRectMake(0, 0, backgroundDiameter, backgroundDiameter)
backgroundLayer.backgroundColor = UIColor.clearColor().CGColor
backgroundLayer.strokeColor = backgroundStrokeColor
backgroundLayer.fillColor = backgroundFillColor


gradient.colors = [UIColor(red: 0.5, green: 0.5, blue: 0.9, alpha: 1.0).CGColor,  
UIColor(red: 0.9, green: 0.9, blue: 0.3, alpha: 1.0).CGColor]
gradient.locations = [0.01, 0.8]
gradient.frame = backgroundLayer.frame

backgroundView.frame = CGRectMake(0, 0, backgroundDiameter, backgroundDiameter)
backgroundView.backgroundColor = UIColor.clearColor()
backgroundView.center = ringControlCenter
backgroundLayer.insertSublayer(gradient, atIndex: 1)
backgroundLayer.path = CGPathCreateWithEllipseInRect(backgroundLayer.frame, nil)

backgroundView.layer.addSublayer(backgroundLayer)
self.addSubview(backgroundView)

However the gradient does not seem to be impacted by:

backgroundLayer.path = CGPathCreateWithEllipseInRect(backgroundLayer.frame, nil)

And still have its initial shape. Is there a way to mask the gradient with an ellipse shaped layer without using CGContext* instructions?

Thank you,

MG

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I've translated Leo library's in Swift 3.x , in other words , the good WCGradientCircleLayer writed in objective-C (it work exactly as aspected)

import UIKit

class WCGraintCircleLayer: CALayer {

    override init () {
        super.init()
    }

    convenience init(bounds:CGRect,position:CGPoint,fromColor:UIColor,toColor:UIColor,linewidth:CGFloat,toValue:CGFloat) {
        self.init()
        self.bounds = bounds
        self.position = position
        let colors : [UIColor] = self.graint(fromColor: fromColor, toColor:toColor, count:4)
        for i in 0..<colors.count-1 {
            let graint = CAGradientLayer()
            graint.bounds = CGRect(origin:CGPoint.zero, size: CGSize(width:bounds.width/2,height:bounds.height/2))
            let valuePoint = self.positionArrayWith(bounds: self.bounds)[i]
            graint.position = valuePoint
            print("iesimo graint position: (graint.position)")
            let fromColor = colors[i]
            let toColor = colors[i+1]
            let colors : [CGColor] = [fromColor.cgColor,toColor.cgColor]
            let stopOne: CGFloat = 0.0
            let stopTwo: CGFloat = 1.0
            let locations : [CGFloat] = [stopOne,stopTwo]
            graint.colors = colors
            graint.locations = locations as [NSNumber]? // with Swift 2 and Swift 3 you can cast directly a `CGFloat` value to `NSNumber` and back
            graint.startPoint = self.startPoints()[i]
            graint.endPoint = self.endPoints()[i]
            self.addSublayer(graint)
            //Set mask
            let shapelayer = CAShapeLayer()
            let rect = CGRect(origin:CGPoint.zero,size:CGSize(width:self.bounds.width - 2 * linewidth,height: self.bounds.height - 2 * linewidth))
            shapelayer.bounds = rect
            shapelayer.position = CGPoint(x:self.bounds.width/2,y: self.bounds.height/2)
            shapelayer.strokeColor = UIColor.blue.cgColor
            shapelayer.fillColor = UIColor.clear.cgColor
            shapelayer.path = UIBezierPath(roundedRect: rect, cornerRadius: rect.width/2).cgPath
            shapelayer.lineWidth = linewidth
            shapelayer.lineCap = kCALineCapRound
            shapelayer.strokeStart = 0.010
            let finalValue = (toValue*0.99)
            shapelayer.strokeEnd = finalValue//0.99;
            self.mask = shapelayer
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func layerWithWithBounds(bounds:CGRect, position:CGPoint, fromColor:UIColor, toColor:UIColor, linewidth : CGFloat,toValue:CGFloat) -> WCGraintCircleLayer {
        let layer = WCGraintCircleLayer(bounds: bounds,position: position,fromColor:fromColor, toColor: toColor,linewidth: linewidth,toValue:toValue )
        return layer
    }

    func graint(fromColor:UIColor, toColor:UIColor, count:Int) -> [UIColor]{
        var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0
        fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha)

        var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0
        toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha)

        var result : [UIColor]! = [UIColor]()

        for i in 0...count {
            let oneR:CGFloat = fromR + (toR - fromR)/CGFloat(count) * CGFloat(i)
            let oneG : CGFloat = fromG + (toG - fromG)/CGFloat(count) * CGFloat(i)
            let oneB : CGFloat = fromB + (toB - fromB)/CGFloat(count) * CGFloat(i)
            let oneAlpha : CGFloat = fromAlpha + (toAlpha - fromAlpha)/CGFloat(count) * CGFloat(i)
            let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha)
            result.append(oneColor)
            print(oneColor)

        }
        return result
    }

    func positionArrayWith(bounds:CGRect) -> [CGPoint]{
        let first = CGPoint(x:(bounds.width/4)*3,y: (bounds.height/4)*1)
        let second = CGPoint(x:(bounds.width/4)*3,y: (bounds.height/4)*3)
        let third = CGPoint(x:(bounds.width/4)*1,y: (bounds.height/4)*3)
        let fourth = CGPoint(x:(bounds.width/4)*1,y: (bounds.height/4)*1)
        print([first,second,third,fourth])
        return [first,second,third,fourth]
    }

    func startPoints() -> [CGPoint] {
        return [CGPoint.zero,CGPoint(x:1,y:0),CGPoint(x:1,y:1),CGPoint(x:0,y:1)]
    }

    func endPoints() -> [CGPoint] {
        return [CGPoint(x:1,y:1),CGPoint(x:0,y:1),CGPoint.zero,CGPoint(x:1,y:0)]
    }

    func midColorWithFromColor(fromColor:UIColor, toColor:UIColor, progress:CGFloat) -> UIColor {
        var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0
        fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha)

        var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0
        toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha)

        let oneR = fromR + (toR - fromR) * progress
        let oneG = fromG + (toG - fromG) * progress
        let oneB = fromB + (toB - fromB) * progress
        let oneAlpha = fromAlpha + (toAlpha - fromAlpha) * progress
        let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha)
        return oneColor
    }

    // This is what you call if you want to draw a full circle.
    func animateCircle(duration: TimeInterval) {
        animateCircleTo(duration: duration, fromValue: 0.010, toValue: 0.99)
    }

    // This is what you call to draw a partial circle.
    func animateCircleTo(duration: TimeInterval, fromValue: CGFloat, toValue: CGFloat){
        // We want to animate the strokeEnd property of the circleLayer
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.isRemovedOnCompletion = true
        // Set the animation duration appropriately
        animation.duration = duration

        // Animate from 0.010 (no circle) to 0.99 (full circle)
        animation.fromValue = 0.010
        animation.toValue = toValue

        // Do an easeout. Don't know how to do a spring instead
        //animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)

        // Set the circleLayer's strokeEnd property to 0.99 now so that it's the
        // right value when the animation ends.
        let circleMask = self.mask as! CAShapeLayer
        circleMask.strokeEnd = toValue

        // Do the actual animation
        circleMask.removeAllAnimations()
        circleMask.add(animation, forKey: "animateCircle")
    }
}

And this is a little example, how to use in Swift 3.x:

let gradientRingLayer = WCGraintCircleLayer(bounds: CGRect(origin: CGPoint.zero,size:CGSize(width: 150, height: 150)), position:CGPoint(x: 200, y: 300),fromColor:UIColor.blue, toColor:UIColor.white, linewidth:4.0, toValue:0)
self.view.layer.addSublayer(gradientRingLayer)
let duration = 3.0
gradientRingLayer.animateCircleTo(duration: duration, fromValue: 0, toValue: 0.99)

This is the code in Swift 2.x:

import UIKit

class WCGraintCircleLayer: CALayer {

override init () {
    super.init()
}

convenience init(bounds:CGRect,position:CGPoint,fromColor:UIColor,toColor:UIColor,linewidth:CGFloat,toValue:CGFloat) {
    self.init()
    self.bounds = bounds
    self.position = position
    let colors : [UIColor] = self.graintFromColor(fromColor, toColor:toColor, count:4)
    for i in 0..<colors.count-1 {
        let graint = CAGradientLayer()
        graint.bounds = CGRectMake(0,0,CGRectGetWidth(bounds)/2,CGRectGetHeight(bounds)/2)
        let valuePoint = self.positionArrayWithMainBounds(self.bounds)[i]
        graint.position = valuePoint
        print("iesimo graint position: (graint.position)")
        let fromColor = colors[i]
        let toColor = colors[i+1]
        let colors : [CGColorRef] = [fromColor.CGColor,toColor.CGColor]
        let stopOne: CGFloat = 0.0
        let stopTwo: CGFloat = 1.0
        let locations : [CGFloat] = [stopOne,stopTwo]
        graint.colors = colors
        graint.locations = locations
        graint.startPoint = self.startPoints()[i]
        graint.endPoint = self.endPoints()[i]
        self.addSublayer(graint)
        //Set mask
        let shapelayer = CAShapeLayer()
        let rect = CGRectMake(0,0,CGRectGetWidth(self.bounds) - 2 * linewidth, CGRectGetHeight(self.bounds) - 2 * linewidth)
        shapelayer.bounds = rect
        shapelayer.position = CGPointMake(CGRectGetWidth(self.bounds)/2, CGRectGetHeight(self.bounds)/2)
        shapelayer.strokeColor = UIColor.blueColor().CGColor
        shapelayer.fillColor = UIColor.clearColor().CGColor
        shapelayer.path = UIBezierPath(roundedRect: rect, cornerRadius: CGRectGetWidth(rect)/2).CGPath
        shapelayer.lineWidth = linewidth
        shapelayer.lineCap = kCALineCapRound
        shapelayer.strokeStart = 0.010
        let finalValue = (toValue*0.99)
        shapelayer.strokeEnd = finalValue//0.99;
        self.mask = shapelayer
    }
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

func layerWithWithBounds(bounds:CGRect, position:CGPoint, fromColor:UIColor, toColor:UIColor, linewidth : CGFloat,toValue:CGFloat) -> WCGraintCircleLayer {
    let layer = WCGraintCircleLayer(bounds: bounds,position: position,fromColor:fromColor, toColor: toColor,linewidth: linewidth,toValue:toValue )
    return layer
}

func graintFromColor(fromColor:UIColor, toColor:UIColor, count:Int) -> [UIColor]{
    var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0
    fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha)

    var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0
    toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha)

    var result : [UIColor]! = [UIColor]()

    for i in 0...count {
        let oneR:CGFloat = fromR + (toR - fromR)/CGFloat(count) * CGFloat(i)
        let oneG : CGFloat = fromG + (toG - fromG)/CGFloat(count) * CGFloat(i)
        let oneB : CGFloat = fromB + (toB - fromB)/CGFloat(count) * CGFloat(i)
        let oneAlpha : CGFloat = fromAlpha + (toAlpha - fromAlpha)/CGFloat(count) * CGFloat(i)
        let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha)
        result.append(oneColor)
        print(oneColor)

    }
    return result
}

func positionArrayWithMainBounds(bounds:CGRect) -> [CGPoint]{
    let first = CGPointMake((CGRectGetWidth(bounds)/4)*3, (CGRectGetHeight(bounds)/4)*1)
    let second = CGPointMake((CGRectGetWidth(bounds)/4)*3, (CGRectGetHeight(bounds)/4)*3)
    let third = CGPointMake((CGRectGetWidth(bounds)/4)*1, (CGRectGetHeight(bounds)/4)*3)
    let fourth = CGPointMake((CGRectGetWidth(bounds)/4)*1, (CGRectGetHeight(bounds)/4)*1)
    print([first,second,third,fourth])
    return [first,second,third,fourth]
}

func startPoints() -> [CGPoint] {
    return [CGPointMake(0,0),CGPointMake(1,0),CGPointMake(1,1),CGPointMake(0,1)]
}

func endPoints() -> [CGPoint] {
    return [CGPointMake(1,1),CGPointMake(0,1),CGPointMake(0,0),CGPointMake(1,0)]
}

func midColorWithFromColor(fromColor:UIColor, toColor:UIColor, progress:CGFloat) -> UIColor {
    var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...