從案例出發(fā),由淺到深了解 iOS 動(dòng)畫(huà)

收錄:原文地址

前言

iOS 的動(dòng)畫(huà)框架很成熟旗国,提供必要的信息枯怖,譬如動(dòng)畫(huà)的起始位置與終止位置,動(dòng)畫(huà)效果就出來(lái)了

動(dòng)畫(huà)的實(shí)現(xiàn)方式挺多的能曾,

有系統(tǒng)提供的簡(jiǎn)單 API 度硝,直接提供動(dòng)畫(huà)般的交互效果。

有手動(dòng)設(shè)置交互效果寿冕,看起來(lái)像是動(dòng)畫(huà)蕊程,一般要用到插值。

至于動(dòng)畫(huà)框架驼唱,有 UIView 級(jí)別的藻茂,有功能強(qiáng)勁的 CALayer 級(jí)別的動(dòng)畫(huà)。

CALayer 級(jí)別的動(dòng)畫(huà)通過(guò)靈活設(shè)置的 CoreAnimation玫恳,CoreAnimation 的常規(guī)操作辨赐,就是自定義路徑

當(dāng)然有蘋(píng)果推了幾年的 UIViewPropertyAnimator, 動(dòng)畫(huà)可交互性做得比較好京办;

話(huà)不多說(shuō)掀序;直接來(lái)看案例


例子一:導(dǎo)航欄動(dòng)畫(huà)

navigationController?.hidesBarsOnSwipe = true

簡(jiǎn)單設(shè)置 hidesBarsOnSwipe 屬性,就可以了臂港。

該屬性森枪,除了可以調(diào)節(jié)頭部導(dǎo)航欄进栽,還可以調(diào)節(jié)底部標(biāo)簽工具欄 toolbar


例子二:屏幕開(kāi)鎖效果

一眼看起來(lái)有點(diǎn)炫楷扬,實(shí)際設(shè)置很簡(jiǎn)單

func openLock() {
        UIView.animate(withDuration: 0.4, delay: 1.0, options: [], animations: {

            // Rotate keyhole.
            self.lockKeyhole.transform = CGAffineTransform(rotationAngle: CGFloat.pi)

            }, completion: { _ in

                UIView.animate(withDuration: 0.5, delay: 0.2, options: [], animations: {

                    // Open lock.
                    let yDelta = self.lockBorder.frame.maxY

                    self.topLock.center.y -= yDelta
                    self.lockKeyhole.center.y -= yDelta
                    self.lockBorder.center.y -= yDelta
                    self.bottomLock.center.y += yDelta

                    }, completion: { _ in
                        self.topLock.removeFromSuperview()
                        self.lockKeyhole.removeFromSuperview()
                        self.lockBorder.removeFromSuperview()
                        self.bottomLock.removeFromSuperview()
                })
        })
    }

總共有四個(gè)控件废岂,先讓中間的鎖控件旋轉(zhuǎn)一下享扔,然后對(duì)四個(gè)控件,做移位操作

用簡(jiǎn)單的關(guān)鍵幀動(dòng)畫(huà)式散,處理要優(yōu)雅一點(diǎn)


例子三:地圖定位波動(dòng)

看上去有些眼花的動(dòng)畫(huà)筋遭,可以分解為三個(gè)動(dòng)畫(huà)

image

一波未平,一波又起暴拄,做一個(gè)動(dòng)畫(huà)效果的疊加漓滔,就成了動(dòng)畫(huà)的第一幅動(dòng)畫(huà)

一個(gè)動(dòng)畫(huà)波動(dòng)效果,效果用到了透明度的變化乖篷,還有范圍的變化

范圍的變化响驴,用的就是 CoreAnimation 的路徑 path

CoreAnimation 簡(jiǎn)單設(shè)置,就是指明 from 撕蔼、to豁鲤,動(dòng)畫(huà)的起始狀態(tài),和動(dòng)畫(huà)終止?fàn)顟B(tài)鲸沮,然后選擇使用哪一種動(dòng)畫(huà)效果琳骡。

動(dòng)畫(huà)的起始狀態(tài),一般是起始位置讼溺。簡(jiǎn)單的動(dòng)畫(huà)楣号,就是讓他動(dòng)起來(lái)

