Using UIDynamicAnimator on a scrollview? - ios

I am trying to implement UIDynamicAnimator effects on a scrollView with many elements. I couldn't find too much information about it anywhere ,so i just need some starting point.
So i have my scrollView, with many UIViews in it. i want to make any kind of animation to its "subViews" while scrolling.
Tried that-nothing happens .
//DYNAMICS ANIMATIONS
UIDynamicAnimator *dynamic=[[UIDynamicAnimator alloc] initWithReferenceView:scroller];
UIGravityBehavior *gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:mainCellsArray];
[dynamic addBehavior:gravityBeahvior];
When mainCellsArray holds all the "subViews" of the scroller .
EDIT:
I have a strong property for the dynamic .
The array holds my costume classes pointers , that each class is a UIView subclass, and they all the scroller children's.

First, make sure you define your animator as a property, not solely as a local variable (and I tend to use animator for the name of that, to avoid confusion with the #dynamic keyword):
#property (strong, nonatomic) UIDynamicAnimator *animator;
Then instantiate the animator:
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.scrollView];
And add the gravity:
UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[viewToAnimate]];
[self.animator addBehavior:gravityBehavior];
If you want them to stop when they hit the bottom of the contentSize of the scroll view, you can't use the typical translatesReferenceBoundsIntoBoundary setting. You have to make a path yourself, e.g. something like:
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:#[viewToAnimate]];
CGRect contentSizeRect = {CGPointZero, self.scrollView.contentSize};
UIBezierPath *path = [UIBezierPath bezierPathWithRect:contentSizeRect];
[collision addBoundaryWithIdentifier:#"contentSize" forPath:path];
[self.animator addBehavior:collision];
Or, if you want them to fly off the scroll view, you probably want to remove them when they no longer intersect the contentSize of the scroll view:
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:#[viewToAnimate]];
UIGravityBehavior __weak *weakGravity = gravity;
CGRect contentSizeRect = {CGPointZero, self.scrollView.contentSize};
gravity.action = ^{
if (!CGRectIntersectsRect(contentSizeRect, viewToAnimate.frame)) {
NSLog(#"removing view");
[viewToAnimate removeFromSuperview];
[self.animator removeBehavior:weakGravity];
}
};
[self.animator addBehavior:gravity];

Related

How to set different gravitydirection vector for different items of gravitybehavior?

I have two UIViews.i want to set different gravitydirection vector for these two views for making **attraction**.and i am doing this by having two gravitybehavior but when i am adding these two to animator.
it's not working as **attracting** to each other.
also giving warning like :-Multiple gravity behavior per animator is undefined and may assert in the future.
Below is the code i am using to make Attractiuon effect b/w two views.
gravity behavior is to make the gravity animation.and animator is dynamic animator.
UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[v1]];
CGVector vector = CGVectorMake(1.0, 1.0);
[gravityBehavior setGravityDirection:vector];
UIGravityBehavior *gravityBehavior1 = [[UIGravityBehavior alloc] initWithItems:#[v2]];
CGVector vector1 = CGVectorMake(-1.0, -1.0);
[gravityBehavior1 setGravityDirection:vector1];
gravityBehavior.magnitude=1000;
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
[self.animator addBehavior:gravityBehavior];
[self.animator addBehavior:gravityBehavior1];
Please Help!
Thanks for the Help!!

UIKitDynamics when working with Interface Builder

I want to add animations to some buttons using UIKitDynamics, however doing the very very basics with a UISnapBehaviour doesn't work at all. I am using IB to layout all of my subviews, so keep in mind that I cannot reposition anything by calling self.button.frame, you have to use constraints.
That said here is what I have...When I run the animation it blinks quickly and looks terrible.
// Position the buttons off screen
self.swerveButton.translatesAutoresizingMaskIntoConstraints = NO;
self.followButton.translatesAutoresizingMaskIntoConstraints = NO;
self.swerveButtonTopSpaceConstraint.constant = -200;
CGPoint swerveSnapPoint = self.swerveButton.center;
CGPoint followSnapPoint = self.followButton.center;
UISnapBehavior *followSnap = [[UISnapBehavior alloc] initWithItem:self.followButton
snapToPoint:followSnapPoint];
UISnapBehavior *swerveSnap = [[UISnapBehavior alloc] initWithItem:self.swerveButton
snapToPoint:swerveSnapPoint];
[self.overlayView fadeInWithDuration:0.2 completion:^{
swerveSnap.damping = 0.3;
[self.animator addBehavior:swerveSnap];
followSnap.damping = 0.3;
[self.animator addBehavior:followSnap];
}];
}
UIKitDynamics doesn't work with constraints. If you want to use them you'll have to go old school and set frames manually, at least during the animation anyway.

making blocks collide with screen boundary

I'm following along with the Stanford ios7 course, chapter 8, where the instructor builds a simplified Tetris game, with colored blocks dropping from above, where you have to fill in rows. After adding gravity behavior to make the blocks fall, he adds a collision behavior (note the property below), lazily instantiates it and, while doing that, he sets the bounds like this
_collider.translatesReferenceBoundsIntoBoundary = YES;
which makes the blocks collide with the bottom of the screen (rather than falling through) so they can stack on top of each other. He then adds the collision behavior to the animator property, and, as a final step, in the drop method, he adds the dropView (which are the blocks) to the collision behavior. When he runs, the blocks hit the bottom and stack ontop of each other. When I run, using the code below, the blocks continue to fall through the bottom of the screen (on the simulator). In other words, there is no stacking.
Can you see why the collision behavior might not be working.
ViewController
#property (strong,nonatomic) UIDynamicAnimator *animator;
#property (strong, nonatomic) UIGravityBehavior *gravity;
#property (strong, nonatomic) UICollisionBehavior *collider;
#end
#implementation DropItViewController
static const CGSize DROP_SIZE = { 40, 40 };
- (IBAction)tap:(UITapGestureRecognizer *)sender {
[self drop];
}
- (UICollisionBehavior *)collider
{
if (!_collider){
_collider = [[UICollisionBehavior alloc] init];
_collider.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:_collider];
}
return _collider;
}
- (UIDynamicAnimator *)animator
{
if (!_animator) {
_animator = [[UIDynamicAnimator alloc] init];
}
return _animator;
}
-(UIGravityBehavior *)gravity
{
if (!_gravity) {
_gravity = [[UIGravityBehavior alloc] init];
[self.animator addBehavior:_gravity];
}
return _gravity;
}
Add the dropView to the collider in the drop method
-(void)drop
{
CGRect frame;
frame.origin = CGPointZero;
frame.size = DROP_SIZE;
int x = (arc4random() % (int)self.gameView.bounds.size.width) / DROP_SIZE.width;
frame.origin.x = x * DROP_SIZE.width;
UIView *dropView = [[UIView alloc] initWithFrame:frame];
dropView.backgroundColor = self.randomColor;
[self.gameView addSubview:dropView];
[self.gravity addItem:dropView];
[self.collider addItem:dropView];
}
When you instantiate your UIDynamicAnimator, use initWithReferenceView instead of init. Then when you use translatesReferenceBoundsIntoBoundary, it will know what reference bounds to use.
I'm in the same course and thought I was seeing the same issue. However, try running in the 4.0 inch simulator. Your squares are probably collecting just offscreen (outside the bounds of a 3.5 inch screen).

