ostrichofevil
ostrichofevil

Reputation: 734

(Swift 3) - How to include animations in custom UIView

I have a UIView subclass called View, in which I want a "ripple" to move outwards from wherever the user double clicks (as well as preforming other drawing functions). Here is the relevant part of my draw method, at present:

override func draw(_ rect: CGRect) {
    for r in ripples {
        r.paint()
    }
}

This is how r.paint() is implemented, in class Ripple:

func paint() {
    let c = UIColor(white: CGFloat(1.0 - (Float(iter))/100), alpha: CGFloat(1))
    print(iter, " - ",Float(iter)/100)
    c.setFill()
    view.fillCircle(center: CGPoint(x:x,y:y), radius: CGFloat(iter))
}

iter is supposed to be incremented every 0.1 seconds by a Timer which is started in the constructor of Ripple:

Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: move)

move is implemented as follows:

func move(_ timer: Timer) {
    while (tier<100) {
        iter += 1
        DispatchQueue.main.async {
            self.view.setNeedsDisplay()
        }
    }
    timer.invalidate()
}

What is supposed to happen is that every 0.1 seconds when the timer fires, it will increment iter and tell the View to repaint, which it will then do using the Ripple.paint() method, which uses iter to determine the color and radius of the circle.

However, as revealed using print statements, what happens instead is that the Timer fires all 100 times before the redraw actually takes place. I have tried dealing with this by replacing DispatchQueue.main.async with DispatchQueue.main.sync, but this just made the app hang. What am I doing wrong? If this is just not the right way to approach the problem, how can I get a smooth ripple animation (which involves a circle which grows while changing colors) to take place while the app can still preform other functions such as spawning more ripples? (This means that multiple ripples need to be able to work at once.)

Upvotes: 2

Views: 1058

Answers (2)

gandhi Mena
gandhi Mena

Reputation: 2293

Custom UIView like AlertView with Animation for Swift 3 and Swift 4

You can use a extension:

extension UIVIew{
  func customAlertView(frame: CGRect, message: String, color: UIColor, startY: CGFloat, endY: CGFloat) -> UIVIew{

     //Adding label to view
     let label = UILabel()
     label.frame = CGRect(x: 0, y: 0, width: frame.width, height:70)
     label.textAlignment = .center
     label.textColor = .white
     label.numberOfLines = 0
     label.text = message
     self.addSubview(label)
     self.backgroundColor = color

     //Adding Animation to view
     UIView.animate(withDuration:0.5, delay: 0, options: 
     [.curveEaseOut], animations:{
        self.frame = CGRect(x: 0, y: startY, width: frame.width, height: 64)
        }) { _ in
           UIView.animate(withDuration: 0.5, delay: 4, options: [.curveEaseOut], animations: {
                 self.frame = CGRect(x: 0, y: endY, width: frame.width, height:64)
           }, completion: {_ in
               self.removeFromSuperview()
           })
        }
     return self
  }

And you can use it un your class. This example startY is when you have a navigationController:

class MyClassViewController: UIViewController{

      override func viewDidLoad(){
         super.viewDidLoad()
         self.view.addSubView(UIView().addCustomAlert(frame: self.view.frame, message: "No internet connection", color: .red, startY:64, endY:-64))
      }
}

Upvotes: 0

Eugene Dudnyk
Eugene Dudnyk

Reputation: 6030

You can achieve what you want with CABasicAnimation and custom CALayer, like that:

class MyLayer : CALayer
{
    var iter = 0

    override class func needsDisplay(forKey key: String) -> Bool
    {
        let result = super.needsDisplay(forKey: key)
        return result || key == "iter"
    }

    override func draw(in ctx: CGContext) {
        UIGraphicsPushContext(ctx)
        UIColor.red.setFill()
        ctx.fill(self.bounds)
        UIGraphicsPopContext()
        NSLog("Drawing, \(iter)")
    }
}

class MyView : UIView
{
    override class var layerClass: Swift.AnyClass {
        get {
            return MyLayer.self
        }
    }
}

class ViewController: UIViewController {

    let myview = MyView()

    override func viewDidLoad() {
        super.viewDidLoad()

        myview.frame = self.view.bounds
        self.view.addSubview(myview)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        let animation = CABasicAnimation(keyPath: "iter")
        animation.fromValue = 0
        animation.toValue = 100
        animation.duration = 10.0
        myview.layer.add(animation, forKey: "MyIterAnimation")
        (myview.layer as! MyLayer).iter = 100
    }
}

Upvotes: 1

Related Questions