Bir dairenin çizimini canlandırın


105

Bir çemberin çizimini canlandırmanın bir yolunu arıyorum. Çemberi yaratabildim ama hepsini bir araya getiriyor.

İşte CircleViewsınıfım:

import UIKit

class CircleView: UIView {
  override init(frame: CGRect) {
    super.init(frame: frame)
    self.backgroundColor = UIColor.clearColor()
  }

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


  override func drawRect(rect: CGRect) {
    // Get the Graphics Context
    var context = UIGraphicsGetCurrentContext();

    // Set the circle outerline-width
    CGContextSetLineWidth(context, 5.0);

    // Set the circle outerline-colour
    UIColor.redColor().set()

    // Create Circle
    CGContextAddArc(context, (frame.size.width)/2, frame.size.height/2, (frame.size.width - 10)/2, 0.0, CGFloat(M_PI * 2.0), 1)

    // Draw
    CGContextStrokePath(context);
  }
}

Ve işte bunu görünüm denetleyicimdeki görünüm hiyerarşisine şu şekilde eklerim:

func addCircleView() {
    let diceRoll = CGFloat(Int(arc4random_uniform(7))*50)
    var circleWidth = CGFloat(200)
    var circleHeight = circleWidth
    // Create a new CircleView
    var circleView = CircleView(frame: CGRectMake(diceRoll, 0, circleWidth, circleHeight))

    view.addSubview(circleView)
}

Çemberin çizimini 1 saniyede canlandırmanın bir yolu var mı?

Örneğin, animasyonun bir bölümünde bu resimdeki mavi çizgi gibi görünecektir:

kısmi animasyon


Yukarıdaki sınıfı kullandığımda, daire tamamen dolu değil, bir halka daire (halka görünümlü) Herhangi bir fikriniz neden?
Ace Yeşil

Bunu yapmaya çalışan başka bir cevap olan bu cevabı deneyebilir misin
Ali A. Jalil

Yanıtlar:


201

Bunu yapmanın en kolay yolu, işin çoğunu sizin yerinize yapmak için çekirdek animasyonun gücünü kullanmaktır. Bunu yapmak için, daire çizim kodunuzu drawRectfonksiyonunuzdan a'ya taşımamız gerekecek CAShapeLayer. Sonra, bir kullanabilirsiniz CABasicAnimationanimasyon uygulamak için CAShapeLayer'ın strokeEndmalını 0.0için 1.0. strokeEndburadaki büyünün büyük bir kısmı; dokümanlardan:

StrokStart özelliğiyle birlikte bu özellik, kontura giden yolun alt bölgesini tanımlar. Bu özellikteki değer, strokStart özelliği başlangıç ​​noktasını tanımlarken konturlamanın biteceği yol boyunca göreceli noktayı belirtir. 0.0 değeri yolun başlangıcını temsil ederken 1.0 değeri yolun sonunu temsil eder. Aradaki değerler, yol uzunluğu boyunca doğrusal olarak yorumlanır.

Biz ayarlarsanız strokeEndiçin 0.0, hiçbir şey çekmeyecektir. Olarak ayarlarsak 1.0, tam bir daire çizecek. Eğer ayarlarsak0.5 , yarım daire çizer. vb.

Yani, başlamak için, bir oluşturmanızı sağlar CAShapeLayersenin içinde CircleView'nin inito katman fonksiyonu ve ekleme görünümü en sublayers(ayrıca kaldırıldığından emin olun drawRectkatman şimdi daire çizim olacağından fonksiyonu):

let circleLayer: CAShapeLayer!

override init(frame: CGRect) {
    super.init(frame: frame)
    self.backgroundColor = UIColor.clearColor()

    // Use UIBezierPath as an easy way to create the CGPath for the layer.
    // The path should be the entire circle.
    let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(Double.pi * 2.0), clockwise: true)

    // Setup the CAShapeLayer with the path, colors, and line width
    circleLayer = CAShapeLayer()
    circleLayer.path = circlePath.CGPath
    circleLayer.fillColor = UIColor.clearColor().CGColor
    circleLayer.strokeColor = UIColor.redColor().CGColor
    circleLayer.lineWidth = 5.0;

    // Don't draw the circle initially
    circleLayer.strokeEnd = 0.0

    // Add the circleLayer to the view's layer's sublayers
    layer.addSublayer(circleLayer)
}

Not:circleLayer.strokeEnd = 0.0 Çemberin hemen çizilmemesi için ayarlıyoruz .

