I'm working on code for an expandable tray view that uses UIDynamicAnimator to achieve a nice expand/contract animation.
To achieve a realistic acceleration I use UIGravityBehavior
to make my tray fall, until the "tab" of the tray hits the bottom of the screen.
This works well, but even though all items in the scene have stopped moving, UIDynamicAnimatorDelegate dynamicAnimatorDidPause:
is never called. This means that the animator continues using CPU cycles to animate the scene ( the delegate is set, and fires for UIDynamicAnimatorDelegate dynamicAnimatorDidPause:
).
I tried removing the UIGravityBehavior
from the scene, which did indeed cause the animator to stop in the end. I can't time the removal of the gravity behavior right though, since I need to remove it from the scene once everything has stopped moving.
I understand that gravity is a constant force, but I still assumed it would stop the animator once everything has 0 velocity and 0 acceleration.
Is this last assumption false?
Anyone having similar problems?
You are correct that the animator should pause once everything comes to rest.
Check what items
are attached to your gravity behavior, and make sure that there aren't other items still falling. For example, it is easy to accidentally create the following bug:
In this situation, the "ghost item" will fall forever.
Another possible problem (though less likely given your description) is if your items are attached to other behaviors that are causing infinite but small "bounce." I would check the full list of behaviors on your animator (remember to check child behaviors, too). In particular I'd be interested in any UIDynamicItemBehavior
that adds elasticity
.
EDIT:
You may also want to go the other way. Start with a very basic dynamics system and add components from yours until you can reproduce the problem. For instance, the following does converge quite quickly (logging "pause"):
@interface PTLViewController () <UIDynamicAnimatorDelegate>
@property (nonatomic, strong) UIDynamicAnimator *animator;
@end
@implementation PTLViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100,100,100,100)];
view.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:view];
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
self.animator.delegate = self;
UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[view]];
collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collisionBehavior];
UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[view]];
[self.animator addBehavior:gravityBehavior];
}
- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator {
NSLog(@"pause");
}
@end
To your question about getting all item velocities, I don't know of an easy way to do that. Unfortunately, UIDynamicAnimator
doesn't directly know all of its items. This is indirectly because UIDyanamicBehavior
doesn't include an items
property. If this bothers you as much as it does me, consider duping radar://15054405.
But there is a solution if you just want to know the current linear velocity of specific items. Just add a UIDynamicItemBehavior
with a custom action to log it:
UIDynamicItemBehavior *dynamicItemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[view]];
__weak UIDynamicItemBehavior *weakBehavior = dynamicItemBehavior;
dynamicItemBehavior.action = ^{
NSLog(@"Velocity: %@", NSStringFromCGPoint([weakBehavior linearVelocityForItem:view]));
};
[self.animator addBehavior:dynamicItemBehavior];