func sonar(_ beginTime: CFTimeInterval) {
        let circlePath1 = UIBezierPath(arcCenter: self.center, radius: CGFloat(3), startAngle: CGFloat(0), endAngle:CGFloat.pi * 2, clockwise: true)

        let circlePath2 = UIBezierPath(arcCenter: self.center, radius: CGFloat(80), startAngle: CGFloat(0), endAngle:CGFloat.pi * 2, clockwise: true)

        let shapeLayer = CAShapeLayer()
        shapeLayer.strokeColor = ColorPalette.green.cgColor
        shapeLayer.fillColor = ColorPalette.green.cgColor
        shapeLayer.path = circlePath1.cgPath
        self.layer.addSublayer(shapeLayer)

        // 兩個(gè)動(dòng)畫(huà)

        let pathAnimation = CABasicAnimation(keyPath: "path")
        pathAnimation.fromValue = circlePath1.cgPath
        pathAnimation.toValue = circlePath2.cgPath

        let alphaAnimation = CABasicAnimation(keyPath: "opacity")
        alphaAnimation.fromValue = 0.8
        alphaAnimation.toValue = 0

        // 組動(dòng)畫(huà)
        let animationGroup = CAAnimationGroup()
        animationGroup.beginTime = beginTime
        animationGroup.animations = [pathAnimation, alphaAnimation]

        // 時(shí)間有講究
        animationGroup.duration = 2.76

        // 不斷重復(fù)
        animationGroup.repeatCount = Float.greatestFiniteMagnitude
        animationGroup.isRemovedOnCompletion = false
        animationGroup.fillMode = CAMediaTimingFillMode.forwards

        // Add the animation to the layer.
        // key 用來(lái) debug
        shapeLayer.add(animationGroup, forKey: "sonar")
    }

波動(dòng)效果調(diào)用了三次

func startAnimation() {
        // 三次動(dòng)畫(huà),效果合成怒坯,
        sonar(CACurrentMediaTime())
        sonar(CACurrentMediaTime() + 0.92)
        sonar(CACurrentMediaTime() + 1.84)
    }

例子四:加載動(dòng)畫(huà)

image

這是 UIView 框架自帶的動(dòng)畫(huà)炫狱,看起來(lái)不錯(cuò),就是做了一個(gè)簡(jiǎn)單的縮放敬肚,通過(guò) transform 屬性做仿射變換

func startAnimation() {

        dotOne.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
        dotTwo.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
        dotThree.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)

        // 三個(gè)不同的 delay, 漸進(jìn)時(shí)間
        UIView.animate(withDuration: 0.6, delay: 0.0, options: [.repeat, .autoreverse], animations: {
            self.dotOne.transform = CGAffineTransform.identity
            }, completion: nil)

        UIView.animate(withDuration: 0.6, delay: 0.2, options: [.repeat, .autoreverse], animations: {
            self.dotTwo.transform = CGAffineTransform.identity
            }, completion: nil)

        UIView.animate(withDuration: 0.6, delay: 0.4, options: [.repeat, .autoreverse], animations: {
            self.dotThree.transform = CGAffineTransform.identity
            }, completion: nil)
    }

例子五:下劃線點(diǎn)擊轉(zhuǎn)移動(dòng)畫(huà)

這個(gè)也是 UIView 的動(dòng)畫(huà)

image

動(dòng)畫(huà)的實(shí)現(xiàn)效果毕荐,是通過(guò)更改約束束析。

約束動(dòng)畫(huà)要注意的是艳馒,確保動(dòng)畫(huà)的起始位置準(zhǔn)確,起始的時(shí)候员寇,一般要調(diào)用其父視圖的 layoutIfNeeded 方法弄慰,確保視圖的實(shí)際位置與約束設(shè)置的一致。

這里的約束動(dòng)畫(huà)蝶锋,是通過(guò) NSLayoutAnchor 做得陆爽。

一般我們用的是 SnapKit 設(shè)置約束,調(diào)用也差不多扳缕。

