Анімаційний малюнок кола


105

Я шукаю спосіб оживити малюнок кола. Мені вдалося створити коло, але воно малює все це разом.

Ось мій CircleViewклас:

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

Ось як я додаю його до ієрархії подання в контролері перегляду:

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

Чи є спосіб анімувати малюнок кола за 1 секунду?

Наприклад, частково через анімацію це виглядатиме на зразок синьої лінії на цьому зображенні:

часткова анімація


Коли я використовую вищевказаний клас, коло не повністю заповнене, його кільце (кружечко виглядає) Будь-які ідеї чому?
Туз Зелений

Нехай ви можете спробувати цю відповідь , і це ще одна спроба зробити це
Алі А. Джаліл

Відповіді:


201

Найпростіший спосіб зробити це використовувати силу основної анімації, щоб зробити більшу частину роботи за вас. Для цього нам доведеться перемістити код для малювання кола з вашої drawRectфункції на a CAShapeLayer. Потім, ми можемо використовувати , CABasicAnimationщоб оживити CAShapeLayer«s strokeEndвласність від 0.0до 1.0. strokeEndтут велика частина магії; з документів:

У поєднанні з властивістю strokeStart це властивість визначає підрегіон шляху до обведення. Значення цього властивості вказує відносну точку вздовж шляху, на якому потрібно закінчити обведення, а властивість strokeStart визначає початкову точку. Значення 0,0 являє собою початок шляху, тоді як значення 1,0 являє собою кінець шляху. Значення між ними інтерпретуються лінійно по довжині шляху.

Якщо ми встановлюємо , strokeEndщоб 0.0вона не буде нічого зробити. Якщо ми встановимо його 1.0, воно намалюватиме повне коло. Якщо ми встановимо його 0.5, воно намалюватиме півкільце. тощо.

Отже, для початку, дозволяє створити CAShapeLayerу вашій CircleView«s initфункції та додати цей шар до функції подання sublayers(також обов'язково видалити drawRectфункцію , так як шар буде малювати коло зараз):

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

Примітка. Ми встановлюємо circleLayer.strokeEnd = 0.0так, щоб коло не було намальовано відразу.

Тепер додамо функцію, яку ми можемо викликати, щоб запустити анімацію кола:

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

Тоді все, що нам потрібно зробити, - це змінити вашу addCircleViewфункцію, щоб вона запускала анімацію, коли ви додавали її CircleViewдо 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)
}

Все, що складається разом, має виглядати приблизно так:

коло анімації

Примітка. Це не повториться так, воно залишиться повним колом після його анімації.


2
@MikeS ви відповідаєте чудово! Чи маєте ви якесь уявлення, як застосувати його до SpriteKit всередині SKScene / SKView / SKSpriteNode? Є об’єкт SKShapeNode, але він вважається поганим (помилки) і не підтримуєstrokeEnd
Kalzem

Знайшли це: self.view?.layer.addSublayer(circleLayer):-)
Кальзем

14
@colindunnn просто змінити startAngle:і endAngle:параметри UIBezierPath. 0- це 3 положення, тож 12 положення було б на 90 ° менше, ніж в радіанах -π / 2. Отже, параметри були б startAngle: CGFloat(-M_PI_2), endAngle: CGFloat((M_PI * 2.0) - M_PI_2).
Майк S

2
@MikeS як би ми пішли про реалізацію цього циклу? - без постійного малювання над собою
rambossa

2
Щоб коло починалося і закінчувалося в 12 0'' годин, startAngle: (0 - (Double.pi / 2)) + 0.00001і endAngle: 0 - (Double.pi / 2). Якщо startAngleі endAngleте ж саме, коло не буде намальовано, тому ви віднімаєте дуже маленьке зміщення доstartAngle
Sylvan D Ash

25

Відповідь Майка оновлено для Swift 3.0

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

Для виклику функції:

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_PIзастаріло - використовуйте CGFloat.piзамість цього.
Том