Şimdi, daire animasyonunu tetiklemek için çağırabileceğimiz bir işlev ekleyelim:

func animateCircle(duration: NSTimeInterval) {
    // We want to animate the strokeEnd property of the circleLayer
    let animation = CABasicAnimation(keyPath: "strokeEnd")

    // Set the animation duration appropriately
    animation.duration = duration

    // Animate from 0 (no circle) to 1 (full circle)
    animation.fromValue = 0
    animation.toValue = 1

    // Do a linear animation (i.e. the speed of the animation stays the same)
    animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)

    // Set the circleLayer's strokeEnd property to 1.0 now so that it's the
    // right value when the animation ends.
    circleLayer.strokeEnd = 1.0

    // Do the actual animation
    circleLayer.addAnimation(animation, forKey: "animateCircle")
}

Sonra yapmamız gereken tüm değiştirmektir addCircleViewbunu eklemek animasyon tetikler, böylece fonksiyonu CircleViewonun için superview:

func addCircleView() {
    let diceRoll = CGFloat(Int(arc4random_uniform(7))*50)
     var circleWidth = CGFloat(200)
     var circleHeight = circleWidth

        // Create a new CircleView
     var circleView = CircleView(frame: CGRectMake(diceRoll, 0, circleWidth, circleHeight))

     view.addSubview(circleView)

     // Animate the drawing of the circle over the course of 1 second
     circleView.animateCircle(1.0)
}

Tüm bunlar bir araya getirildiğinde şuna benzemelidir:

daire animasyonu

Not: Bu şekilde tekrar etmez, canlandırıldıktan sonra tam bir daire olarak kalır.


2
@MikeS cevabınız harika! Bunu bir SKScene / SKView / SKSpriteNode içindeki SpriteKit'e nasıl uygulayacağınıza dair bir fikriniz var mı? SKShapeNode nesnesi var ancak kötü olarak kabul ediliyor (hatalar) ve desteklemiyorstrokeEnd
Kalzem

Bulundu: self.view?.layer.addSublayer(circleLayer):-)
Kalzem

14
@colindunnn sadece değiştirmek startAngle:ve endAngle:parametreleri UIBezierPath. 03 konumdur, bu nedenle 12 konumu, radyan cinsinden -π / 2 olan bundan 90 ° daha az olacaktır. Yani parametreler olacaktır startAngle: CGFloat(-M_PI_2), endAngle: CGFloat((M_PI * 2.0) - M_PI_2).
Mike S

2
@MikeS bunu bir döngüde uygulamaya nasıl başlayabiliriz? - sürekli üstüne
çizmeden

2
Dairenin saat 12'de başlayıp bitmesi startAngle: (0 - (Double.pi / 2)) + 0.00001ve endAngle: 0 - (Double.pi / 2). Eğer startAngleve endAngleaynıysa, daire çizilmeyecektir, bu yüzden çok çok küçük bir ofseti çıkarırsınızstartAngle
Sylvan D Ash

25

Mikes yanıtı Swift 3.0 için güncellendi

var circleLayer: CAShapeLayer!

override init(frame: CGRect) {
    super.init(frame: frame)
    self.backgroundColor = UIColor.clear

    // Use UIBezierPath as an easy way to create the CGPath for the layer.
    // The path should be the entire circle.
    let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)

    // Setup the CAShapeLayer with the path, colors, and line width
    circleLayer = CAShapeLayer()
    circleLayer.path = circlePath.cgPath
    circleLayer.fillColor = UIColor.clear.cgColor
    circleLayer.strokeColor = UIColor.red.cgColor
    circleLayer.lineWidth = 5.0;

    // Don't draw the circle initially
    circleLayer.strokeEnd = 0.0

    // Add the circleLayer to the view's layer's sublayers
    layer.addSublayer(circleLayer)
} 

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

func animateCircle(duration: TimeInterval) {
    // We want to animate the strokeEnd property of the circleLayer
    let animation = CABasicAnimation(keyPath: "strokeEnd")

    // Set the animation duration appropriately
    animation.duration = duration

    // Animate from 0 (no circle) to 1 (full circle)
    animation.fromValue = 0
    animation.toValue = 1

    // Do a linear animation (i.e The speed of the animation stays the same)
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)

    // Set the circleLayer's strokeEnd property to 1.0 now so that it's the
    // Right value when the animation ends
    circleLayer.strokeEnd = 1.0

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

İşlevi çağırmak için:

func addCircleView() {
    let diceRoll = CGFloat(Int(arc4random_uniform(7))*50)
    var circleWidth = CGFloat(200)
    var circleHeight = circleWidth

    // Create a new CircleView
    let circleView = CircleView(frame: CGRect(x: diceRoll, y: 0, width: circleWidth, height: circleHeight))
    //let test = CircleView(frame: CGRect(x: diceRoll, y: 0, width: circleWidth, height: circleHeight))

    view.addSubview(circleView)

    // Animate the drawing of the circle over the course of 1 second
    circleView.animateCircle(duration: 1.0)
}

2
M_PIkullanımdan kaldırıldı - CGFloat.pibunun yerine kullanın.
Tom

addCircleView işlevinde "Çözümlenmemiş tanımlayıcı 'CircleView' kullanımı" hatası alınıyor
UserDev

16

Mike'ın cevabı harika! Bunu yapmanın bir başka güzel ve basit yolu da setNeedsDisplay () ile birlikte drawRect kullanmaktır. Gecikmiş gibi görünüyor, ama değil :-) görüntü açıklamasını buraya girin

Yukarıdan -90 ° olan ve 270 ° ile biten bir daire çizmek istiyoruz. Dairenin merkezi, belirli bir yarıçapa sahip (centerX, centerY) dir. CurrentAngle, dairenin bitiş noktasının minAngle (-90) 'dan maxAngle (270)' e giden mevcut açısıdır.

// MARK: Properties
let centerX:CGFloat = 55
let centerY:CGFloat = 55
let radius:CGFloat = 50

var currentAngle:Float = -90
let minAngle:Float = -90
let maxAngle:Float = 270

DrawRect'te, dairenin nasıl görüntüleneceğini belirtiyoruz:

override func drawRect(rect: CGRect) {

    let context = UIGraphicsGetCurrentContext()

    let path = CGPathCreateMutable()

    CGPathAddArc(path, nil, centerX, centerY, radius, CGFloat(GLKMathDegreesToRadians(minAngle)), CGFloat(GLKMathDegreesToRadians(currentAngle)), false)

    CGContextAddPath(context, path)
    CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor)
    CGContextSetLineWidth(context, 3)
    CGContextStrokePath(context)
}

Sorun şu ki, currentAngle değişmediğinden, çember statiktir ve currentAngle = minAngle olarak görünmez bile.

Daha sonra bir zamanlayıcı oluşturuyoruz ve bu zamanlayıcı her tetiklendiğinde, akım Açısını artırıyoruz. Sınıfınızın en üstüne, iki ateş arasındaki zamanlamayı ekleyin:

let timeBetweenDraw:CFTimeInterval = 0.01

Başlığınıza zamanlayıcıyı ekleyin:

NSTimer.scheduledTimerWithTimeInterval(timeBetweenDraw, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)

Zamanlayıcı tetiklendiğinde çağrılacak işlevi ekleyebiliriz:

func updateTimer() {

    if currentAngle < maxAngle {
        currentAngle += 1
    }
}

Ne yazık ki, uygulamayı çalıştırırken, yeniden çizmesi gereken sistemi belirtmediğimiz için hiçbir şey görüntülenmiyor. Bu, setNeedsDisplay () çağrılarak yapılır. İşte güncellenmiş zamanlayıcı işlevi:

func updateTimer() {

    if currentAngle < maxAngle {
        currentAngle += 1
        setNeedsDisplay()
    }
}

_ _ _

İhtiyacınız olan tüm kod burada özetlenmiştir:

import UIKit
import GLKit

class CircleClosing: UIView {

    // MARK: Properties
    let centerX:CGFloat = 55
    let centerY:CGFloat = 55
    let radius:CGFloat = 50

    var currentAngle:Float = -90

    let timeBetweenDraw:CFTimeInterval = 0.01

    // MARK: Init
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    func setup() {
        self.backgroundColor = UIColor.clearColor()
        NSTimer.scheduledTimerWithTimeInterval(timeBetweenDraw, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
    }

    // MARK: Drawing
    func updateTimer() {

        if currentAngle < 270 {
            currentAngle += 1
            setNeedsDisplay()
        }
    }

    override func drawRect(rect: CGRect) {

        let context = UIGraphicsGetCurrentContext()

        let path = CGPathCreateMutable()

        CGPathAddArc(path, nil, centerX, centerY, radius, -CGFloat(M_PI/2), CGFloat(GLKMathDegreesToRadians(currentAngle)), false)

        CGContextAddPath(context, path)
        CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor)
        CGContextSetLineWidth(context, 3)
        CGContextStrokePath(context)
    }
}