func animateContraintsForUnderlineView(_ underlineView: UIView, toSide: Side) {

        switch toSide {
        case .left:

            for constraint in underlineView.superview!.constraints {
                if constraint.identifier == ConstraintIdentifiers.centerRightConstraintIdentifier {

                    constraint.isActive = false

                    let leftButton = optionsBar.arrangedSubviews[0]
                    let centerLeftConstraint = underlineView.centerXAnchor.constraint(equalTo: leftButton.centerXAnchor)
                    centerLeftConstraint.identifier = ConstraintIdentifiers.centerLeftConstraintIdentifier

                    NSLayoutConstraint.activate([centerLeftConstraint])
                }
            }

        case .right:

            for constraint in underlineView.superview!.constraints {
                if constraint.identifier == ConstraintIdentifiers.centerLeftConstraintIdentifier {
                    // 先失效慌闭,舊的約束
                    constraint.isActive = false
                    // 再新建約束别威,并激活
                    let rightButton = optionsBar.arrangedSubviews[1]
                    let centerRightConstraint = underlineView.centerXAnchor.constraint(equalTo: rightButton.centerXAnchor)
                    centerRightConstraint.identifier = ConstraintIdentifiers.centerRightConstraintIdentifier

                    NSLayoutConstraint.activate([centerRightConstraint])

                }
            }
        }

        UIView.animate(withDuration: 0.6, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.0, options: [], animations: {
            self.view.layoutIfNeeded()
            }, completion: nil)

    }

例子六:列表視圖的頭部拉伸效果

這個(gè)沒(méi)有用到動(dòng)畫(huà)框架,就是做了一個(gè)交互插值

就是補(bǔ)插連續(xù)的函數(shù) scrollViewDidScroll, 及時(shí)更新列表視圖頭部的位置驴剔、尺寸

override func scrollViewDidScroll(_ scrollView: UIScrollView) {
        updateHeaderView()
    }

    func updateHeaderView() {
        var headerRect = CGRect(x: 0, y: -tableHeaderHeight, width: tableView.bounds.width, height: tableHeaderHeight)
        // 決定拉動(dòng)的方向
        if tableView.contentOffset.y < -tableHeaderHeight {
            // 就是改 frame
            headerRect.origin.y = tableView.contentOffset.y
            headerRect.size.height = -tableView.contentOffset.y
        }

        headerView.frame = headerRect
    }

例子七:進(jìn)度繪制動(dòng)畫(huà)

image

用到了 CoreAnimation省古,也用到了插值。

每一段插值都是一個(gè) CoreAnimation 動(dòng)畫(huà)丧失,進(jìn)度的完成分為多次插值豺妓。

這里動(dòng)畫(huà)效果的主要用到 strokeEnd 屬性, 筆畫(huà)結(jié)束

插值的時(shí)候,要注意布讹,下一段動(dòng)畫(huà)的開(kāi)始琳拭,正是上一段動(dòng)畫(huà)的結(jié)束

// 這個(gè)用來(lái),主要的效果
    let progressLayer = CAShapeLayer()
   // 這個(gè)用來(lái)描验,附加的顏色
    let gradientLayer = CAGradientLayer()

    // 給個(gè)默認(rèn)值白嘁,外部設(shè)置
    var range: CGFloat = 128

    var curValue: CGFloat = 0 {
        didSet {
            animateStroke()
        }
    }

    func setupLayers() {

        progressLayer.position = CGPoint.zero
        progressLayer.lineWidth = 3.0
        progressLayer.strokeEnd = 0.0
        progressLayer.fillColor = nil
        progressLayer.strokeColor = UIColor.black.cgColor

        let radius = CGFloat(bounds.height/2) - progressLayer.lineWidth
        let startAngle = CGFloat.pi * (-0.5)
        let endAngle = CGFloat.pi * 1.5

        let width = bounds.width
        let height = bounds.height
        let modelCenter = CGPoint(x: width / 2, y: height / 2)
        let path = UIBezierPath(arcCenter: modelCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
        //  指定路徑
        progressLayer.path = path.cgPath

        layer.addSublayer(progressLayer)
        // 有一個(gè)漸變
        gradientLayer.frame = CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height)

        //  teal, 藍(lán)綠色

        gradientLayer.colors = [ColorPalette.teal.cgColor, ColorPalette.orange.cgColor, ColorPalette.pink.cgColor]
        gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
        gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)

        gradientLayer.mask = progressLayer // Use progress layer as mask for gradient layer.
        layer.addSublayer(gradientLayer)
    }

    func animateStroke() {
        // 前一段的終點(diǎn)
        let fromValue = progressLayer.strokeEnd
        let toValue = curValue / range

        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.fromValue = fromValue
        animation.toValue = toValue
        progressLayer.add(animation, forKey: "stroke")
        progressLayer.strokeEnd = toValue
    }

}

// 動(dòng)畫(huà)路徑,結(jié)合插值

例子八:漸變動(dòng)畫(huà)

image

這個(gè)漸變動(dòng)畫(huà)膘流,主要用到了漸變圖層 CAGradientLayerlocations 位置屬性权薯,用來(lái)調(diào)整漸變區(qū)域的分布

