Question

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];
    }
}
Was it helpful?

Solution

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

OTHER TIPS

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
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top