How to Change Speed while Animation CABasicAnimation - objective-c

In my application i am using CABasicAnimation for animation. I want to change the speed of the animation dynamically so i have added one slider to change the speed. Following is my animation code. But i am not able to change the speed, when i change the value of speed nothing happens.
CABasicAnimation * a = [CABasicAnimation animationWithKeyPath:#"position"];
[a setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
CGPoint startPt = CGPointMake(self.view.bounds.size.width + displayLabel.bounds.size.width / 2,
displayLabel.frame.origin.y);
CGPoint endPt = CGPointMake(displayLabel.bounds.size.width / -2, displayLabel.frame.origin.y);
[a setFromValue:[NSValue valueWithCGPoint:startPt]];
[a setToValue:[NSValue valueWithCGPoint:endPt]];
[a setAutoreverses:NO];
[a setDuration:speeds];
[a setRepeatCount:HUGE_VAL];
[displayLabel.layer addAnimation:a forKey:#"rotationAnimation"];
- (IBAction)speedSlider:(id)sender {
speeds = slider.value;
}

I think the best way to change speed is change your layer's time system
displayLabel.layer.timeOffset =
[displayLabel.layer convertTime:CACurrentMediaTime() fromLayer:nil];
displayLabel.layer.beginTime = CACurrentMediaTime();
displayLabel.layer.speed= slider.value;
You can see this for advance. https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreAnimation_guide/AdvancedAnimationTricks/AdvancedAnimationTricks.html#//apple_ref/doc/uid/TP40004514-CH8-SW2

EDIT: It looks like you will have a further problem, though: it doesn't look like you can change values like that on a running animation. You will have to remove the current animation and add a new one with the altered value. This may need a bit of care to prevent a jarring effect when you add the new animation.
From the thread above, you might be able to do this by not repeating the animation, but by using a delegate (see here) to keep re-adding the animation, and setting the new speed for the next animation cycle instead.
Original post:
You are changing the value that you had originally passed in to the animation. This isn't going to affect the running animation. You'll need to get a reference to that, and change the duration property of the animation object. Something like this in your action method:
CABasicAnimation *a = [displayLabel.layer animationForKey:#"rotationAnimation"];
a.duration = slider.value;

I think jrturton is correct that you can't change properties on an animation that is already running. But what you could do is break the animation into short segments and change the speed for the next segment when the slider value changes.
Instead of animating from point A to point D, you'd animate from A-B, then B-C, then C-D. Use the parent class's animationDidStop to check the current point, check the slider value, and kick off the next animation.
This might produce jerky motion, but if you use very small segments, you might be able to smooth it out.

u should stop the animation and restart a new with a new duration time
but remember to log down the fromValue and the toValue , and use the old toValue as the new fromValue to perform a seamless change

set speed as what you need.
a.duration=0.5;
Try this...

If you just want Autoscrolling text then you can also use one class
http://blog.stormyprods.com/2009/10/simple-scrolling-uilabel-for-iphone.html
It might also work in your case, try it.

Related

Creating a Jump action

I'm working in spritekit/IOS where I have a button that runs an action when it is clicked. The button works fine, but my action is terrible. On click I run this action.
actionMove=[SKAction moveTo:CGPointMake(300,300)duration:2.0];
Which then causes my sprite does a spinning flying kick too that location. I was needed a little help cause I tried replacing the y axis with
_sprite.position+10
But it gives me an error.
Thanks
What you need is the action moveByX:y:duration:...
Sort of.
You can do this...
SKAction *rise = [SKAction moveByX:0 y:10 duration:1];
SKAction *fall = [SKAction moveByX:0 y:-10 duration:1];
SKAction *jump = [SKAction sequence:#[rise, fall]];
Then you can run the jump action on your sprite.
This will move the sprite up by 10 points over 1 second and then down by 10 points over 1 second.
However, it will not look like a jump. It will not accelerate or anything.
If you are including something like a jump in your game then you would normally want to have some sort of collision detection with the floor and use forces to move your sprite instead of actions.
Collision detection method
Sprite Kit Physics Engine
There are two general ways you could do this. The easiest (although longest learning curve) is to use SKPhysicsBody and the built in physics engine in SpriteKit.
You would add a SKPhysicsBody to your character and a boundary SKPhysicsBody to the floor.
Then to make the character jump you would use something like...
[character.physicsBody applyImpulse:CGVectorMake(0, 1)];
This will apply an immediate force to the character so it "jumps" in the air. Then with the physics engine you have built in gravity and so the character will start to slow down and fall until it hits the floor again.
There is a bit of setup for this (less than you would think) but once the setup is done the rest is VERY VERY VERY simple.
Manually
To do it manually is another case. This requires no setup but a LOT of maths. The way this works is to use the update: function and then each update cycle calculate what the velocity and position of the character is and to update the position accordingly.
You really don't want to do this though.
Make things really easy for yourself by adding SKPhysicsBody to the things that need to collide (the character and the floor) and then exploit the gravity and applyImpulse. It will make it much easier, it will perform faster and it will look a million times better :D
There you go :
CGPoint pos = _sprite.position;
pos.y = pos.y + 10;
_sprite.position = pos;
If you are going to use force (and physics) to move your player then using impulse will give you better results:
_sprite.physicsBody applyImpulse:CGVectorMake(0, <value>);
The above depends on a number of factors, including gravity value and the mass of the _sprite.physicsBody. Experimenting is the best way to figure out what works here.
If you are not using physics you can use the followPath action and easing to get a somewhat realistic jumping effect (similar to cocos2d's jump to action). A simple example of how this can be implemented as a category method on SKAction:
+ (SKAction *)jumpWithStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint height:(CGFloat)height {
UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
[bezierPath moveToPoint:startPoint];
CGPoint peak = CGPointMake(endPoint.x - startPoint.x, startPoint.y + height);
[bezierPath addCurveToPoint:endPoint controlPoint1:peak controlPoint2:peak];
SKAction *jumpAction = [SKAction followPath:bezierPath.CGPath asOffset:NO orientToPath:NO duration:.5f];
jumpAction.timingMode = SKActionTimingEaseIn;
return jumpAction;
}

Pause a CCMoveBy Cocos2d 2.0 ios

im developing a game where i have time to finish the level, during wich i have a filling bar animation, on that particular animation im using CCMoveBy:
self.animatedBar = [CCMoveBy actionWithDuration:time position: ccp(12, -20)];
CCNode* animatedContainer = [self getChildByTag:1];
[animatedContainer runAction:self.animatedBar];
is there any way to say something like [animatedBar pause] and [animatedBar resume] ?
Or the best bet is to put this into my game loop and pause it there? ( im doing a return if the BOOL paused is set to true ).
I will have more animations attached to this on the future ( not game core related, but just to make it more "shinny" ) so i want to avoid using:
[animatedContainer pauseSchedulerAndActions];
There is no pause/resume method for action in cocos2d, so the first way - you can implement your own CCAction subclass, that will allow this, or just stop current action and recreate it instead of unpause.

Invisible Box2d Sprite using Position?

Creating an CCNode, setting it to my player's position- in debug draw I see the physics object, but the sprite is invisible or nil or something. It doesn't crash the sprite simply doesn't appear. The bomb also travels the proper path and it's selector method is called.
Does NOT Appear:
GameObject *bomb = [_useBombArray nextSprite];
bomb.tag = kShipMissile;
[bomb stopAllActions];
NSLog(#"_bombSpawnPoint: %.0f, %.0f", _bombSpawnPoint.x, _bombSpawnPoint.y);
bomb.position = _bombSpawnPoint;
I have gotten it to appear by doing this:
GameObject *bomb = [_useBombArray nextSprite];
bomb.tag = kShipMissile;
[bomb stopAllActions];
bomb.position = ccp(_winSize.width * 0.5, _winSize.width * 0.5);
The _bombSpawnPoint is set prior to this and I do receive proper results on output.
Originally I thought I had called to create the object at an inopportune time in the update. So I changed the function slightly, to be sure it is called in proper order in the update method.
Not sure what's causing this! Please help!
I've created all my objects like this and they've all worked perfectly thus far!
The result of this was caused by the Texture of the Bomb was not in the proper BatchNode.
The error was not triggering until I removed the excess subclasses and used solely the sprite.
The error received was: 'CCSprite is not using the same texture id'
Once I used the other batch node everything worked perfect. Hope this helps someone!

Eternal scrolling UITableView

I'm trying to create a UITableView with dates, shouldn't be very exciting, I know. It starts around the current date, but the user should be able to scroll down (the future) and up (the past) as far as he/she wants. This results in a potentially infinite number of rows. So what's the way to go about creating this?
Returning NSIntegerMax as the number of rows already crashes the app, but even if it wouldn't, that still doesn't account for being able to scroll up. I could start half way of course, but eventually there's a maximum.
Any ideas how to do or fake this? Can I update/reload the table somehow without the user noticing, so I never run into a border?
SOLUTION:
I went with #ender's suggestion and made a table with a fixed amount of cells. But instead of reloading it when the user scrolls to near the edges of the fixed cells, I went with reloading the table when the scrolling grinds to a halt. To accomodate with a user scrolling great distances without stopping, I just increased the row count to 1000, putting the ROW_CENTER constant to 500. This is the method that takes care of updating the rows.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
NSArray *visible = [self.tableView indexPathsForVisibleRows];
NSIndexPath *upper = [visible objectAtIndex:0];
NSIndexPath *lower = [visible lastObject];
// adjust the table to compensate for the up- or downward scrolling
NSInteger upperDiff = ROW_CENTER - upper.row;
NSInteger lowerDiff = lower.row - ROW_CENTER;
// the greater difference marks the direction we need to compensate
NSInteger magnitude = (lowerDiff > upperDiff) ? lowerDiff : -upperDiff;
self.offset += magnitude;
CGFloat height = [self tableView:self.tableView heightForRowAtIndexPath:lower];
CGPoint current = self.tableView.contentOffset;
current.y -= magnitude * height;
[self.tableView setContentOffset:current animated:NO];
NSIndexPath *selection = [self.tableView indexPathForSelectedRow];
[self.tableView reloadData];
if (selection)
{
// reselect a prior selected cell after the reload.
selection = [NSIndexPath indexPathForRow:selection.row - magnitude inSection:selection.section];
[self.tableView selectRowAtIndexPath:selection animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}
The magic breaks when a user scrolls to the edge of the table without stopping, but with the table view bounces property disabled, this merely feels like a minor glitch, yet totally acceptable. As always, thanks StackOverflow!
You should establish a fixed number of cells and adjust your datasource when the user scrolls near the end of the tableview. For example, you have an array with 51 dates (today, 25 future and 25 past). When the app tries to render a cell near one of the borders, reconfigure your array and call reloadData
You might also have a look at the "Advanced Scroll View Techniques" talk of the WWDC 2011. They showed how you would create a UIScrollView which scrolls indefinitely. It starts at about 5 mins. in.
Two thoughts:
Do you really need a UITableView? You could use a UIScrollView, three screens high. If end of scrolling is reached, layout your content and adjust scrolling position. This gives the illusion of infinite scrolling. Creating some date labels and arranging them in layoutSubviews: should not be too much of an effort.
If you really want to stick to UITableView you could think about having two UITableViews. If scrolling in your first one reaches a critical point, spin off a thread and populate the second one. Then at some point, exchange the views and trigger the scrolling manually so that the user does not note the change. This is just some idea from the top of my head. I have not implemented something like this yet, but I implemented the infinite UIScrollView.
I answered this question in another post: https://stackoverflow.com/a/15305937/2030823
Include:
https://github.com/samvermette/SVPullToRefresh
SVPullToRefresh handles the logic when UITableView reaches the bottom. A spinner is shown automatically and a callback block is fired. You add in your business logic to the callback block.
#import "UIScrollView+SVInfiniteScrolling.h"
// ...
[tableView addInfiniteScrollingWithActionHandler:^{
// append data to data source, insert new cells at the end of table view
// call [tableView.infiniteScrollingView stopAnimating] when done
}];
This question has already been asked: implementing a cyclic UITableView
I'm copying that answer here to make it easier because the asker hasn't ticked my answer.
UITableView is same as UIScrollView in scrollViewDidScroll method.
So, its easy to emulate infinite scrolling.
double the array so that head and tail are joined together to emulate circular table
use my following code to make user switch between 1st part of doubled table and 2nd part of doubled table when they tend to reach the start or the end of the table.
:
/* To emulate infinite scrolling...
The table data was doubled to join the head and tail: (suppose table had 1,2,3,4)
1 2 3 4|1 2 3 4 (actual data doubled)
---------------
1 2 3 4 5 6 7 8 (visualizing joined table in eight parts)
When the user scrolls backwards to 1/8th of the joined table, user is actually at the 1/4th of actual data, so we scroll instantly (we take user) to the 5/8th of the joined table where the cells are exactly the same.
Similarly, when user scrolls to 6/8th of the table, we will scroll back to 2/8th where the cells are same. (I'm using 6/8th when 7/8th sound more logical because 6/8th is good for small tables.)
In simple words, when user reaches 1/4th of the first half of table, we scroll to 1/4th of the second half, when he reaches 2/4th of the second half of table, we scroll to the 2/4 of first half. This is done simply by subtracting OR adding half the length of the new/joined table.
Written and posted by Anup Kattel. Feel free to use this code. Please keep these comments if you don't mind.
*/
-(void)scrollViewDidScroll:(UIScrollView *)scrollView_
{
CGFloat currentOffsetX = scrollView_.contentOffset.x;
CGFloat currentOffSetY = scrollView_.contentOffset.y;
CGFloat contentHeight = scrollView_.contentSize.height;
if (currentOffSetY < (contentHeight / 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY + (contentHeight/2)));
}
if (currentOffSetY > ((contentHeight * 6)/ 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY - (contentHeight/2)));
}
}
P.S. - I've used this code on one of my apps called NT Time Table (Lite). If you want the preview, you can check out the app: https://itunes.apple.com/au/app/nt-time-table-lite/id528213278?mt=8
If your table can sometimes be too short, at the beginning of the above method you can add a if logic to exit when data count is say for example less than 9.

UIScrollView - snap to control when decelerating

The UIScrollView has a lot of information available to the programmer, but I dont see an obvious way to control the location that the control stop at after decelerating from a scroll gesture.
Basically I would like the scrollview to snap to specific regions of the screen. The user can still scroll like normal, but when they stop scrolling the view should snap to the most relevant location, and in the case of a flick gesture the deceleration should stop at these locations too.
Is there an easy way to do something like this, or should I consider the only way to accomplish this effect to write a custom scrolling control?
Since the UITableView is a UIScrollView subclass, you could implement the UIScrollViewDelegate method:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset
And then compute what the closest desired target content offset is that you want, and set that on the inout CGPoint parameter.
I've just tried this and it works well.
First, retrieve the unguided offset like this:
CGFloat unguidedOffsetY = targetContentOffset->y;
Then Figure out through some math, where you'd want it to be, noting the height of the table header. Here's a sample in my code dealing with custom cells representing US States:
CGFloat guidedOffsetY;
if (unguidedOffsetY > kFirstStateTableViewOffsetHeight) {
int remainder = lroundf(unguidedOffsetY) % lroundf(kStateTableCell_Height_Unrotated);
log4Debug(#"Remainder: %d", remainder);
if (remainder < (kStateTableCell_Height_Unrotated/2)) {
guidedOffsetY = unguidedOffsetY - remainder;
}
else {
guidedOffsetY = unguidedOffsetY - remainder + kStateTableCell_Height_Unrotated;
}
}
else {
guidedOffsetY = 0;
}
targetContentOffset->y = guidedOffsetY;
The last line above, actually writes the value back into the inout parameter, which tells the scroll view that this is the y-offset you'd like it to snap to.
Finally, if you're dealing with a fetched results controller, and you want to know what just got snapped to, you can do something like this (in my example, the property "states" is the FRC for US States). I use that information to set a button title:
NSUInteger selectedStateIndexPosition = floorf((guidedOffsetY + kFirstStateTableViewOffsetHeight) / kStateTableCell_Height_Unrotated);
log4Debug(#"selectedStateIndexPosition: %d", selectedStateIndexPosition);
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:selectedStateIndexPosition inSection:0];
CCState *selectedState = [self.states objectAtIndexPath:indexPath];
log4Debug(#"Selected State: %#", selectedState.name);
self.stateSelectionButton.titleLabel.text = selectedState.name;
OFF-TOPIC NOTE: As you probably guessed, the "log4Debug" statements are just logging. Incidentally, I'm using Lumberjack for that, but I prefer the command syntax from the old Log4Cocoa.
After the scrollViewDidEndDecelerating: and scrollViewDidEndDragging:willDecelerate: (the last one just when the will decelerate parameter is NO) you should set the contentOffset parameter of your UIScrollView to the desired position.
You also will know the current position by checking the contentOffset property of your scrollview, and then calculate the closest desired region that you have
Although you don't have to create your own scrolling control, you will have to manually scroll to the desired positions
To add to what felipe said, i've recently created a table view that snaps to cells in a similar way the UIPicker does.
A clever scrollview delegate is definitely the way to do this (and you can also do that on a uitableview, since it's just a subclass of uiscrollview).
I had this done by, once the the scroll view started decelerating (ie after scrollViewDidEndDragging:willDecelerate: is called), responding to scrollViewDidScroll: and computing the diff with the previous scroll event.
When the diff is less than say a 2 to 5 of pixels, i check for the nearest cell, then wait until that cell has been passed by a few pixels, then scroll back in the other direction with setContentOffset:animated:.
That creates a little bounce effect that is very nice for user experience, as it gives a good feedback on the snapping.
You'll have to be clever and not do anything when the table is bouncing at the top or bottom (comparing the offset to 0 or the content size will tell you that).
It works pretty well in my case because the cells are small (about 80-100px high), you might run into problems if you have a regular scroll view with bigger content areas.
Of course, you will not always decelerate past a cell, so in this case i just scroll to the nearest cell, and the animation looks jerky. Turns out with the right tuning, it barely ever happens, so i'm cool with this.
Spend a few hours tuning the actual values depending on your specific screen and you can get something decent.
I've also tried the naive approach, calling setContentOffset:animated: on scrollViewDidEndDecelerating: but it creates a really weird animation (or just plain confusing jump if you don't animate), that gets worse the lower the deceleration rate is (you'll be jumping from a slow movement to a much faster one).
So to answer the question:
- no, there is no easy way to do this, it'll take some time polishing the actual values of the previous algorithm, which might not work at all on your screen,
- don't try to create your own scroll view, you'll just waste time and badly reinvent a beautiful piece of engineering apple created with truck loads of bug. The scrollview delegate is the key to your problem.
Try something like this:
- (void) snapScroll;
{
int temp = (theScrollView.contentOffset.x+halfOfASubviewsWidth) / widthOfSubview;
theScrollView.contentOffset = CGPointMake(temp*widthOfSubview , 0);
}
- (void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
{
if (!decelerate) {
[self snapScroll];
}
}
- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self snapScroll];
}
This takes advantage of the int's drop of the post-decimal digits. Also assumes all your views are lined up from 0,0 and only the contentOffset is what makes it show up in different areas.
Note: hook up the delegate and this works perfectly fine. You're getting a modified version - mine just has the actual constants lol. I renamed the variables so you can read it easy