Hızı değiştirmek istiyorsanız, sadece updateTimer işlevini veya bu işlevin çağrılma hızını değiştirin. Ayrıca, daire tamamlandığında zamanlayıcıyı geçersiz kılmak isteyebilirsiniz ki bunu yapmayı unuttum :-)

Not: Çevreyi film şeridinize eklemek için bir görünüm ekleyin, seçin, Kimlik Denetçisine gidin ve Sınıf olarak CircleClosing'i belirtin .

Şerefe! kanka


Çok teşekkürler! Bunu bir SKScene'ye nasıl uygulayacağınızı biliyor musunuz? Ben herhangi bir yol bulamıyorum.
Oscar

Hey, henüz SK konusunda yetkin olmadığım için
araştıracağım

13

Bir tamamlama işleyicisi istiyorsanız, bu, Swift 3.0'da yapılan Mike S'ye benzer başka bir çözümdür.

func animateCircleFull(duration: TimeInterval) {
    CATransaction.begin()
    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.duration = duration
    animation.fromValue = 0
    animation.toValue = 1
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    circleLayer.strokeEnd = 1.0
    CATransaction.setCompletionBlock {
        print("animation complete")
    }
    // Do the actual animation
    circleLayer.add(animation, forKey: "animateCircle")
    CATransaction.commit()
}

Tamamlama işleyicisiyle, animasyonu tekrar tekrar yapmak için aynı işlevi yinelemeli olarak çağırarak tekrar çalıştırabilirsiniz (ki bu pek hoş görünmeyecektir) veya bir koşul karşılanana kadar sürekli zincirleme yapacak tersine çevrilmiş bir işleve sahip olabilirsiniz. , Örneğin:

func animate(duration: TimeInterval){
    self.isAnimating = true
    self.animateCircleFull(duration: 1)
}

func endAnimate(){
    self.isAnimating = false
}

func animateCircleFull(duration: TimeInterval) {
    if self.isAnimating{
        CATransaction.begin()
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.duration = duration
        animation.fromValue = 0
        animation.toValue = 1
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        circleLayer.strokeEnd = 1.0
        CATransaction.setCompletionBlock { 
            self.animateCircleEmpty(duration: duration)
        }
        // Do the actual animation
        circleLayer.add(animation, forKey: "animateCircle")
        CATransaction.commit()
    }
}

func animateCircleEmpty(duration: TimeInterval){
    if self.isAnimating{
        CATransaction.begin()
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.duration = duration
        animation.fromValue = 1
        animation.toValue = 0
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        circleLayer.strokeEnd = 0
        CATransaction.setCompletionBlock {
            self.animateCircleFull(duration: duration)
        }
        // Do the actual animation
        circleLayer.add(animation, forKey: "animateCircle")
        CATransaction.commit()
    }
}

Daha da çekici hale getirmek için animasyonun yönünü şu şekilde değiştirebilirsiniz:

 func setCircleClockwise(){
    let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
    self.circleLayer.removeFromSuperlayer()
    self.circleLayer = formatCirle(circlePath: circlePath)
    self.layer.addSublayer(self.circleLayer)
}

func setCircleCounterClockwise(){
    let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: false)
    self.circleLayer.removeFromSuperlayer()
    self.circleLayer = formatCirle(circlePath: circlePath)
    self.layer.addSublayer(self.circleLayer)
}

func formatCirle(circlePath: UIBezierPath) -> CAShapeLayer{
    let circleShape = CAShapeLayer()
    circleShape.path = circlePath.cgPath
    circleShape.fillColor = UIColor.clear.cgColor
    circleShape.strokeColor = UIColor.red.cgColor
    circleShape.lineWidth = 10.0;
    circleShape.strokeEnd = 0.0
    return circleShape
}

func animate(duration: TimeInterval){
    self.isAnimating = true
    self.animateCircleFull(duration: 1)
}

func endAnimate(){
    self.isAnimating = false
}

func animateCircleFull(duration: TimeInterval) {
    if self.isAnimating{
        CATransaction.begin()
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.duration = duration
        animation.fromValue = 0
        animation.toValue = 1
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        circleLayer.strokeEnd = 1.0
        CATransaction.setCompletionBlock {
            self.setCircleCounterClockwise()
            self.animateCircleEmpty(duration: duration)
        }
        // Do the actual animation
        circleLayer.add(animation, forKey: "animateCircle")
        CATransaction.commit()
    }
}

