Observing change in frame of a UIView during anima

2019-01-18 06:07发布

问题:

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.

回答1:

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.



回答2:

create a NSTimer with some delay and run particular selector after each time lapse.
In that method check the frame of animating view and compare it with your colliding view.

And make sure you use presentationLayer frame because if you access view.frame while animating, it gives the destination frame which is constant through out the animation.

CGRect animationViewFrame= [[animationView.layer presentationLayer] frame];

If you don't want to create timer, write a selector which calls itself after some delay.Have delay around .01 seconds.

CLARIFICATION->
Lets say you have a view which you are animating its position from (0,0) to (100,100) with duration of 5secs. Assume you implemented KVO to the frame of this view

When you call the animateWithDuration block, then the position of the view changes directly to (100,100) which is final value even though the view moves with intermediate position values.

So, your KVO will be fired one time at the instant of start of animation. Because, layers have layer Tree and Presentation Tree. While layer tree just stores destination values while presentation Layer stores intermediate values.
When you access view.frame it will always gives the value of frame in layer tree not the intermediate frames it takes.

So, you had to use presentation Layer frame to get intermediate frames.

Hope this helps.



回答3:

UIDynamics and collision behaviours would be worth investigating here. You can set a delegate which is called when a collision occurs.

See the collision behaviour documentation for more details.