另一個(gè)關(guān)鍵點(diǎn)是用了圖層 CALayer 的遮罩 mask,

簡(jiǎn)單理解,把漸變圖層全部蒙起來(lái)睡扬,只露出文本的形狀盟蚣,就是那幾個(gè)字母的痕跡

class LoadingLabel: UIView {

    let gradientLayer: CAGradientLayer = {
        let gradientLayer = CAGradientLayer()

        gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
        gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)

        // 灰, 白卖怜, 灰
        let colors = [UIColor.gray.cgColor, UIColor.white.cgColor, UIColor.gray.cgColor]
        gradientLayer.colors = colors

        let locations = [0.25, 0.5, 0.75]
        gradientLayer.locations = locations as [NSNumber]?

        return gradientLayer
    }()

    // 文字轉(zhuǎn)圖片屎开,然后繪制到視圖上

    // 通過(guò)設(shè)置漸變圖層的遮罩 `mask` , 為指定文字,來(lái)設(shè)置漸變閃爍的效果

    @IBInspectable var text: String! {
          didSet {
               setNeedsDisplay()

                UIGraphicsBeginImageContextWithOptions(frame.size, false, 0)
                text.draw(in: bounds, withAttributes: textAttributes)
                let image = UIGraphicsGetImageFromCurrentImageContext()
                UIGraphicsEndImageContext()
               // 從文字中马靠,抽取圖片

                 let maskLayer = CALayer()
                 maskLayer.backgroundColor = UIColor.clear.cgColor
                 maskLayer.frame = bounds.offsetBy(dx: bounds.size.width, dy: 0)
                 maskLayer.contents = image?.cgImage

                 gradientLayer.mask = maskLayer
            }
      }

    // 設(shè)置位置與尺寸
    override func layoutSubviews() {
        gradientLayer.frame = CGRect(x: -bounds.size.width, y: bounds.origin.y, width: 2 * bounds.size.width, height: bounds.size.height)
    }

    override func didMoveToWindow() {
        super.didMoveToWindow()

        layer.addSublayer(gradientLayer)

        let gradientAnimation = CABasicAnimation(keyPath: "locations")
        gradientAnimation.fromValue = [0.0, 0.0, 0.25]
        gradientAnimation.toValue = [0.75, 1.0, 1.0]
        gradientAnimation.duration = 1.7

        // 一直循環(huán)
        gradientAnimation.repeatCount = Float.infinity
        gradientAnimation.isRemovedOnCompletion = false
        gradientAnimation.fillMode = CAMediaTimingFillMode.forwards

        gradientLayer.add(gradientAnimation, forKey: nil)
    }

}

例子九:下拉刷新動(dòng)畫(huà)

image

首先通過(guò)方法 scrollViewDidScrollscrollViewWillEndDragging 做插值

extension PullRefreshView: UIScrollViewDelegate{

    // MARK: - UIScrollViewDelegate

       func scrollViewDidScroll(_ scrollView: UIScrollView) {
           let offsetY = CGFloat(max(-(scrollView.contentOffset.y + scrollView.contentInset.top), 0.0))
           self.progress = min(max(offsetY / frame.size.height, 0.0), 1.0)

           // 做互斥的狀態(tài)管理
           if !isRefreshing {
               redrawFromProgress(self.progress)
           }
       }

       func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
           if !isRefreshing && self.progress >= 1.0 {
               delegate?.PullRefreshViewDidRefresh(self)
               beginRefreshing()
           }
       }

}

畫(huà)面中飛碟動(dòng)來(lái)動(dòng)去奄抽,是通過(guò) CAKeyframeAnimation(keyPath: "position") ,關(guān)鍵幀動(dòng)畫(huà)的位置屬性甩鳄,設(shè)置的

func redrawFromProgress(_ progress: CGFloat) {

        /* PART 1 ENTER ANIMATION */

        let enterPath = paths.start

        // 動(dòng)畫(huà)指定路徑走
        let pathAnimation = CAKeyframeAnimation(keyPath: "position")
        pathAnimation.path = enterPath.cgPath
        pathAnimation.calculationMode = CAAnimationCalculationMode.paced
        pathAnimation.timingFunctions = [CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)]
        pathAnimation.beginTime = 1e-100

        pathAnimation.duration = 1.0
        pathAnimation.timeOffset = CFTimeInterval() + Double(progress)
        pathAnimation.isRemovedOnCompletion = false
        pathAnimation.fillMode = CAMediaTimingFillMode.forwards

        flyingSaucerLayer.add(pathAnimation, forKey: nil)
        flyingSaucerLayer.position = enterPath.currentPoint

        let sizeAlongEnterPathAnimation = CABasicAnimation(keyPath: "transform.scale")
        sizeAlongEnterPathAnimation.fromValue = 0
        sizeAlongEnterPathAnimation.toValue = progress
        sizeAlongEnterPathAnimation.beginTime = 1e-100

        sizeAlongEnterPathAnimation.duration = 1.0
        sizeAlongEnterPathAnimation.isRemovedOnCompletion = false
        sizeAlongEnterPathAnimation.fillMode = CAMediaTimingFillMode.forwards

        flyingSaucerLayer.add(sizeAlongEnterPathAnimation, forKey: nil)

    }