func animateCircleEmpty(duration: TimeInterval){
    if self.isAnimating{
        CATransaction.begin()
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.duration = duration
        animation.fromValue = 1
        animation.toValue = 0
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        circleLayer.strokeEnd = 0
        CATransaction.setCompletionBlock {
            self.setCircleClockwise()
            self.animateCircleFull(duration: duration)
        }
        // Do the actual animation
        circleLayer.add(animation, forKey: "animateCircle")
        CATransaction.commit()
    }
}

1

Hayır, sadece siz alt sınıflara UIViewgirebilirsiniz, ayrıca biraz daha derine inebilir, alt sınıflara daCALayer

Başka bir deyişle, CoreAnimation'ın strokEnd'i tamam. CALayer'ın çekilişini (ctx içinde :) sık sık çağırmak da sorun değil

ve yuvarlak hatlı şapka güzel

111

Kilit nokta, CALayer'ın yöntemini geçersiz kılmaktır action(forKey:)

Eylemler, bir katman için dinamik davranışları tanımlar. Örneğin, bir katmanın canlandırılabilir özellikleri, gerçek animasyonları başlatmak için tipik olarak karşılık gelen eylem nesnelerine sahiptir. Bu özellik değiştiğinde, katman özellik adıyla ilişkilendirilmiş eylem nesnesini arar ve onu yürütür.

CAShapeLayer için dahili alt sınıf

/**
 The internal subclass for CAShapeLayer.
 This is the class that handles all the drawing and animation.
 This class is not interacted with, instead
 properties are set in UICircularRing

 */
class UICircularRingLayer: CAShapeLayer {

    // MARK: Properties
    @NSManaged var val: CGFloat

    let ringWidth: CGFloat = 20
    let startAngle = CGFloat(-90).rads

    // MARK: Init

    override init() {
        super.init()
    }

    override init(layer: Any) {
        guard let layer = layer as? UICircularRingLayer else { fatalError("unable to copy layer") }

        super.init(layer: layer)
    }

    required init?(coder aDecoder: NSCoder) { return nil }

    // MARK: Draw

    /**
     Override for custom drawing.
     Draws the ring 
     */
    override func draw(in ctx: CGContext) {
        super.draw(in: ctx)
        UIGraphicsPushContext(ctx)
        // Draw the rings
        drawRing(in: ctx)
        UIGraphicsPopContext()
    }

    // MARK: Animation methods

    /**
     Watches for changes in the val property, and setNeedsDisplay accordingly
     */
    override class func needsDisplay(forKey key: String) -> Bool {
        if key == "val" {
            return true
        } else {
            return super.needsDisplay(forKey: key)
        }
    }

    /**
     Creates animation when val property is changed
     */
    override func action(forKey event: String) -> CAAction? {
        if event == "val"{
            let animation = CABasicAnimation(keyPath: "val")
            animation.fromValue = presentation()?.value(forKey: "val")
            animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
            animation.duration = 2
            return animation
        } else {
            return super.action(forKey: event)
        }
    }


    /**
     Draws the ring for the view.
     Sets path properties according to how the user has decided to customize the view.
     */
    private func drawRing(in ctx: CGContext) {

        let center: CGPoint = CGPoint(x: bounds.midX, y: bounds.midY)

        let radiusIn: CGFloat = (min(bounds.width, bounds.height) - ringWidth)/2
        // Start drawing
        let innerPath: UIBezierPath = UIBezierPath(arcCenter: center,
                                                   radius: radiusIn,
                                                   startAngle: startAngle,
                                                   endAngle: toEndAngle,
                                                   clockwise: true)

        // Draw path
        ctx.setLineWidth(ringWidth)
        ctx.setLineJoin(.round)
        ctx.setLineCap(CGLineCap.round)
        ctx.setStrokeColor(UIColor.red.cgColor)
        ctx.addPath(innerPath.cgPath)
        ctx.drawPath(using: .stroke)

    }


    var toEndAngle: CGFloat {
        return (val * 360.0).rads + startAngle
    }


}

yardımcı yöntemler

/**
 A private extension to CGFloat in order to provide simple
 conversion from degrees to radians, used when drawing the rings.
 */
extension CGFloat {
    var rads: CGFloat { return self * CGFloat.pi / 180 }
}

dahili özel CALayer ile bir UIView alt sınıfı kullanın

