UIDynamicKit with attachment and push behaviour causes long oscillation time - ios

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)

Related

Can't prevent the sprite from moving out of the screen

I want to keep my sprite just moving in the range of the screen. So I create a edge loop by bodyWithEdgeLoopFromRect, also the collision bit mask has been set to make them collide with each other.
static const uint32_t kRocketCategory = 0x1 << 0;
static const uint32_t kEdgeCategory = 0x1 << 6;
I use the pan recognizer to move the sprite and here is the current code that sets the properties of all the things.
- (void)didMoveToView:(SKView *)view
{
// Pan gesture
self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanFrom:)];
[self.view addGestureRecognizer:self.panRecognizer];
// Edge
self.backgroundColor = [SKColor blackColor];
self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f);
self.physicsWorld.contactDelegate = self;
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody.categoryBitMask = kEdgeCategory;
self.physicsBody.contactTestBitMask = kRocketCategory;
self.physicsBody.collisionBitMask = kRocketCategory;
self.physicsBody.usesPreciseCollisionDetection = YES;
self.physicsBody.restitution = 0;
// Rocket
SKTexture *texture = [SKTexture textureWithImageNamed:#"Rocketship-v2-1"];
texture.filteringMode = SKTextureFilteringNearest;
self.rocketSprite = [SKSpriteNode spriteNodeWithTexture:texture];
self.rocketSprite.position = CGPointMake(CGRectGetMidX(self.scene.frame), CGRectGetMaxY(self.scene.frame)*0.3); // 30% Y-axis
self.rocketSprite.name = #"rocketNode";
self.rocketSprite.xScale = 2;
self.rocketSprite.yScale = 2;
self.rocketSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.rocketSprite.size];
self.rocketSprite.physicsBody.categoryBitMask = kRocketCategory;
self.rocketSprite.physicsBody.contactTestBitMask = kEdgeCategory;
self.rocketSprite.physicsBody.collisionBitMask = kEdgeCategory;
self.rocketSprite.physicsBody.usesPreciseCollisionDetection = YES;
self.rocketSprite.physicsBody.allowsRotation = NO;
self.rocketSprite.physicsBody.restitution = 0;
[self addChild:self.rocketSprite];
}
Pan recognizer code:
- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
self.selectedRocket = self.rocketSprite;
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:recognizer.view];
translation = CGPointMake(translation.x, -translation.y);
[self panForTranslation:translation];
[recognizer setTranslation:CGPointZero inView:recognizer.view];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
}
}
- (void)panForTranslation:(CGPoint)translation
{
CGPoint position = self.selectedRocket.position;
if ([self.selectedRocket.name isEqualToString:#"rocketNode"]) {
self.selectedRocket.position = CGPointMake(position.x + translation.x, position.y);
}
}
Now the problem is when I move the sprite (rocketship) slowly, the edge will stop the sprite going out of the screen. However, when I move the sprite very quickly, the sprite will rush out the range. See the animation below. I have read some solutions to the similar problem but I still don't know what's wrong with my code. Do the edge loop and collision bit mask not enough for the situation here?
Your attempt is similar to what I've found when searching online, and having read what you've tried it looks like you've pretty much covered most of the more common problems (Not using SKAction, for instance).
I don't currently use a contact Bitmask to handle collisions on the side of my scene, but I do use a loop that actively checks positions.
Here's the method I use to reposition objects: (Repositions everything, you can specify it to move only the rocket.
-(void)repositionObjects
{
for (CXSpriteNode *i in self.sceneObjects) // CXSpriteNode is a certain subclass
{
CGPoint position = i.position;
if (position.x > self.background.size.width || position.x < 0)
{
CGPoint newPosition;
if (position.x > self.background.size.width)
{
newPosition = CGPointMake(self.background.size.width-1, position.y);
} else {
newPosition = CGPointMake(1, position.y);
}
[i runAction:[SKAction moveTo:newPosition duration:0.0f]];
}
if (position.y > self.background.size.height || position.y < 0)
{
CGPoint newPosition;
if (position.y > self.background.size.height)
{
newPosition = CGPointMake(self.background.size.height-1, 1);
} else {
newPosition = CGPointMake(position.x, 1);
}
[i runAction:[SKAction moveTo:newPosition duration:0.0f]];
}
}
}
This gets called from the SKScene loop as such:
-(void)update:(NSTimeInterval)currentTime
{
[self repositionObjects];
}
Now, it's evidently not as elegant as your desired outcome, and I have a feeling it might still induce flickering, but I'd give it a try anyways.
Also, it might be worth trying to disable/cancel the gesture once it goes out of range momentarily to stop repeated swipes that may cause flickering.
Hope this helps.
Use SpriteKit actions instead of directly modifying the position of the sprite.
What is happening is that the recognizer isn't sending updates fast enough. The sprite's bounds never intersect with the edge in any one frame.
In one update, the recognizer says that the touch is within the bounds of the edge loop. But in the next update, the touch has moved way beyond the edge loop. This leaves no frame in which the sprite's bounds intersect the edge, so no collision gets detected.
I do not use sprite kit in daily life, but you could for example solve it the hard way by adding something like this in your sprite's update method
(Pseudo code)
CGFloat x = clamp(self.position.x, minXValue, maxXValue);
CGFloat y = clamp(self.position.y, minYValue, maxYValue);
self.position = CGPointMake(x, y);

Blurry image after using UIDynamicAnimator

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.

How do I make my view that is being manipulated by UIPushBehavior not rotate as much? It's an absurd amount

Right now, I have a UIPushBehavior that I use on a view to push it off screen depending on where the user touched with a UIPanGestureRecognizer. It works, but my problem is the view rotates an absurd amount... like it's attached to the tire of a sports car.
This is how I attach the UIPushBehavior:
else if (panGestureRecognizer.state == UIGestureRecognizerStateEnded) {
[self.animator removeBehavior:self.panAttachmentBehavior];
self.pushBehavior = [[UIPushBehavior alloc] initWithItems:#[self.imageView] mode:UIPushBehaviorModeInstantaneous];
[self.pushBehavior setTargetOffsetFromCenter:centerOffset forItem:self.imageView];
self.pushBehavior.active = YES;
CGPoint velocity = [panGestureRecognizer velocityInView:self.view];
CGFloat area = CGRectGetWidth(self.imageView.bounds) * CGRectGetHeight(self.imageView.bounds);
CGFloat UIKitNewtonScaling = 1000000.0;
CGFloat scaling = area / UIKitNewtonScaling;
CGVector pushDirection = CGVectorMake(velocity.x * scaling, velocity.y * scaling);
self.pushBehavior.pushDirection = pushDirection;
[self.animator addBehavior:self.pushBehavior];
}
I don't know exactly what I should be changing to manipulate it more. Velocity and everything are perfect, I just don't want it to rotate as absurdly.
Maybe it's too late, but if you want to eliminate rotation altogether, do this:
UIDynamicItemBehavior* dynamic = [[UIDynamicItemBehavior alloc] initWithItems:#[self.imageView]];
dynamic.allowsRotation = NO;
[self.animator addBehavior:dynamic];
and the [self.animator addBehavior:self.pushBehavior];

UIAttachment not working in iOS 7

I have the following code which simply attaches a password UITextField to an anchorPoint. When I run this nothing happens.
UIDynamicItemBehavior *behavior =
[[UIDynamicItemBehavior alloc] initWithItems:#[passwordTextField]];
CGPoint anchor = passwordTextField.center;
anchor.y -= 200;
_attachment = [[UIAttachmentBehavior alloc] initWithItem:passwordTextField
attachedToAnchor:anchor];
_gravity = [[UIGravityBehavior alloc] initWithItems:#[passwordTextField]];
_gravity.magnitude = 10;
[behavior addChildBehavior:_gravity];
[behavior addChildBehavior:_attachment];
[_animator addBehavior:behavior];
The attachment doesn't change the position of the item. Instead it only makes the distance between the item and the attachment point to be fixed.
If you wish to modify the distance then try UISnapBehavior.

Detecting Touch of a UIView in UIScrollView layer with an array of UIViews

Forgive me I am kinda new at this.
I am trying to detect a touch like the MoveMe example -- only I have an array of UIViews (studentCell) put in to a NSMutableArray called studentCellArray.
[self.studentCellArray addObject:self.studentCell];
When I have one touch I want to make the program smart enough to know if it has touched any of the UIViews in the array and if it has then to do something.
Here is the code in touchesBegan: method.
//prep for tap
int ct = [[touches anyObject] tapCount];
NSLog(#"touchesBegan for ClassRoomViewController tap[%i]", ct);
if (ct == 1) {
CGPoint point = [touch locationInView:[touch view]];
for (UIView *studentCard in self.studentCellArray) {
//if I Touch a Card then I grow card...
}
NSLog(#"I am here[%#]", NSStringFromCGPoint(point));
}
I don't know how t access the views and touch them.
I "solved" this issue by assigning a UIPanGestureRecognizer to each UIView in the array.
This propbably isn't the best way to do it but I can now move them on screen.
Here is the code:
for (int x = 0; x < [keys count]; x++) {
UIPanGestureRecognizer *pGr = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(dragging:)];
UIView *sca = [self.studentCellArray objectAtIndex:x];
[sca addGestureRecognizer:pGr];
[pGr release];
}
Here is the "dragging" method I used. I have divided the screen into thirds and there is an animation to snap the UIViews to a point if it happens to cross a threshold. I hope this gives someone some good ideas. Please help if you can see any better way to do this.
- (void) dragging:(UIPanGestureRecognizer *)p{
UIView *v = p.view;
if (p.state == UIGestureRecognizerStateBegan || p.state == UIGestureRecognizerStateChanged) {
CGPoint delta = [p translationInView:studentListScrollView];
CGPoint c = v.center;
c.x += delta.x;
//c.y += delta.x;
v.center = c;
[p setTranslation:CGPointZero inView:studentListScrollView];
}
if (p.state == UIGestureRecognizerStateEnded) {
CGPoint pcenter = v.center;
//CGRect frame = v.frame;
CGRect scrollFrame = studentListScrollView.frame;
CGFloat third = scrollFrame.size.width/3.0;
if (pcenter.x < third) {
pcenter = CGPointMake(third/2.0, pcenter.y);
//pop the view
[self showModalDialog:YES perfMode:YES andControlTag:[studentCellArray indexOfObjectIdenticalTo:p.view]];
}
else if (pcenter.x >= third && pcenter.x < 2.0*third) {
pcenter = CGPointMake(3.0*third/2.0, pcenter.y);
}
else
{
pcenter = CGPointMake(5.0 * third/2.0, pcenter.y);
//pop the view
[self showModalDialog:YES perfMode:YES andControlTag:[studentCellArray indexOfObjectIdenticalTo:p.view]];
}
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.2];
v.center = pcenter;
[UIView commitAnimations];
}
}
EDIT: Adding [studentCellArray indexOfObjectIdenticalTo:p.view] to the andControlTag gives me the position in the array of the view touched so i can pass that on the my modal dialog to present the appropriate information.

Resources