Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Observing change in frame of a UIView during animation

I want to observe changes to the x coordinate of my UIView's origin while it is being animated using animateWithDuration:delay:options:animations:completion:. I want to track changes in the x coordinate during this animation at a granular level because I want to make a change in interaction to another view that the view being animated may make contact with. I want to make that change at the exact point of contact. I want to understand the best way to do something like this at a higher level:

-- Should I use animateWithDuration:... in the completion call back at the point of contact? In other words, The first animation runs until it hits that x coordinate, and the rest of the animation takes place in the completion callback?

-- Should I use NSNotification observers and observe changes to the frame property? How accurate / granular is this? Can I track every change to x? Should I do this in a separate thread?

Any other suggestions would be welcome. I'm looking for a abest practice.

like image 750
samonderous Avatar asked Mar 22 '14 19:03

samonderous


People also ask

How does UIView animate work?

it queries the views objects for changed state and gets back the initial and target values and hence knows what properties to change and in what range to perform the changes. it calculates the intermediate frames, based on the duration and initial/target values, and fires the animation.

Does UIView animate need weak self?

You don't need to use [weak self] in static function UIView. animate() You need to use weak when retain cycle is possible and animations block is not retained by self.

Does UIView animate run on the main thread?

The contents of your block are performed on the main thread regardless of where you call [UIView animateWithDuration:animations:] . It's best to let the OS run your animations; the animation thread does not block the main thread -- only the animation block itself.


1 Answers

Use CADisplayLink since it is specifically built for this purpose. In the documentation, it says:

Once the display link is associated with a run loop, the selector on the target is called when the screen’s contents need to be updated.

For me I had a bar that fills up, and as it passed a certain mark, I had to change the colors of the view above that mark.

This is what I did:

let displayLink = CADisplayLink(target: self, selector: #selector(animationDidUpdate))
displayLink.frameInterval = 3
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)

UIView.animateWithDuration(1.2, delay: 0.0, options: [.CurveEaseInOut], animations: { 
    self.viewGaugeGraph.frame.size.width = self.graphWidth
    self.imageViewGraphCoin.center.x = self.graphWidth
    }, completion: { (_) in
        displayLink.invalidate()
})

func animationDidUpdate(displayLink: CADisplayLink) {
    let presentationLayer = self.viewGaugeGraph.layer.presentationLayer() as! CALayer

    let newWidth = presentationLayer.bounds.width

    switch newWidth {
    case 0 ..< width * 0.3: 
        break
    case width * 0.3 ..< width * 0.6: 
        // Color first mark
        break 
    case width * 0.6 ..< width * 0.9:
        // Color second mark
        break
    case width * 0.9 ... width:
        // Color third mark
        break
    default:
        fatalError("Invalid value observed. \(newWidth) cannot be bigger than \(width).")
    }
}

In the example, I set the frameInterval property to 3 since I didn't have to rigorously update. Default is 1 and it means it will fire for every frame, but it will take a toll on performance.

like image 103
funct7 Avatar answered Sep 18 '22 06:09

funct7