I tried to set an block on the action property, but that didn't work... any ideas? I know the UIView animation... method has a completion block, but not sure about the UIDynamicAnimations.
EDIT: adding code
[self.animator removeAllBehaviors];
UIGravityBehavior *gravityBehaviour = [[UIGravityBehavior alloc] initWithItems:#[self.onscreen]];
gravityBehaviour.gravityDirection = CGVectorMake(0, 10);
gravityBehaviour.action = ^{
if(self.onscreen.frame.origin.y > [UIScreen mainScreen].bounds.size.height)
[self.onscreen removeFromSuperview];
NSLog(#"locations is %f, height is %f", self.onscreen.frame.origin.y, [UIScreen mainScreen].bounds.size.height);
};
[self.animator addBehavior:gravityBehaviour];
UIDynamicItemBehavior *itemBehaviour = [[UIDynamicItemBehavior alloc] initWithItems:#[self.onscreen]];
[itemBehaviour addAngularVelocity:-M_PI_2 forItem:self.onscreen];
[self.animator addBehavior:itemBehaviour];
The output reflected that the view just continued to be moved even while off screen.
So I solved it by throwing one more thing in the if statement: [animator removeAllBehaviors]; that seemed to do the trick.
let dynamicBehaviour = UIDynamicItemBehavior(items: [randomWord])
weak var weakBehaviour = dynamicBehaviour
weak var weakSelf = self
dynamicBehaviour.action = {
if let currentY = weakBehaviour?.linearVelocityForItem(rndWord).y {
if currentY > CGRectGetMaxY(self.view.frame) / 2 {
weakSelf?.animator?.removeAllBehaviors()
weakSelf?.randomWord?.removeFromSuperview()
}
}
}
Related
i create UIDynamicAnimator and UIGravityBehavior add gravity to animator but gravity doesn't work. and there display my dropView.
-(UIDynamicAnimator *)animaotr
{
if(!_animator)
{
_animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.gameView];
}
return _animator;
}
-(UIGravityBehavior *)gravity
{
if(!_gravity)
{
_gravity = [[UIGravityBehavior alloc] init];
_gravity.magnitude = 1;
[self.animator addBehavior:_gravity];
}
return _gravity;
}
-(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];
}
The problem is this declaration:
-(UIDynamicAnimator *)animaotr {
That method doesn't match the name self.animator, so it won't be called, and thus your gravity behavior is not being added to any animator.
I try to implement side bar menu. I already have done it with standard UIView animations, but now i want to add some feelings to it. I decided to use UIViewDynamics. I noticed that, after main view frame changed, it is necessary to recreate UIViewAnimator and all behaviors.
I usually use layoutSubviews view's method. But in that case, my controller inherit UINavigationController and i couldn't override its view, so i worked with controller life cycle methods.
To avoid unexpected result, first i remove all animator behaviors in viewWillLayoutSubviews method. After that, at end of viewDidLayoutSubviews I create an animator and behaviors (gravity, item, push, collision).
That works fine, but when layoutSubviews triggered, it is some lags (looks like low fps).
Some code:
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
if (_animator) {
[_animator removeAllBehaviors];
_animator = nil;
}
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self configSubviewFrames];
[self configAnimator];
}
- (void)configSubviewFrames
{
CGFloat offset = [UIApplication sharedApplication].isStatusBarHidden ? 0 : _statusBarHeight;
CGRect drawerFrame, protectionFrame, unusedFrame;
CGRectDivide(self.view.frame, &drawerFrame, &unusedFrame, _drawerWidth, CGRectMinXEdge);
protectionFrame= self.view.frame;
if (!_isMenuShown) {
drawerFrame.origin.x -= _drawerWidth;
}
drawerFrame.origin.y += offset;
drawerFrame.size.height -= offset;
protectionFrame.origin.y += offset;
protectionFrame.size.height -= offset;
_drawerView.frame = drawerFrame;
_protectionView.frame = protectionFrame;
}
- (void)configAnimator
{
_animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
_collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[_drawerView]];
_itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:#[_drawerView]];
_gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[_drawerView]];
_pushBehavior = [[UIPushBehavior alloc] initWithItems:#[_drawerView] mode:UIPushBehaviorModeInstantaneous];
_itemBehavior.allowsRotation = NO;
_itemBehavior.elasticity = kDrawerElacity;
_gravityBehavior.gravityDirection = CGVectorMake(_isMenuShown ? 1.0f : -1.0f, 0.0f);
_pushBehavior.magnitude = 0.0f;
_pushBehavior.angle = 0.0f;
_collisionBehavior.collisionDelegate = self;
[_collisionBehavior setTranslatesReferenceBoundsIntoBoundaryWithInsets:UIEdgeInsetsMake(0, - _drawerWidth, 0, self.view.frame.size.width - _drawerWidth)];
[self addInititalBihaviors];
}
- (void)addInititalBihaviors
{
[_animator addBehavior:_collisionBehavior];
[_animator addBehavior:_pushBehavior];
[_animator addBehavior:_gravityBehavior];
[_animator addBehavior:_itemBehavior];
}
Hope you will help and feel free to ask any related questions.
Thanks you.
App consume 9-12 % cpu on screen rotate.
P.S.: I already tried to move animator and behaviors creation into separated thread, but:
Working with view in background thread - bad idea
It just didn't help
I'm using UIDynamicsAnimator with good results except that after the animations are done the image that have been pushed and bounced is blurry. Please help me.
#property (nonatomic,weak) IBOutlet UIImageView *someImage;
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
self.gravity = [[UIGravityBehavior alloc] initWithItems:#[self.someImage]];
CGVector gravityDirection = {-1.0, 0.5};
[self.gravity setGravityDirection:gravityDirection];
self.collision = [[UICollisionBehavior alloc] initWithItems:#[self.someImage]];
self.collision.translatesReferenceBoundsIntoBoundary = YES;
self.itemBehaviour = [[UIDynamicItemBehavior alloc] initWithItems:#[self.someImage]];
self.itemBehaviour.elasticity = 0.5;
[self.animator removeAllBehaviors]; // to avoid problems from the last behaviour animation
[self.collision addBoundaryWithIdentifier:#"barrier"
fromPoint:CGPointMake(444,0)
toPoint:CGPointMake(444,768)];
self.pushBehavior = [[UIPushBehavior alloc] initWithItems:#[self.someImage] mode:UIPushBehaviorModeInstantaneous];
self.pushBehavior.magnitude = 1.0f;
self.pushBehavior.angle = 0.0f;
[self.animator addBehavior:self.pushBehavior];
self.pushBehavior.pushDirection = CGVectorMake(1.0f, 0.0f)
self.pushBehavior.active = YES;
[self.animator addBehavior:self.itemBehaviour];
[self.animator addBehavior:self.collision];
[self.animator addBehavior:self.gravity];
I think your problem is that the image isn't on an exact point when the animation is done. I would sugest that you add a delegate and when the animation pauses you round the x and y for the image to the closest integer.
Is there anyway to get UISnapBevahiour to only work along a linear x-axis?
I want the exact behaviour of a UISnapBehaviour but don't want it to 'shake' into position on both x & y axis, just the x axis.
This is for a UIView inside my contentView of a table cell.
// UIKitDynamics
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self];
CGPoint centerPoint = self.contentView.center;
self.snapBehaviour = [[UISnapBehavior alloc] initWithItem:self.frontView snapToPoint:centerPoint];
[self.snapBehaviour setDamping:1.0f];
[self.animator addBehavior:self.snapBehaviour];
UIDynamicItemBehavior *itemBehaviour = [[UIDynamicItemBehavior alloc] initWithItems:#[self.frontView]];
itemBehaviour.elasticity = 0.0f;
itemBehaviour.allowsRotation = NO;
[self.animator addBehavior:itemBehaviour];
From UIDynamicBehavior documentation of it's action block:
#property(nonatomic, copy) void (^action)(void)
The dynamic animator calls the action block on every animation step.
So when creating your behaviour, do something like this:
UIView *view = ...
UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:view attachedToAnchor:point];
CGFloat xCoordinate = view.frame.origin.x;
attachment.action = ^{
if (view.frame.origin.x != xCoordinate) {
CGRect frame = view.frame;
frame.origin.x = xCoordinate;
view.frame = frame;
}
};
[self.dynamicAnimator addBehavior:attachment];
In my example I use a UIAttachmentBehavior but this method will also work for any other subclass of UIDynamicBehavior, in your case a UISnapBehavior.
I'll post the code I used to do a similar thing, as I mentioned in my comment it used UIView animation that leverage UIKit Dynamics underneath as opposed to using them directly...
[UIView animateWithDuration:0.5
delay:0.0
usingSpringWithDamping:0.5
initialSpringVelocity:0.3
options:UIViewAnimationOptionCurveEaseIn
animations:^{
[toView setFrame:_modalFrame];
}
completion:^(BOOL finished) {
[self.context completeTransition:YES];
}];
This presents modal view which springs up and oscillates a bit on presentation.
I am just starting to use the UIDynamic kit and have followed examples online. I have achieved the behaviour I want, which is for a view to recieved an instantaneous force in a direction and spring back to the original spot.
I achieved this using the code below
CGPoint origCenter = self.viewContentContainer.center;
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UIPushBehavior *push = [[UIPushBehavior alloc]
initWithItems:#[self.viewContentContainer] mode:UIPushBehaviorModeInstantaneous];
CGVector vector = CGVectorMake(200.0, 0.0);
push.pushDirection = vector;
CGPoint anchorpoint = origCenter;
UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:self.viewContentContainer attachedToAnchor:anchorpoint];
[attachment setFrequency:2.0];
[attachment setDamping:0.5];
[self.animator addBehavior:push];
[self.animator addBehavior:attachment];
However, it oscillates for a very long time at the end over 1 or 2 pixels. Changing the frequency and damping values doesn't seem to make this any better or worse.
Has anyone else experienced this?
Many Thanks.
Add this and it should fix it:
attachment.length = 1.0f;
It is an expected behavior, although it seems "unsightly". The above just offsets that "unsightliness".
Tested. Hope it helps.
i have the same problem. didn't solve it yet, but i have some ideas
i remove attachment behavior when UIGestureRecognizer ended,
but when i drag it still oscillates :(
-(void) handlePanGestureRecognizer:(UIPanGestureRecognizer*) gr
{
CGPoint att = [gr locationInView:self.view];
self.attachView.center = att;
if (gr.state == UIGestureRecognizerStateBegan)
{
_attachment.anchorPoint = att;
[_animator addBehavior:_attachment];
if (_snap)
[_animator removeBehavior:_snap];
}
if (gr.state == UIGestureRecognizerStateChanged)
{
_attachment.anchorPoint = att;
}
if (gr.state == UIGestureRecognizerStateEnded)
{
_snap = [[UISnapBehavior alloc] initWithItem:self.button snapToPoint:att];
[_animator addBehavior:_snap];
[_animator removeBehavior:_attachment];
}
}
and this is not pretty solution but it works as i like
-(void) handlePanGestureRecognizer:(UIPanGestureRecognizer*) gr
{
CGPoint att = [gr locationInView:self.view];
self.attachView.center = att;
if (_snap)
[_animator removeBehavior:_snap];
_snap = [[UISnapBehavior alloc] initWithItem:self.button snapToPoint:att];
[_animator addBehavior:_snap];
}
The way I got around these oscillations was by removing the behavior entirely, using animator.removeBehavior() and then re-adding the same behavior back to the animator.
Add this behaviour:
dynamic = UIDynamicItemBehavior()
dynamic.resistance = 1
self.animator.addBehavior(dynamic)