UIDynamics and Autolayout

Recently I used UIDynamics to animate an image view into place. However, because its autolayout y-pos constraint was set to off-screen, when navigating away from the screen and then returning to it, my image view was being placed off-screen again. The animation took about 3 seconds, so after three seconds I just reset the constraint. That feels a little hacky.
So my question is this: what is the proper way to handle autolayout and UIDynamics at the same time?
This is not really a dynamics problem. Autolayout is incompatible with any view animation, or any manual setting of the frame: when layout comes along, it is the constraints that will be obeyed. It is up to you, if you move a view manually in any way, to update the constraints to match its new position/size/whatever.
Having said that: with UIKit Dynamics, when the animation ends, the animator will pause, and the animator's delegate is notified:
https://developer.apple.com/library/ios/documentation/uikit/reference/UIDynamicAnimatorDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UIDynamicAnimatorDelegate/dynamicAnimatorDidPause:
So that is the moment to update the constraints.
You have a nice solution provided by Geppy Parziale in this tutorial.
Basically you can create an object that conforms to UIDynamicItem:
#interface DynamicHub : NSObject <UIDynamicItem>
#property(nonatomic, readonly) CGRect bounds;
#property(nonatomic, readwrite) CGPoint center;
#property(nonatomic, readwrite) CGAffineTransform transform;
#end
That needs to init the bounds or it will crash:
- (id)init {
self = [super init];
if (self) {
_bounds = CGRectMake(0, 0, 100, 100);
}
return self;
}
And then you use UIDynamics on that object and use the intermediate values to update your constraints:
DynamicHub *dynamicHub = [[DynamicHub alloc] init];
UISnapBehavior *snapBehavior = [[UISnapBehavior alloc] initWithItem:dynamicHub
snapToPoint:CGPointMake(50.0, 150.0)];
[snapBehavior setDamping:.1];
snapBehavior.action = ^{
self.firstConstraint.constant = [dynamicHub center].y;
self.secondConstraint.constant = [dynamicHub center].x;
};
[self.animator addBehavior:snapBehavior];

Ball bouncing/moving inside an invisible box ios/xcode

I'd like to make it when the user shakes the device the ball rattles around inside the object on screen. I'm assuming I need to set up an invisible box for it to collide with. It doesn't matter if it moved randomly or follows a predefined path, whichever is easiest.
I think I understand the "Activate on shake" part of the code, just the ball/object movement I'm not sure of
This should work:
//You need an #property (nonatomic, strong) UIDynamicAnimator *animator; in your .h
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.viewToBounceAroundIn];
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:#[self.viewThatBouncesAround]];
collision.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collision];
UIPushBehavior *push = [[UIPushBehavior alloc] initWithItems:#[self.viewThatBouncesAround] mode:UIPushBehaviorModeInstantaneous];
push.magnitude = 1; //Play with this, it's how much force is applied to your object
push.angle = 0; //play with this too
[self.animator addBehavior:push];
I typed this away from a compiler - let me know if it works. The idea is that you use UIKitDynamics as a physics engine, use a UICollisionBehavior to let the item bounce around inside the box, and a UIPushBehavior to apply the initial force.
If the item slows down too quickly for you, or loses too much energy when it bounces off walls, you can adjust its properties:
UIDynamicItemBehavior *behavior = [[UIDynamicItemBehavior alloc] initWithItems:#[self.itemThatBouncesAround]];
behavior.friction = 0; //no friction. play with this.
behavior.elasticity = 1;; //completely elastic, play with this.
[self.animator addBehavior:behavior];

Resources