//  設(shè)置路徑
   func customPaths(frame: CGRect = CGRect(x: 4, y: 3, width: 166, height: 74)) -> ( UIBezierPath, UIBezierPath) {

        // 兩條路徑

        let startY = 0.09459 * frame.height
        let enterPath = UIBezierPath()
        // ...
        enterPath.addCurve(to: CGPoint(x: frame.minX + 0.21694 * frame.width, y: frame.minY + 0.85855 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.04828 * frame.width, y: frame.minY + 0.68225 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.21694 * frame.width, y: frame.minY + 0.85855 * frame.height))

        enterPath.addCurve(to: CGPoint(x: frame.minX + 0.36994 * frame.width, y: frame.minY + 0.92990 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.21694 * frame.width, y: frame.minY + 0.85855 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.33123 * frame.width, y: frame.minY + 0.93830 * frame.height))
        // ...
        enterPath.usesEvenOddFillRule = true

        let exitPath = UIBezierPath()
        exitPath.move(to: CGPoint(x: frame.minX + 0.98193 * frame.width, y: frame.minY + 0.15336 * frame.height))
        exitPath.addLine(to: CGPoint(x: frame.minX + 0.51372 * frame.width, y: frame.minY + 0.28558 * frame.height))
        // ... 
        exitPath.miterLimit = 4
        exitPath.usesEvenOddFillRule = true

        return (enterPath, exitPath)
    }

}

這個(gè)動(dòng)畫(huà)比較復(fù)雜逞度,需要做大量的數(shù)學(xué)計(jì)算,還要調(diào)試妙啃,具體看文尾的 git repo.

一般這種動(dòng)畫(huà)档泽,我們用 Lottie


例子十:文本變換動(dòng)畫(huà)

image

這個(gè)動(dòng)畫(huà)有些復(fù)雜,重點(diǎn)使用了 CoreAnimation 的組動(dòng)畫(huà)揖赴,疊加了五種效果馆匿,縮放、尺寸燥滑、布局渐北、位置與透明度。

具體看文尾的 git repo.

class func animation(_ layer: CALayer, duration: TimeInterval, delay: TimeInterval, animations: (() -> ())?, completion: ((_ finished: Bool)-> ())?) {

        let animation = CLMLayerAnimation()

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) {

            var animationGroup: CAAnimationGroup?
            let oldLayer = self.animatableLayerCopy(layer)
            animation.completionClosure = completion

            if let layerAnimations = animations {
                CATransaction.begin()
                CATransaction.setDisableActions(true)
                layerAnimations()
                CATransaction.commit()
            }

            animationGroup = groupAnimationsForDifferences(oldLayer, newLayer: layer)

            if let differenceAnimation = animationGroup {
                differenceAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
                differenceAnimation.duration = duration
                differenceAnimation.beginTime = CACurrentMediaTime()
                layer.add(differenceAnimation, forKey: nil)
            }
            else {
                if let completion = animation.completionClosure {
                    completion(true)
                }
            }

        }        
    }

    class func groupAnimationsForDifferences(_ oldLayer: CALayer, newLayer: CALayer) -> CAAnimationGroup? {
        var animationGroup: CAAnimationGroup?
        var animations = [CABasicAnimation]()

        // 疊加了五種效果

        if !CATransform3DEqualToTransform(oldLayer.transform, newLayer.transform) {
            let animation = CABasicAnimation(keyPath: "transform")
            animation.fromValue = NSValue(caTransform3D: oldLayer.transform)
            animation.toValue = NSValue(caTransform3D: newLayer.transform)
            animations.append(animation)
        }

        if !oldLayer.bounds.equalTo(newLayer.bounds) {
            let animation = CABasicAnimation(keyPath: "bounds")
            animation.fromValue = NSValue(cgRect: oldLayer.bounds)
            animation.toValue = NSValue(cgRect: newLayer.bounds)
            animations.append(animation)
        }

        if !oldLayer.frame.equalTo(newLayer.frame) {
            let animation = CABasicAnimation(keyPath: "frame")
            animation.fromValue = NSValue(cgRect: oldLayer.frame)
            animation.toValue = NSValue(cgRect: newLayer.frame)
            animations.append(animation)
        }

        if !oldLayer.position.equalTo(newLayer.position) {
            let animation = CABasicAnimation(keyPath: "position")
            animation.fromValue = NSValue(cgPoint: oldLayer.position)
            animation.toValue = NSValue(cgPoint: newLayer.position)
            animations.append(animation)
        }

        if oldLayer.opacity != newLayer.opacity {
            let animation = CABasicAnimation(keyPath: "opacity")
            animation.fromValue = oldLayer.opacity
            animation.toValue = newLayer.opacity
            animations.append(animation)
        }

        if animations.count > 0 {
            animationGroup = CAAnimationGroup()
            animationGroup!.animations = animations
        }

        return animationGroup
    }