отримання помилки "Використання невирішеного ідентифікатора" CircleView "" у функції
addCircleView

16

Відповідь Майка чудова! Ще один приємний і простий спосіб зробити це використовувати drawRect в поєднанні з setNeedsDisplay (). Це здається млявим, але його немає :-) введіть тут опис зображення

Ми хочемо намалювати коло, починаючи з вершини, яка дорівнює -90 °, а закінчується на 270 °. Центр кола - (centerX, центрY), із заданим радіусом. CurrentAngle - поточний кут кінцевої точки кола, що йде від minAngle (-90) до maxAngle (270).

// 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 ми визначаємо, як коло повинно відображатися:

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

Проблема полягає в тому, що зараз, оскільки currentAngle не змінюється, коло є статичним і навіть не показується, як currentAngle = minAngle.

Потім ми створюємо таймер, і кожного разу, коли цей таймер запускається, ми збільшуємо currentAngle. У верхній частині класу додайте терміни між двома пожежами:

let timeBetweenDraw:CFTimeInterval = 0.01

Додайте таймер:

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

Ми можемо додати функцію, яка буде викликана при запуску таймера:

func updateTimer() {

    if currentAngle < maxAngle {
        currentAngle += 1
    }
}

На жаль, при запуску програми нічого не відображається, оскільки ми не вказали систему, яку вона повинна малювати ще раз. Це робиться за допомогою виклику setNeedsDisplay (). Ось оновлена ​​функція таймера:

func updateTimer() {

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

_ _ _

Весь потрібний код підсумовується тут:

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

Якщо ви хочете змінити швидкість, просто змініть функцію updateTimer або швидкість, з якою ця функція викликається. Крім того, ви можете скасувати таймер, коли коло завершиться, що я забув зробити :-)

Примітка: Щоб додати коло у вашій дошці, просто додайте подання, виберіть його, перейдіть до його інспектора ідентичності та в якості класу вкажіть CircleClosing .

Ура! bRo


Дуже дякую! Чи знаєте ви, як реалізувати це в SKScene? Я не знаходжу жодного способу.
Оскар

Гей, я
вивчу

13

Якщо ви хочете обробляти завершення, це ще одне рішення, схоже на рішення Майка S, виконане в Swift 3.0

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

За допомогою обробника завершення ви можете запустити анімацію ще раз, рекурсивно викликаючи ту саму функцію, щоб зробити анімацію заново (що не буде виглядати дуже приємно), або ви можете мати зворотну функцію, яка буде постійно ланцюжком, поки умова не буде виконана , наприклад:

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

Щоб зробити його ще більш вигадливим, ви можете змінити напрямок анімації таким чином:

 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

Не тільки ви можете підкласифікувати UIView, ви також можете трохи пізніше заглибити підкласCALayer

Іншими словами, обведення CoreAnimationEnd добре. Щоб зателефонувати в розіграш CALayer (в ctx :), часто також нормально

і кришка круглої лінії приємна

111

Ключовим моментом є перекриття методу CALayer action(forKey:)

Дії визначають динамічну поведінку для шару. Наприклад, властивості анімації шару, як правило, мають відповідні об'єкти дій, щоб ініціювати фактичну анімацію. Коли це властивість змінюється, шар шукає об’єкт дії, пов'язаний з ім'ям властивості, і виконує його.

Внутрішній підклас для CAShapeLayer

/**
 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
    }


}

хелперні методи

/**
 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 }
}

використовувати підклас UIView з внутрішнім спеціальним CALayer

@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
    }
}

Використання:

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()

    }


}

з індикаторним зображенням для встановлення кута

введіть тут опис зображення


0

оновлення відповіді @Mike S для Swift 5

працює для frame manuallystoryboard setupautolayout 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")
    }
}

Використання:

зразок коду для frame manuallystoryboard setupautolayout 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)

    }

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.