1

I'm trying to create a view where some items fall along normal gravity vector and others fall along an opposite vector

self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self];

// Normal Gravity
self.gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[ ]];
self.gravityBehavior.gravityDirection = CGVectorMake(0, 1);
[self.animator addBehavior:self.gravityBehavior];

// Inverse Gravity
self.inverseGravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[ ]];
self.inverseGravityBehavior.gravityDirection = CGVectorMake(0, -1);
[self.animator addBehavior:self.inverseGravityBehavior];

I would think then I could just add some items to one behavior and some to the other, but it appears that adding the second gravity behavior overrides the first?

    [self.gravityBehavior        addItem: ballA];
    [self.inverseGravityBehavior addItem: ballB];

Is this true, and if so is there another way to achieve this effect?

Warpling
  • 2,024
  • 2
  • 22
  • 38

1 Answers1

6

This behavior is definitely possible. However, it cannot be achieved with one animator using differing UIGravityBehaviors.

One solution would be to use two UIDynamicAnimator instances, each with its own UIGravityBehavior instance. Below is an example of a view controller that has two tap gesture recognizers attached, one for single tap, and one for double. The single will add a view that reacts to normal gravity. The double tap adds a view that reacts to inverted gravity. Obviously the code would need to be tweaked to match your specific requirements.

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) UIDynamicAnimator *animator;
@property (nonatomic, strong) UIDynamicAnimator *secondAnimator;

@property (nonatomic, strong) UIGravityBehavior *normalGravity;
@property (nonatomic, strong) UIGravityBehavior *inverseGravity;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

    self.normalGravity = [[UIGravityBehavior alloc] init];
    [self.animator addBehavior:self.normalGravity];

    self.secondAnimator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    self.inverseGravity = [[UIGravityBehavior alloc] init];
    [self.inverseGravity setAngle:self.normalGravity.angle magnitude:(-1.0 * self.normalGravity.magnitude)];
    [self.secondAnimator addBehavior:self.inverseGravity];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


- (IBAction)viewWasTapped:(id)sender {
    UIView *v = [[UIView alloc] initWithFrame:CGRectMake(30, 10, 25, 25)];
    v.backgroundColor = [UIColor redColor];
    [self.view addSubview:v];
    [self.normalGravity addItem:v];
}

- (IBAction)viewWasDoubleTapped:(id)sender {
    UIView *v = [[UIView alloc] initWithFrame:CGRectMake(90, 400, 25, 25)];
    v.backgroundColor = [UIColor greenColor];
    [self.view addSubview:v];
    [self.inverseGravity addItem:v];
}

@end

A second option would be to use a UIPushBehavior to simulate inverted gravity. Below is a similarly set up view controller, but without the second animator and using the push behavior.

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) UIDynamicAnimator *animator;

@property (nonatomic, strong) UIGravityBehavior *normalGravity;
@property (nonatomic, strong) UIPushBehavior *pushBehavior;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

    self.normalGravity = [[UIGravityBehavior alloc] init];
    [self.animator addBehavior:self.normalGravity];

    self.pushBehavior = [[UIPushBehavior alloc] initWithItems:@[] mode:UIPushBehaviorModeContinuous];
    [self.pushBehavior setAngle:self.normalGravity.angle magnitude:(-1.0 * self.normalGravity.magnitude)];
    [self.animator addBehavior:self.pushBehavior];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


- (IBAction)viewWasTapped:(id)sender {
    UIView *v = [[UIView alloc] initWithFrame:CGRectMake(30, 10, 25, 25)];
    v.backgroundColor = [UIColor redColor];
    [self.view addSubview:v];
    [self.normalGravity addItem:v];
}

- (IBAction)viewWasDoubleTapped:(id)sender {
    UIView *v = [[UIView alloc] initWithFrame:CGRectMake(90, 400, 25, 25)];
    v.backgroundColor = [UIColor greenColor];
    [self.view addSubview:v];

    [self.pushBehavior addItem:v];
}

@end

In all situations be sure you are managing which items are being impacted by the UIDynamicAnimator and the various behaviors so that you are not constantly adding to them and bogging the simulation down.

Andrew Monshizadeh
  • 1,784
  • 11
  • 16
  • This is a great answer. Unfortunately I wasn't explicitly clear in my question: what I really wanted was one `UIDynamicsAnimator` for animating collisions of all items, with half the items being pulled in opposite directions. I think this could be solved adequately with the later solution though? Even if it is not ideal I think this answers the question. – Warpling Jan 16 '15 at 17:32
  • I've been meaning to test this and comment back but have not found the time—so sorry for the delay! – Warpling Jan 16 '15 at 17:33
  • @Warpling yes, the second option should handle collisions correctly. It is effectively just applying an force opposite to the direction of gravity and using the same animator. – Andrew Monshizadeh Jan 16 '15 at 18:59
  • Would you say that the `UIGravityBehavior` is a specific `UIPushBehavior`? – Warpling Jan 16 '15 at 19:06
  • Conceptually, yes. But I do not know if that is how it is implemented. The inheritance tree for `UIGravityBehavior` does not include `UIPushBehavior`. – Andrew Monshizadeh Jan 16 '15 at 22:30