Swift - 颜色填充动画

Swift - Color fill animation

我对 ios 动画比较陌生,我认为我对 UIView.

进行动画处理的方法有问题

我将从 UI 屏幕截图开始,以更准确地描述我的问题:

有一个带有两个标签和彩色实心圆的 tableView 单元格

每当我向单元格引入新值时,我都想为这个最左边的灯泡设置动画,使其看起来像是充满了红色。

这是od BadgeView的实现,基本上就是前面提到的最左边的实心圆圈

class BadgeView: UIView {

var coeff:CGFloat = 0.5

override func drawRect(rect: CGRect) {
    let topRect:CGRect = CGRectMake(0, 0, rect.size.width, rect.size.height*(1.0 - self.coeff))
    UIColor(red: 249.0/255.0, green: 163.0/255.0, blue: 123.0/255.0, alpha: 1.0).setFill()
    UIRectFill(topRect)

    let bottomRect:CGRect = CGRectMake(0, rect.size.height*(1-coeff), rect.size.width, rect.size.height*coeff)
    UIColor(red: 252.0/255.0, green: 95.0/255.0, blue: 95.0/255.0, alpha: 1.0).setFill()
    UIRectFill(bottomRect)
            self.layer.cornerRadius = self.frame.height/2.0
    self.layer.masksToBounds = true
   }
}

这是我实现不均匀填充的方法 - 我引入了我在 viewController 中修改的系数。

在我的 cellForRowAtIndexPath 方法中,我尝试使用带回调的自定义按钮为这个形状制作动画

let btn:MGSwipeButton = MGSwipeButton(title: "", icon: img, backgroundColor: nil, insets: ins, callback: {
        (sender: MGSwipeTableCell!) -> Bool in
        print("Convenience callback for swipe buttons!")
        UIView.animateWithDuration(4.0, animations:{ () -> Void in
            cell.pageBadgeView.coeff = 1.0
            let frame:CGRect = cell.pageBadgeView.frame
            cell.pageBadgeView.drawRect(frame)
        })
        return true
        }) 

但它除了打印到控制台外什么都不做

: CGContextSetFillColorWithColor: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.

虽然我很想知道正确的答案和方法,但出于教育目的,如果知道为什么此代码不起作用,那就太好了。 提前致谢

试试这个 playground 代码

import UIKit
import CoreGraphics

var str = "Hello, playground"

class BadgeView: UIView {

    var coeff:CGFloat = 0.5

    func drawCircleInView(){
    // Set up the shape of the circle
        let size:CGSize = self.bounds.size;
        let layer = CALayer();
        layer.frame = self.bounds;
        layer.backgroundColor = UIColor.blue().cgColor



        let initialRect:CGRect = CGRect.init(x: 0, y: size.height, width: size.width, height: 0)

        let finalRect:CGRect = CGRect.init(x: 0, y: size.height/2, width: size.width, height: size.height/2)

        let sublayer = CALayer()
        sublayer.frame = initialRect
        sublayer.backgroundColor = UIColor.orange().cgColor
        sublayer.opacity = 0.5


        let mask:CAShapeLayer = CAShapeLayer()
        mask.frame = self.bounds
        mask.path = UIBezierPath(ovalIn: self.bounds).cgPath
        mask.fillColor = UIColor.black().cgColor
        mask.strokeColor = UIColor.yellow().cgColor

        layer.addSublayer(sublayer)
        layer.mask = mask

        self.layer.addSublayer(layer)

        let boundsAnim:CABasicAnimation  = CABasicAnimation(keyPath: "bounds")
        boundsAnim.toValue = NSValue.init(cgRect:finalRect)

        let anim = CAAnimationGroup()
        anim.animations = [boundsAnim]
        anim.isRemovedOnCompletion = false
        anim.duration = 3
        anim.fillMode = kCAFillModeForwards
        sublayer.add(anim, forKey: nil)
    }
}

var badgeView:BadgeView = BadgeView(frame:CGRect.init(x: 50, y: 50, width: 50, height: 50))
var window:UIWindow = UIWindow(frame: CGRect.init(x: 0, y: 0, width: 200, height: 200))
window.backgroundColor = UIColor.red()
badgeView.backgroundColor = UIColor.green()
window.becomeKey()
window.makeKeyAndVisible()
window.addSubview(badgeView)
badgeView.drawCircleInView()

问题的错误部分似乎是这部分代码:

cell.pageBadgeView.drawRect(frame)

来自 Apple docs on UIView drawRect:

This method is called when a view is first displayed or when an event occurs that invalidates a visible part of the view. You should never call this method directly yourself. To invalidate part of your view, and thus cause that portion to be redrawn, call the setNeedsDisplay or setNeedsDisplayInRect: method instead.

因此,如果您将代码更改为:

cell.pageBadgeView.setNeedsDisplay() 

您将摆脱错误并正确看到 badgeView filled。然而,这不会为它设置动画,因为默认情况下 drawRect 不可设置动画。

解决您问题的最简单方法是让 BadgeView 具有填充颜色的内部视图。我会这样重构 BadgeView:

class BadgeView: UIView {
    private let fillView = UIView(frame: .zero)

    private var coeff:CGFloat = 0.5 {
        didSet {
            // Make sure the fillView frame is updated everytime the coeff changes
            updateFillViewFrame()
        }
    }

    // Only needed if view isn't created in xib or storyboard
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    // Only needed if view isn't created in xib or storyboard
    override init(frame: CGRect) {
        super.init(frame: frame)

        setupView()
    }

    override func awakeFromNib() {
        setupView()
    }

    private func setupView() {
        // Setup the layer
        layer.cornerRadius = bounds.height/2.0
        layer.masksToBounds = true

        // Setup the unfilled backgroundColor
        backgroundColor = UIColor(red: 249.0/255.0, green: 163.0/255.0, blue: 123.0/255.0, alpha: 1.0)

        // Setup filledView backgroundColor and add it as a subview
        fillView.backgroundColor = UIColor(red: 252.0/255.0, green: 95.0/255.0, blue: 95.0/255.0, alpha: 1.0)
        addSubview(fillView)

        // Update fillView frame in case coeff already has a value
        updateFillViewFrame()
    }

    private func updateFillViewFrame() {
        fillView.frame = CGRect(x: 0, y: bounds.height*(1-coeff), width: bounds.width, height: bounds.height*coeff)
    }

    // Setter function to set the coeff animated. If setting it not animated isn't necessary at all, consider removing this func and animate updateFillViewFrame() in coeff didSet
    func setCoeff(coeff: CGFloat, animated: Bool) {
        if animated {
            UIView.animate(withDuration: 4.0, animations:{ () -> Void in
                self.coeff = coeff
            })
        } else {
            self.coeff = coeff
        }
    }
}

在您的按钮回调中,您只需要做:

cell.pageBadgeView.setCoeff(1.0, animated: true)

对以上代码进行更多修改,以上代码中缺少锚点代码

   ```
    var str = "Hello, playground"

 class BadgeView: UIView {

var coeff:CGFloat = 0.7

func drawCircleInView(){
    // Set up the shape of the circle
    let size:CGSize = self.bounds.size;

    let layerBackGround = CALayer();
    layerBackGround.frame = self.bounds;
    layerBackGround.backgroundColor = UIColor.blue.cgColor
   self.layer.addSublayer(layerBackGround)


    let initialRect:CGRect = CGRect.init(x: 0, y: size.height , width: size.width, height: 0)

    let finalRect:CGRect = CGRect.init(x: 0, y: 0, width: size.width, height:  size.height)

    let sublayer = CALayer()
    //sublayer.bounds = initialRect
    sublayer.frame = initialRect
    sublayer.anchorPoint = CGPoint(x: 0.5, y: 1)
    sublayer.backgroundColor = UIColor.orange.cgColor
    sublayer.opacity = 1


    let mask:CAShapeLayer = CAShapeLayer()
    mask.frame = self.bounds
    mask.path = UIBezierPath(ovalIn: self.bounds).cgPath
    mask.fillColor = UIColor.black.cgColor
    mask.strokeColor = UIColor.yellow.cgColor

    layerBackGround.addSublayer(sublayer)
    layerBackGround.mask = mask

    self.layer.addSublayer(layerBackGround)

    let boundsAnim:CABasicAnimation  = CABasicAnimation(keyPath: "bounds")
    boundsAnim.toValue = NSValue.init(cgRect:finalRect)

    let anim = CAAnimationGroup()
    anim.animations = [boundsAnim]
    anim.isRemovedOnCompletion = false
    anim.duration = 1
    anim.fillMode = kCAFillModeForwards
    sublayer.add(anim, forKey: nil)
}