I am trying my hand at UIKit Dynamics and am trying to create a simple animation with a ball that bounces off of the screen boundaries.
As shown in the code below, when I add the gravity behavior the ball seems to go off the screen as expected. But when I add the collision behavior, nothing happens. The ball stays as is at the center of the screen where I have drawn it initially.
I also trie to add a push behavior with continuous push force option, but still the ball does not move.
#implementation TAMViewController
UIDynamicAnimator* animator;
UIGravityBehavior* gravity;
UICollisionBehavior* collision;
UIPushBehavior* push;
- (void)viewDidLoad
{
[super viewDidLoad];
TAMBouncyView *ballView = [[TAMBouncyView alloc] initWithFrame:CGRectMake(0,0,
self.view.frame.size.width,
self.view.frame.size.height)];
[self.view addSubview:ballView];
animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
gravity = [[UIGravityBehavior alloc] initWithItems:#[ballView]];
[animator addBehavior:gravity];
collision = [[UICollisionBehavior alloc] initWithItems:#[ballView]];
[collision setTranslatesReferenceBoundsIntoBoundary:YES];
[animator addBehavior:collision];
push = [[UIPushBehavior alloc] initWithItems:#[ballView]
mode:UIPushBehaviorModeContinuous];
[animator addBehavior:push];
// Do any additional setup after loading the view, typically from a nib.
}
Related
I'm trying to use UIKit Dynamics to implement swipe-to-delete on my UICollectionViewCells. Things aren't going as planned, though. Here's the code in UICollectionViewCell's awakeFromNib method:
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.contentView];
UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[self.contentView]];
[collisionBehavior setTranslatesReferenceBoundsIntoBoundaryWithInsets:UIEdgeInsetsMake(0, 0, 0, -20)];
[self.animator addBehavior:collisionBehavior];
self.gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[self.containerView]];
self.gravityBehavior.gravityDirection = CGVectorMake(0, 0);
[self.animator addBehavior:self.gravityBehavior];
self.pushBehavior = [[UIPushBehavior alloc] initWithItems:#[self.containerView] mode:UIPushBehaviorModeInstantaneous];
self.pushBehavior.magnitude = 0.0f;
self.pushBehavior.angle = 0.0f;
[self.animator addBehavior:self.pushBehavior];
UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:#[self.containerView]];
itemBehavior.elasticity = 0.45f;
[self.animator addBehavior:itemBehavior];
But this is not working so well. This is what happens at launch with the code above:
That behavior seems to be mostly generated by UICollisionBehavior. If I comment it out, the container view with everything that's not the red background doesn't animate, but does show up offset a few points to the left.
Am I right to try to implement this within UICollectionViewCell? What's the right way of doing this?
Thanks!
Never mind. It was pretty much a typo. The collision behavior was attached to the wrong object.
The right code should be
UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[self.containerView]];
[collisionBehavior setTranslatesReferenceBoundsIntoBoundaryWithInsets:UIEdgeInsetsMake(0, 0, 0, -20)];
[self.animator addBehavior:collisionBehavior];
I'm new to the iOS UI development and I need some help to figure out, what is the best way to implement image belt (sequence of images in horizontal belt). I don't need details (someone to write me the code), just guidance what I should focus on learning as a libraries and most important design patterns (by design patters I mean software design for iOS UI).
The task details:
1. I will have image belt - sequence of few related images. This will be my horizontal transition. The transition animation will be simple push animation (something like push CATransion), initiated by swipe left/right. On top of the images there will be overlay with some text, icon info and etc. The overlay info will be the same for all images in the current belt.
2. I will have multiple image belts - this will be my vertical transition. The transition between belts will be triggered by swipe up/down. The different belts will contain unrelated information, so the information shown in overlays from point 1 must be also changed. The animation will be also simple push animation as in point 1.
I probably should inherit UIView and implement my belt as one object, which will consist of different UIView(overlay) and UIImageViews(for the images). Am I on the right track?
Some helpful brainstorm will be highly appreciated.
Yes - I've done many of these. You add a UIView above the top view. I usually use a full-screen one with some alpha so they can see through the core content a little. Then add stuff on top of that which won't change: things like your text, icons, etc... Then on top of that you put the image you want to swipe and add a swipe gesture recognizer, typically one for left and one for right. When they swipe you init a new UIView off the edge of the device, init it with the image, and animate the frames of both left or right, depending on which way they've swiped. Mine are help tutorials showing how to use my app step-by-step so I also init multi-part animations on some of the frames that fly in; that's a nice looking optional effect. Here's the one I use (with the animation support stripped out or it'd be too much code) and there's lots of variations. Edited to note my app doesn't support rotation but if yours does you have to adjust the frames after a rotation.
- (void)init
{
currentPanelNumber = 10;
self = [super initWithNibName:nil bundle:nil];
if (self)
{
[self.view setBackgroundColor:[UIColor clearColor]];
currentPanelNumber = 10;
overlay = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
overlay.backgroundColor = [UIColor colorWithRed:.9 green:.9 blue:.9 alpha:.9];
[self.view addSubview:overlay];
CGRect currentPanelFrame = CGRectMake(267, 175, 494, 444);
currentPanel =
[[HelpView alloc] initWithFrame:currentPanelFrame withImage:[UIImage imageNamed:#"10"]];
currentPanel.frame = currentPanelFrame;
[overlay addSubview:currentPanel];
// ----------------------------------
// Gesture swipes go here
swipeLeftRecognizer =
[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeLeft:)];
[swipeLeftRecognizer setDirection:UISwipeGestureRecognizerDirectionLeft];
[overlay addGestureRecognizer:swipeLeftRecognizer];
swipeRightRecognizer =
[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeRight:)];
[swipeRightRecognizer setDirection:UISwipeGestureRecognizerDirectionRight];
[overlay addGestureRecognizer:swipeRightRecognizer];
swipeDownRecognizer =
[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeDown:)];
[swipeDownRecognizer setDirection:UISwipeGestureRecognizerDirectionDown];
[overlay addGestureRecognizer:swipeDownRecognizer];
UITapGestureRecognizer *tapRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[overlay addGestureRecognizer:tapRecognizer];
skipIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"SkipAndStartIcon"]];
CGSize size = CGSizeMake(84, 124);
skipIcon.frame = CGRectMake((self.view.frame.size.width/2)-(size.width/2), self.view.frame.size.height-size.height-25, size.width, size.height);
[self.view addSubview:skipIcon];
}
- (void)goNext
{
// Clear out all subviews to remove lingering animations
[[currentPanel subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
if(currentPanelNumber>=80)
{
[self closeHelp];
return;
}
NSString *nextImage = [NSString stringWithFormat:#"%i", currentPanelNumber+10];
CGRect nextPanelFrame = CGRectMake(765, 175, 494, 444);
nextPanel = [[HelpView alloc] initWithFrame:nextPanelFrame withImage:[UIImage imageNamed:nextImage]];
nextPanel.frame = nextPanelFrame;
[overlay addSubview:nextPanel];
[UIView animateWithDuration:.2 animations:^
{
nextPanel.frame = currentPanel.frame;
}
completion:^(BOOL finished)
{
currentPanel.image = nextPanel.image;
nextPanel.alpha = 0;
currentPanelNumber += 10;
}
- (void)goPrevious
{
if(currentPanelNumber<=10)
{
return;
}
// Clear out all subviews to remove lingering animations
[[currentPanel subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
NSString *nextImage = [NSString stringWithFormat:#"%i", currentPanelNumber-10];
CGRect nextPanelFrame = CGRectMake(-230, 175, 494, 444);
// nextPanel = [[UIImageView alloc] initWithImage:[UIImage imageNamed:nextImage]];
nextPanel = [[HelpView alloc] initWithFrame:nextPanelFrame withImage:[UIImage imageNamed:nextImage]];
nextPanel.frame = nextPanelFrame;
[overlay addSubview:nextPanel];
[UIView animateWithDuration:.2 animations:^
{
nextPanel.frame = currentPanel.frame;
}
completion:^(BOOL finished)
{
currentPanel.image = nextPanel.image;
nextPanel.alpha = 0;
currentPanelNumber -= 10;
}];
}
Presenting a UIView from under the nav bar with "bounce effect" self.dropDownBar
Despite setting up boundaries the UIView continues to drop off the bottom of the screen
Checked here https://developer.apple.com/library/ios/documentation/uikit/reference/UICollisionBehavior_Class/Reference/Reference.html#//apple_ref/occ/instm/UICollisionBehavior/addBoundaryWithIdentifier:fromPoint:toPoint:
That states:
addBoundaryWithIdentifier:fromPoint:toPoint:
p1
The starting point for the boundary line segment.
p2
The ending point for the boundary line segment. Seem to be doing that,
and here Collision not working between items (no accepted answer and nothing suitable)
.h
<UICollisionBehaviorDelegate>
#property (strong, nonatomic) UIDynamicAnimator *animator;
.m
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UIGravityBehavior* gravityBehavior =
[[UIGravityBehavior alloc] initWithItems:#[self.dropDownBar]];
[self.animator addBehavior:gravityBehavior];
UICollisionBehavior* collisionBehavior =
[[UICollisionBehavior alloc] initWithItems:#[self.dropDownBar]];
//To - from boundries
[collisionBehavior addBoundaryWithIdentifier:#"NotificationBoundary" fromPoint:CGPointMake(0, 45)
toPoint:CGPointMake(0, 90)];
[self.animator addBehavior:collisionBehavior];
What am I missing here?
I have a UIVIew (container) and another UIView (box) the box view is inside of the ContainerView. When a UIButton is pressed I would like the box view to drop down off the bottom of the screen, and bounce with 10px left; then once the bouncing has stopped I still want the box to have 10px showing. Here is some sample code of from another question:
UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self]; //self is the container
UIGravityBehavior* gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[box]];
[animator addBehavior:gravityBehavior];
UICollisionBehavior* collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[reportBar.bar]];
collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
[animator addBehavior:collisionBehavior];
UIDynamicItemBehavior *elasticityBehavior =
[[UIDynamicItemBehavior alloc] initWithItems:#[box]];
elasticityBehavior.elasticity = 0.7f;
[animator addBehavior:elasticityBehavior];
The code is running when it should but the box isn't dropping.
Edit 1:
Self refers to the container UIView
I have also tried changing self for the currentViewController.view, no change.
Edit 2:
all of this code is inside the container view implementation file.
give a try to make animator property,
#property UIDynamicAnimator *animator;
and then your code,
_animator = [[UIDynamicAnimator alloc] initWithReferenceView:self];
...
I think you didn't specify the correct reference view. It should be either self.view or self.containerView
UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self];
Update
I think you should put the code in the ViewController for this scene and as #BaSha suggested create a animator property and after a button click you will add the behavior and will reference self.containerView
Just make sure that boxView is inside of the containerView
The following code snippet can provide BOUNCE EFFECT on your views.
CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:#"position.y"];</br>
[animation setFromValue:[NSNumber numberWithFloat:y-position1]];
[animation setToValue:[NSNumber numberWithFloat:y-position2]];
[animation setDuration:.7]; // time gap between the bounces
animation.repeatCount=500; // no:of times bounce effect has to be done
[YOURVIEW.layer addAnimation:animation forKey:#"somekey"];
I've run into what I believe is a bug with UICollisionBehavior in UIKit. Adding them to an array of UIViews leads to a memory leak. I put together a simple demo project that creates 10 animations of a group of views falling with gravity applied, and a collision with the enclosing view's bounds. (Code below.) The Leaks template in Instruments reports nine 64-byte leak for each run.
- (void)doAnimation
{
self.animateButton.enabled = NO;
CGFloat left = 12.0f;
NSMutableArray *items = [NSMutableArray new];
// set up an array of views and add them to the superview
while (left < self.view.bounds.size.width - 12.0f) {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(left, 70, 32, 32)];
left += 34.0f;
[self.view addSubview:view];
view.backgroundColor = [UIColor grayColor];
[items addObject:view];
}
// create a gravityBehavior and initialize with views array
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:items];
[self.animator addBehavior:gravity];
// create a collisionBehavior and initialize with views array
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:items];
collision.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collision];
}
// UIDynamicAnimatorDelegate method that's called when collision animation is complete
- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator
{
// get a collision behavior in order to access its items for loop below
UICollisionBehavior *behavior;
for (UIDynamicBehavior *oneBehavior in animator.behaviors) {
if ([oneBehavior isKindOfClass:[UICollisionBehavior class]]) {
behavior = (UICollisionBehavior *)oneBehavior;
break;
}
}
// reset the UIDynamicAnimator property's behaviors for next run
[self.animator removeAllBehaviors];
self.dropCount++;
// remove all subviews
for (UIView *view in behavior.items) {
[view removeFromSuperview];
}
// run the animation again or break
if (self.dropCount < 10) {
[self doAnimation];
} else {
self.animateButton.enabled = YES;
}
}
I'd really like to be able to implement collisions in an app I'm working on, but this leak makes it unusable. I have tried saving the collisionBehavior in property and reusing it. That prevents leaking all but one 64-byte chunk of memory, but the collisions no longer work when that's done. Can anyone suggest a workaround that works?
I ran into the same issue but fixed the memory leak by setting the boundaries using UICollisionBehavior's
addBoundaryWithIdentifier
Unfortunately setting boundaries with translatesReferenceBoundsIntoBoundary and setTranslatesReferenceBoundsIntoBoundaryWithInsets caused leaks for me.