@IBDesignable open class UICircularRing: UIView {

    /**
     Set the ring layer to the default layer, casted as custom layer
     */
    var ringLayer: UICircularRingLayer {
        return layer as! UICircularRingLayer
    }

    /**
     Overrides the default layer with the custom UICircularRingLayer class
     */
    override open class var layerClass: AnyClass {
        return UICircularRingLayer.self
    }

    /**
     Override public init to setup() the layer and view
     */
    override public init(frame: CGRect) {
        super.init(frame: frame)
        // Call the internal initializer
        setup()
    }

    /**
     Override public init to setup() the layer and view
     */
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        // Call the internal initializer
        setup()
    }

    /**
     This method initializes the custom CALayer to the default values
     */
    func setup(){
        // Helps with pixelation and blurriness on retina devices
        ringLayer.contentsScale = UIScreen.main.scale
        ringLayer.shouldRasterize = true
        ringLayer.rasterizationScale = UIScreen.main.scale * 2
        ringLayer.masksToBounds = false

        backgroundColor = UIColor.clear
        ringLayer.backgroundColor = UIColor.clear.cgColor
        ringLayer.val = 0
    }


    func startAnimation() {
        ringLayer.val = 1
    }
}

Kullanım:

class ViewController: UIViewController {

    let progressRing = UICircularRing(frame: CGRect(x: 100, y: 100, width: 250, height: 250))


    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(progressRing)
    }

    @IBAction func animate(_ sender: UIButton) {

        progressRing.startAnimation()

    }


}

açıyı ayarlamak için bir gösterge görüntüsü ile

görüntü açıklamasını buraya girin


0

@Mike S'nin Swift 5 cevabını güncelleme

frame manuallystoryboard setup、için çalışıyorautolayout setup

class CircleView: UIView {

    let circleLayer: CAShapeLayer = {
        // Setup the CAShapeLayer with the path, colors, and line width
        let circle = CAShapeLayer()
        circle.fillColor = UIColor.clear.cgColor
        circle.strokeColor = UIColor.red.cgColor
        circle.lineWidth = 5.0

        // Don't draw the circle initially
        circle.strokeEnd = 0.0
        return circle
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }

    func setup(){
        backgroundColor = UIColor.clear

        // Add the circleLayer to the view's layer's sublayers
        layer.addSublayer(circleLayer)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        // Use UIBezierPath as an easy way to create the CGPath for the layer.
        // The path should be the entire circle.
        let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(Double.pi * 2.0), clockwise: true)

        circleLayer.path = circlePath.cgPath
    }

    func animateCircle(duration t: TimeInterval) {
        // We want to animate the strokeEnd property of the circleLayer
        let animation = CABasicAnimation(keyPath: "strokeEnd")

        // Set the animation duration appropriately
        animation.duration = t

        // Animate from 0 (no circle) to 1 (full circle)
        animation.fromValue = 0
        animation.toValue = 1

        // Do a linear animation (i.e. the speed of the animation stays the same)
        animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)

        // Set the circleLayer's strokeEnd property to 1.0 now so that it's the
        // right value when the animation ends.
        circleLayer.strokeEnd = 1.0

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

Kullanım:

frame manuallystoryboard setup、için örnek kodautolayout setup

class ViewController: UIViewController {

    @IBOutlet weak var circleV: CircleView!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func animateFrame(_ sender: UIButton) {


        let diceRoll = CGFloat(Int(arc4random_uniform(7))*30)
        let circleEdge = CGFloat(200)

        // Create a new CircleView
        let circleView = CircleView(frame: CGRect(x: 50, y: diceRoll, width: circleEdge, height: circleEdge))

        view.addSubview(circleView)

        // Animate the drawing of the circle over the course of 1 second
        circleView.animateCircle(duration: 1.0)


    }

    @IBAction func animateAutolayout(_ sender: UIButton) {

        let circleView = CircleView(frame: CGRect.zero)
        circleView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(circleView)
        circleView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        circleView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        circleView.widthAnchor.constraint(equalToConstant: 250).isActive = true
        circleView.heightAnchor.constraint(equalToConstant: 250).isActive = true
        // Animate the drawing of the circle over the course of 1 second
        circleView.animateCircle(duration: 1.0)
    }

    @IBAction func animateStoryboard(_ sender: UIButton) {
        // Animate the drawing of the circle over the course of 1 second
        circleV.animateCircle(duration: 1.0)

    }

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.