例子十一:動(dòng)態(tài)圖動(dòng)畫(huà)

image

從 gif 文件里面取出每楨圖片铭拧,算出持續(xù)時(shí)間赃蛛,設(shè)置動(dòng)畫(huà)圖片

internal class func animatedImageWithSource(_ source: CGImageSource) -> UIImage? {

        // 需要喂圖片恃锉,
        // 喂動(dòng)畫(huà)持續(xù)時(shí)間

        let count = CGImageSourceGetCount(source)

        var data: (images: [CGImage], delays: [Int]) = ([CGImage](), [Int]())

        // Fill arrays
        for i in 0..<count {
            // Add image
            if let image = CGImageSourceCreateImageAtIndex(source, i, nil) {
                data.images.append(image)
            }

            let delaySeconds = UIImage.delayForImageAtIndex(Int(i),
                source: source)
            data.delays.append(Int(delaySeconds * 1000.0)) // Seconds to ms
        }

        // Calculate full duration
        let duration: Int = {
            var sum = 0
            for val: Int in data.delays {
                sum += val
            }
            return sum
        }()

        let gcd = gcdForArray(data.delays)
        var frames = [UIImage]()

        var frame: UIImage
        var frameCount: Int
        for i in 0..<count {
            frame = UIImage(cgImage: data.images[Int(i)])
            frameCount = Int(data.delays[Int(i)] / gcd)

            for _ in 0..<frameCount {
                frames.append(frame)
            }
        }

        let animation = UIImage.animatedImage(with: frames,
            duration: Double(duration) / 1000.0)

        return animation
    }

推薦文集

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市呕臂,隨后出現(xiàn)的幾起案子淡喜,更是在濱河造成了極大的恐慌,老刑警劉巖诵闭,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炼团,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡疏尿,警方通過(guò)查閱死者的電腦和手機(jī)瘟芝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)褥琐,“玉大人锌俱,你說(shuō)我怎么就攤上這事〉谐剩” “怎么了贸宏?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)磕洪。 經(jīng)常有香客問(wèn)我吭练,道長(zhǎng),這世上最難降的妖魔是什么析显? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任鲫咽,我火速辦了婚禮,結(jié)果婚禮上谷异,老公的妹妹穿的比我還像新娘分尸。我一直安慰自己,他們只是感情好歹嘹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布箩绍。 她就那樣靜靜地躺著,像睡著了一般尺上。 火紅的嫁衣襯著肌膚如雪材蛛。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天尖昏,我揣著相機(jī)與錄音仰税,去河邊找鬼。 笑死抽诉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吐绵。 我是一名探鬼主播迹淌,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼河绽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了唉窃?” 一聲冷哼從身側(cè)響起耙饰,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纹份,沒(méi)想到半個(gè)月后苟跪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蔓涧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年件已,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片元暴。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡篷扩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出茉盏,到底是詐尸還是另有隱情鉴未,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布鸠姨,位于F島的核電站铜秆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏讶迁。R本人自食惡果不足惜羽峰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望添瓷。 院中可真熱鬧梅屉,春花似錦、人聲如沸鳞贷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)搀愧。三九已至惰聂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間咱筛,已是汗流浹背搓幌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留迅箩,地道東北人溉愁。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像饲趋,于是被迫代替她去往敵國(guó)和親拐揭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撤蟆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容