I have a tall UIImageView scrolling across the screen when a button is pressed. I used a timer to change the y position of the image at a specific speed. The image is 10,000px tall.
Here's my simple code:
-(void) moveImage {
myImage.center = CGPointMake(myImage.center.x, myImage.center.y +6);
}
-(IBAction)StartGame:(id)sender{
moveImageTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(moveImage)userInfo:nil repeats:YES];
[StartGame setHidden:TRUE];
}
Here's what i've tried to stop the timer when the image has scrolled completely through the screen:
-(void) moveImage {
myImage.center = CGPointMake(myImage.center.x, myImage.center.y +6);
if ([myImage == GPointMake(myImage.center.x, myImage.center.y -568) {
[moveImageTimer invalidate];
}
That doesn't work but i thought it would. Can someone tell me why the IF statement doesn't work? Any help is appreciated, thanks
Since you want to stop when the image moves to the bottom corner.
-(void) moveImage {
CGPoint newPoint= CGPointMake(myImage.center.x, myImage.center.y +6);
myImage.center = newPoint;
if ((newPoint.y - (myImage.frame.size.width/2)) >= [UIScreen mainScreen].bounds.size.height)
{
[moveImageTimer invalidate];
}
}
you can check the condition
like if (imageView.center.y > imageView.frame.size.height - self.view.frame.size.height)
here imageView.frame.size.height - self.view.frame.size.height because the image size is twice screen size. You have to set you logic
imageView.center = CGPointMake(imageView.center.x, imageView.center.y + 6);
if (imageView.center.y > imageView.frame.size.height - self.view.frame.size.height)
{
[moveImageTimer invalidate];
}
}
Related
I have a UIImageView rotating forever on my screen... I use the code below, which I found HERE:
- (void) spinWithOptions: (UIViewAnimationOptions) options {
[UIView animateWithDuration: 0.0f
delay: 0.0f
options: options
animations: ^{
Hand.transform = CGAffineTransformRotate(Hand.transform, M_PI / 30);
}
completion: ^(BOOL finished) {
if (finished) {
if (animating) { //if, keep spinning
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
} else if (options != UIViewAnimationOptionCurveEaseOut) { //final spin
//[self spinWithOptions: UIViewAnimationOptionCurveEaseOut];
}
}
}];
}
- (void) startSpin {
if (!animating) {
animating = YES;
[self spinWithOptions: UIViewAnimationOptionCurveEaseIn];
}
}
- (void) stopSpin {
animating = NO;
}
It worked find and the rotation seems smooth...
I've since added a countdown timer, using the follow:
-(void)timerTick:(NSTimer *)timer{
ticks -= 0.1; //NSLog(#"Total Ticks: %.2f",ticks);
if(ticks < 0.0){ //3 seconds is up
Failed.image = [UIImage imageNamed:#"Failed.png"];
ticks = 0.0;
[self GameOver]; //out of time...
}
else {
FailedBeforeTimer.image = [UIImage imageNamed:#"Failed Before.png"];
}
CountDownTimer.text = [NSString stringWithFormat:#"%.1f",ticks];
}
-(void)startTimer{
ticks = 3.0;
timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
}
-(void)stopTimer{
[timer invalidate];
ticks = 0.0;
}
Since adding this timer to my app, the rotation animation is really blocky and no longer a smooth rotation...
Any ideas what I should implement to solve this?
To check, I removed the timer and re-run the application, and sure enough, the animation was smooth again :(
UPDATE:
After some more trial and error, it seems to be this line of code that slows the process down: CountDownTimer.text = [NSString stringWithFormat:#"%.1f",ticks]; ie. When I don't display the countDownTimer text on the screen/update label on screen), the rotation is smooth still...
So confused... What can I do?
Animating with zero-duration animations does not make sense. That's not how UIView animation is supposed to work. My guess is that is the source of your problems. A zero duration animation will be jumpy by definition.
You should be animating your changes over some time-period. (Always use a non-zero duration.)
EDIT:
Note that your timerTick method is horribly inefficient. You load the same image into an image view on every tick. Don't do that. Set it up with the initial image, and then only change the image when your tick count reaches zero.
You should give CADisplayLink a try, it should be suitable for your case. It's easy to use, there are heaps of tutorials out there, just do a search.
Whenever I click on an image, I want the image to display as a larger image with the ability to zoom. So far when I click on the image, it displays as I want it to inside a scrollview. However, I have to get lucky to be able to zoom in properly. Most of the time when I attempt to zoom, the image just moves down and to the right and does not zoom at all. Here is my code:
-(void) pictureButtonAction
{
self.scrollImageView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
self.scrollImageView.contentSize = self.fullImageView.image.size;
self.scrollImageView.delegate = self;
self.scrollImageView.clipsToBounds = YES;
self.scrollImageView.scrollEnabled = YES;
[self.scrollImageView setMaximumZoomScale:4.0f];
[self.scrollImageView setMinimumZoomScale:1.0f];
[self.view addSubview:self.scrollImageView];
[self.scrollImageView addSubview:fullImageView];
}
-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.fullImageView;
}
ImageViews frame got changed while zooming , so that it moves down. So, You have to centralize the image view each time you zoom .
your scrollviews contentsize should be greater than or equal to your image size , if your fullImageView.image.size is less than your scrollviews bounds ,then set your scrollviews contentSize atleast double the scrollviews bounds .
call the below function in scrollViewDidZoom delegate method
-(void) centerScrollViewContents
{
CGSize boundsSize = self.scrollView.bounds.size;
CGRect contentsFrame = self.imageView.frame;
if (contentsFrame.size.width < boundsSize.width) {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0f;
} else {
contentsFrame.origin.x = 0.0f;
}
if (contentsFrame.size.height < boundsSize.height) {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f;
} else {
contentsFrame.origin.y = 0.0f;
}
self.imageView.frame = contentsFrame;
}
Try this , hope it will help you ; happy coding ! :)
#Maria's answer works but when zooming you'll experience unwanted bounces, instead of using it under scrollViewDidZoom, use scrollViewDidEndZooming: delegate to prevent that..
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
{
[self centerScrollViewContents];
}
and with a little enhancement from her code:
[UIView animateWithDuration:0 animations:^{
self.previewImageView.frame = contentsFrame;
}];
a bit late for an answer but i hope this will be useful to some..
I want to use NSTimer as a Countdown. The counter works fine;-)!
But I I switch to another viewController the timer doesn't runs in "foreground" - so the label isn't updated.... after coming back to this view.
My NSLOG shows that the timer is still runnig, like I want ;-)
So in whicht way I have to work, that my timer update my label?
Here is the code:
- (IBAction)startClock:(id)sender {
globalTimer = 20;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateCircularProgressBar)
userInfo:nil
repeats:YES];
}
- (void)updateCircularProgressBar
{
// Values to be passed on to Circular Progress Bar
if (globalTimer > 0 && globalTimer <= 1200) {
globalTimer--;
minutesLeft = globalTimer / 60;
secondsLeft = globalTimer % 60;
[self drawCircularProgressBarWithMinutesLeft:minutesLeft secondsLeft:secondsLeft];
NSLog(#"Time left: %02d:%02d", minutesLeft, secondsLeft);
}
else {
[self drawCircularProgressBarWithMinutesLeft:0 secondsLeft:0];
[timer invalidate];
}
}
I tested to run the "updateCircularProgressBar" in my viewDidLoad but this shows NO result...
THX for Help
UPDATE more with more Code:
/*-------------------- TIMER --------------------*/
// Draws the progress circles on top of the already painted background
- (void)drawCircularProgressBarWithMinutesLeft:(NSInteger)minutes secondsLeft:(NSInteger)seconds
{
// Removing unused view to prevent them from stacking up
for (id subView in [self.view subviews]) {
if ([subView isKindOfClass:[CircularProgressTimer class]]) {
[subView removeFromSuperview];
}
}
// Init our view and set current circular progress bar value
CGRect progressBarFrame = CGRectMake(0, 0, 180, 180);
progressTimerView = [[CircularProgressTimer alloc] initWithFrame:progressBarFrame];
[progressTimerView setCenter:CGPointMake(160, 210)];
[progressTimerView setPercent:seconds];
if (minutes == 0 && seconds <= 0) {
[progressTimerView setInstanceColor:[UIColor magentaColor]];
}
// Here, setting the minutes left before adding it to the parent view
[progressTimerView setMinutesLeft:minutesLeft];
[progressTimerView setSecondsLeft:secondsLeft];
[self.view addSubview:progressTimerView];
progressTimerView = nil;
}
The label is in another file (CircularProgressTimer) which controls the progessbar...
I am trying to make a game movement animation in objective c so that when a button is pressed something will move across the screen while it is pressed. This works fine though the calling of the NSTimer which I am using is irregular and I am getting laggy movements. This is in iOS 6 so I wont use sprite kit as it is not available
Any suggestions on how to make it a regular call or to make it a smooth animation would be much appreciated
Thanks in advance
Here is the code:
-(void) click {
if (!self.animating) {
self.animating = YES;
timer = [NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(move)
userInfo:nil
repeats:YES];
}
}
- (void) cancelClick {
self.animating = NO;
[timer invalidate];
}
-(void) move {
CGPoint origin = self.target.frame.origin;
if ([self.direction isEqual:#"left"]) {
origin.x -= self.magnitude;
} else if ([self.direction isEqual:#"right"]) {
origin.x += _magnitude;
} else if ([self.direction isEqual:#"up"]) {
origin.y -= _magnitude;
} else {
origin.y += _magnitude;
}
[self.target move:0 toPoint:origin animated:YES delay:0 animationOptions:UIViewAnimationOptionCurveLinear];
}
I want to add an UIImageView to my view at the users touch location and have the UIImageView grow while the user is holding their finger down. Think of a ballon being blown up. I want the center of the UIImageView to remain at the user's touch location while its growing.
I figured the best way would be a UILongPressGestureRecognizer and wrote the below. This does work as I planed except the visual effect is somewhat choppy and clumsy.
Is there any way that I can animate the UIImageView's size until the UILongPressGestureRecognizer calls UIGestureRecognizerStateEnded?
Or, is there a better way to do this altogether?
declared in .h: CGPoint longPressLocation;
.m:
- (IBAction) handleInflation:(UILongPressGestureRecognizer *) inflateGesture {
longPressLocation= [inflateGesture locationInView:self.view];
switch (inflateGesture.state) {
case UIGestureRecognizerStateBegan:{
NSLog(#"Long press Began .................");
inflateTimer = [NSTimer scheduledTimerWithTimeInterval:.4 target:self selector:#selector(inflate) userInfo:nil repeats:YES];
UIImage *tempImage=[UIImage imageNamed:#"bomb.png"];
UIImageView *inflatableImageView = [[UIImageView alloc] initWithFrame:CGRectMake(longPressLocation.x-tempImage.size.width/2,
longPressLocation.y-tempImage.size.height/2,
tempImage.size.width, tempImage.size.height)];
inflatableImageView.image = tempImage;
[bonusGame addSubview:inflatableImageView];
inflatable=inflatableImageView;
}
break;
case UIGestureRecognizerStateChanged:{
NSLog(#"Long press Changed .................");
}
break;
case UIGestureRecognizerStateEnded:{
NSLog(#"Long press Ended .................");
[inflateTimer invalidate];
}
break;
default:
break;
}
}
-(void)inflate{
inflatable.frame=CGRectMake(inflatable.frame.origin.x,inflatable.frame.origin.y , inflatable.bounds.size.width+15, inflatable.bounds.size.height+15);
inflatable.center=longPressLocation;
}
Final Working Code:
- (IBAction) handleInflation:(UILongPressGestureRecognizer *) inflateGesture {
inflateGesture.minimumPressDuration = .01;
longPressLocation= [inflateGesture locationInView:self.view];
switch (inflateGesture.state) {
case UIGestureRecognizerStateBegan:{
NSLog(#"Long press Began .................");
inflateStart = [NSDate date];
inflateDisplayLink = [NSClassFromString(#"CADisplayLink") displayLinkWithTarget:self selector:#selector(inflate)];
[inflateDisplayLink setFrameInterval:1];
[inflateDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
UIImage *tempImage=[UIImage imageNamed:#"bomb.png"];
UIImageView *inflatableImageView = [[UIImageView alloc] initWithFrame:CGRectMake(longPressLocation.x-tempImage.size.width/2,
longPressLocation.y-tempImage.size.height/2,
tempImage.size.width, tempImage.size.height)];
inflatableImageView.image = tempImage;
[bonusGame addSubview:inflatableImageView];
inflatable=inflatableImageView;
}
break;
case UIGestureRecognizerStateChanged:{
NSLog(#"Long press Changed .................");
}
break;
case UIGestureRecognizerStateEnded:{
NSLog(#"Long press Ended .................");
[inflateDisplayLink invalidate];
}
break;
default:
break;
}
}
-(void)inflate{
NSDate *inflateEnd = [NSDate date];
NSTimeInterval inflateInterval;
inflateInterval = ([inflateEnd timeIntervalSince1970] - [inflateStart timeIntervalSince1970])*25;
inflatable.frame=CGRectMake(inflatable.frame.origin.x,
inflatable.frame.origin.y ,
inflatable.bounds.size.width+inflateInterval,
inflatable.bounds.size.height+inflateInterval);
inflatable.center=longPressLocation;
if(inflatable.bounds.size.width>200){
[inflateDisplayLink invalidate];
}
}
A timer may not be smooth. Instead check out CADisplayLink, which will provide a delegate callback whenever the device's screen is redrawn (~60hz), so you will get a per frame chance to adjust your balloon size.
Another thing to consider is the time between refreshes isn't constant, it could be a lot slower than when the last refresh occured, so if you are incrementing the size by a constant 15 every time you get a callback, then the animation may not seem smooth.
To combat this, when you start the animation take a timestamp and hold onto it, then when you inflate the balloon take another timestamp and determine the difference between now and the last redraw, then multiply the difference by some value, which will ensure a constant smooth size growth - this is called a timestep.
https://developer.apple.com/library/ios/documentation/QuartzCore/Reference/CADisplayLink_ClassRef/Reference/Reference.html
Getting creative here, but I think this might work:
You start an animation that animates the images frame from its current size towards a maximum size.
Start the animation as soon as you detect the long press gesture.
If the gesture ends, get the current frame size from the presentationLayer of the animated view/image and update the view/image's frame to that size by starting a new animation with UIViewAnimationOptionBeginFromCurrentState so that the old animation will stop.
That should give you a smoothly growing balloon.
Try this....
imageView = [[UIImageView alloc] initWithFrame:(CGRectMake(120, 100, 30, 30))];
imageView.backgroundColor = [UIColor redColor];
[self.view addSubview:imageView];
imageView.userInteractionEnabled = TRUE;
UILongPressGestureRecognizer *g = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[g setMinimumPressDuration:0.001];
[imageView addGestureRecognizer:g];
And Add these methods
-(void)longPress:(UITapGestureRecognizer *)t
{
if (t.state == UIGestureRecognizerStateBegan)
{
[timer invalidate];
timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(makeIncrc) userInfo:nil repeats:TRUE];
}
else if (t.state == UIGestureRecognizerStateEnded || t.state == UIGestureRecognizerStateCancelled)
{
[timer invalidate];
timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(makeDec) userInfo:nil repeats:TRUE];
}
}
-(void)makeIncrc
{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationCurve:(UIViewAnimationCurveLinear)];
CGRect frame = imageView.frame;
frame.origin.x = frame.origin.x - 5;
frame.origin.y = frame.origin.y - 5;
frame.size.width = frame.size.width + 10;
frame.size.height = frame.size.height + 10;
imageView.frame = frame;
[UIView commitAnimations];
}
-(void)makeDec
{
if (imageView.frame.size.width < 20 || imageView.frame.size.height < 20)
{
[timer invalidate];
}
else
{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationCurve:(UIViewAnimationCurveLinear)];
CGRect frame = imageView.frame;
frame.origin.x = frame.origin.x + 5;
frame.origin.y = frame.origin.y + 5;
frame.size.width = frame.size.width - 10;
frame.size.height = frame.size.height - 10;
imageView.frame = frame;
[UIView commitAnimations];
}
}
Here's a Swift version. I had to replace invalidate() calls with "paused" in order to avoid runaway inflation.
var inflateDisplayLink: CADisplayLink?
var inflateStart: NSDate!
var longPressLocation: CGPoint!
var inflatable: UIImageView!
func handleInflation(inflateGesture: UILongPressGestureRecognizer) {
longPressLocation = inflateGesture.locationInView(self.view)
let imageView = inflateGesture.view as! UIImageView
switch (inflateGesture.state) {
case .Began:
println("Long press Began .................")
inflateStart = NSDate()
let tempImageView = UIImageView(image: imageView.image)
tempImageView.contentMode = .ScaleAspectFit
tempImageView.frame = imageView.frame
self.view.addSubview(tempImageView)
inflatable = tempImageView
if inflateDisplayLink == nil {
inflateDisplayLink = CADisplayLink(target: self, selector: Selector("inflate"))
inflateDisplayLink!.frameInterval = 1
inflateDisplayLink!.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
} else {
inflateDisplayLink!.paused = false
}
break
case .Changed:
println("Long press Changed .................")
break
case .Ended:
println("Long press Ended .................")
inflateDisplayLink!.paused = true
break
default:
break
}
}
func inflate() {
var inflateEnd = NSDate()
// Convert from Double to CGFloat, due to bug (?) on iPhone5
var inflateInterval: CGFloat = CGFloat((Double(inflateEnd.timeIntervalSince1970) - Double(inflateStart.timeIntervalSince1970))) * 5.0
inflatable.frame = CGRectMake(inflatable.frame.origin.x, inflatable.frame.origin.y, inflatable.bounds.size.width + inflateInterval, inflatable.bounds.size.height + inflateInterval)
inflatable.center = longPressLocation.center
if inflatable.bounds.size.width > 200 {
inflateDisplayLink!.paused = true
}
}