【问题标题】:Swift - Color fill animationSwift - 颜色填充动画
【发布时间】:2016-06-28 12:26:58
【问题描述】:

我对 ios 动画比较陌生,我认为我为 UIView 制作动画的方法有问题。

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

有一个带有两个标签和彩色填充圆圈的 tableView 单元格

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

这是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:无效的上下文 0x0。如果要查看回溯,请设置 CG_CONTEXT_SHOW_BACKTRACE 环境变量。

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

【问题讨论】:

    标签: ios swift uitableview core-animation


    【解决方案1】:

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

    cell.pageBadgeView.drawRect(frame)
    

    来自 UIView drawRect 上的Apple docs

    当第一次显示视图或发生使视图的可见部分无效的事件时调用此方法。你永远不应该自己直接调用这个方法。要使视图的一部分无效,从而导致该部分被重绘,请改为调用 setNeedsDisplay 或 setNeedsDisplayInRect: 方法。

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

    cell.pageBadgeView.setNeedsDisplay() 
    

    您将摆脱错误并正确地看到badgeView 填充。但是这不会为它设置动画,因为默认情况下 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)
    

    【讨论】:

    • 这太棒了,但它实际上是如何工作的呢?为什么这次 animateWithDuration 完成了它的工作?为什么我不能将 animateWithDuration 放入我的回调中?尽管如此,我还是很喜欢这个解决方案!
    • @DCDC 您可以将 animateWithDuration 放入回调中。但是我选择在 BadgeView 中设置,这样无论何时设置新系数,动画在时间或选项方面都是一致的。此外,您可以查看View Programming Guide for iOS 以更深入地了解动画的工作原理以及块中可以动画的内容。
    • 是的,我现在知道了。我必须承认我有点惊讶这是多么简单。基本上,您需要做的只是动画 coeff 变量的值变化,然后调用相关方法来绘制视图。提出了一个非常简单而强大的解决方案!
    【解决方案2】:

    试试这个游乐场代码

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

    【讨论】:

    • 你怎么能在操场上看到这个?它没有显示任何视觉效果?
    • @user1007522 添加此代码:code import PlaygroundSupport ... code PlaygroundPage.current.needsIndefiniteExecution = true code PlaygroundPage.current.liveView = window 然后点击预览按钮。跨度>
    【解决方案3】:

    以上代码的更多修改,以上代码中缺少锚点代码

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-06-25
      • 2015-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多