iOS: UIDynamicAnimator updateItemUsingCurrentState

2019-08-09 09:29发布

I am trying to manually change rotation of a view in UIDynamics, but the view's rotation is always reset after update. The documentation says, that UIDynamicAnimator's updateItemUsingCurrentState: method should update position and rotation of an item, but only position is updated.
I created a short example where a square view is falling from the screen and after a touch it should be positioned to the location of the touch and rotated to 45 degrees. However, no rotation happens (sometimes the rotation can be seen for a fraction of a second, but after that it is reset again):

#import "ViewController.h"

@interface ViewController ()
{
    UIDynamicAnimator* _animator;
    UIView* _square;
}
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    _square = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    _square.backgroundColor = [UIColor grayColor];
    [self.view addSubview:_square];

    _animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[_square]];
    gravityBehavior.magnitude = 0.1;
    [_animator addBehavior:gravityBehavior];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    _square.center = [touch locationInView:self.view];
    NSLog(@"transform before rotation: %@", NSStringFromCGAffineTransform(_square.transform));
    _square.transform = CGAffineTransformMakeRotation(M_PI/4);
    [_animator updateItemUsingCurrentState:_square];
    NSLog(@"transform after rotation: %@", NSStringFromCGAffineTransform(_square.transform));
}
@end

(I've left here all code, so that you can copy-paste it into a newly created project. Hopefully there isn't too much of irrelevant code to make it disturbing)

So am I doing something wrong? Or isn't it possible to explicitly change rotation of the view?

2条回答
Emotional °昔
2楼-- · 2019-08-09 09:45

My solution would be to remove the behaviour from the animator, and then recreate the behaviour with the item's modified state. Explicitly setting an item's position (through center) and rotation is not something that should really be done whilst it is under the influence of UIDynamics (like being under the influence of alcohol, but more boring).

Code for my solution:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.animator removeBehavior:self.gravityBehavior];
    self.gravityBehavior = nil;

    UITouch *touch = touches.anyObject;
    CGPoint touchLocation = [touch locationInView:touch.view];

    self.square.center = touchLocation
    self.square.transform = CGAffineTransformMakeRotation(M_PI/4);

    self.gravityBehavior = [[UIGravityBehavior alloc] initWithItem:self.square];
    self.gravityBehavior.magnitude = 0.1f;

    [self.animator addBehavior:self.gravityBehavior];
}
查看更多
贼婆χ
3楼-- · 2019-08-09 09:50

Based on Infinity James' answer I found a solution that works quite well. So to update an item's rotation you need to
1. remove all behaviors that affect the item from the dynamic animator
2. update the item's rotation
3. return the removed behaviors back to the animator (there's no need to create new behaviors)

Note: You could add all behaviors to one parent behavior as child behaviors, so you can remove and return all of them at once (but this attitude stops all the movement in dynamic animator, so you should consider also other less harming ways).

- (void)rotateItem:(UICollectionViewLayoutAttributes *)item toAngle:(CGFloat)angle
{
    CGPoint center = item.center;
    [self.dynamicAnimator removeBehavior:self.parentBehavior];
    item.transform = CGAffineTransformMakeRotation(angle);
    [self.dynamicAnimator addBehavior:self.parentBehavior];
}

This solution is a kind of compromise, since it stops movement of the item, so please add your answer if you find something better...

查看更多
登录 后发表回答