Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple UIView's Animation with UIBezierPath

I have below screen:

enter image description here

When I click Get More Button, it will add new Rounded Button. I have added Pan Gesture on the UIView where all Rounded Buttons are added which will then draw a UIBezierPath from Rounded Button only. Up to this point, everything works.

Now, I want the following:

  1. I have parent UIView called playGroundView, which contains all Rounded Button. This will draw a Bezier Path for all those Buttons.
  2. Once Bezier Path is drawn for all Buttons, then I click Move Button to animate all Rounded Button at once.
  3. Once, all Button moves at the End Point of Bezier Path, then I can again draw further Bezier Path (Adding more steps for Rounded Button) connection to same path for same button.
  4. After this, Final Move button will animate whole for same Button at once.

Refer to Below code for PlayGround UIView subclass:

@interface PlayGroundView : UIView
@property (nonatomic, weak) PlayGroundViewController *playViewController;
@end

#import "PlayGroundView.h"
#import "RoundedButton.h"
#import "PlayGroundViewController.h"

@interface PlayGroundView () {
    UIBezierPath *path;
    BOOL isDrawPointInside;
}

@end

@implementation PlayGroundView

#pragma mark - View Methods -
- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder])
        [self commonInit];
    return self;
}

- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame])
        [self commonInit];
    return self;
}

- (void)drawRect:(CGRect)rect {
    [[UIColor redColor] setStroke];
    [path stroke];
}

#pragma mark - Action Methods -
- (void)pan:(UIPanGestureRecognizer *)pan {
    CGPoint currentPoint = [pan locationInView:self];
    //    NSLog(@"currentPoint: %@", NSStringFromCGPoint(currentPoint));
    if (pan.state == UIGestureRecognizerStateBegan) {
        NSMutableArray *buttonAry = [NSMutableArray array];
        buttonAry = self.playViewController.rBtnOpponentPlayerList;
        [buttonAry addObjectsFromArray:self.playViewController.rBtnPlayerList];
        for (int i=0; i<buttonAry.count; i++) {
            UIButton *btn = [buttonAry objectAtIndex:i];
            CGRect nearByRect = CGRectMake(btn.frame.origin.x - 20, btn.frame.origin.y - 20, btn.frame.size.width + 40, btn.frame.size.height + 40);
            if (CGRectContainsPoint(nearByRect, currentPoint)) {
                isDrawPointInside = YES;
                [path moveToPoint:btn.center];
                //                NSLog(@"point is inside....");
            }
        }
    }
    if (pan.state == UIGestureRecognizerStateChanged) {
        if (isDrawPointInside) {
            [path addLineToPoint:currentPoint];
            [self setNeedsDisplay];
        }
    }
    
    if (pan.state == UIGestureRecognizerStateEnded) {
        isDrawPointInside = NO;
        //        NSLog(@"pan ended");
    }
}

#pragma mark - Helper Methods -
- (void)commonInit {
    path = [UIBezierPath bezierPath];
    path.lineWidth = 3.0;
    
    // Capture touches
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    pan.maximumNumberOfTouches = pan.minimumNumberOfTouches = 1;
    [self addGestureRecognizer:pan];
    
    // Erase with long press
    [self addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(erase)]];
}

- (void)erase {
    path = [UIBezierPath bezierPath];
    path.lineWidth = 3.0;

    [self setNeedsDisplay];
}

@end

I am still not able to manage all Rounded Button state. Let me know the best way to manage all the step of Rounded Button while moving.

I have referred to: http://nachbaur.com/blog/core-animation-part-4

My Full Demo code is available at: https://www.dropbox.com/s/f0ri0bff8ioxtpz/FreeHandDrawingDemo%202.zip?dl=0

like image 252
DShah Avatar asked Nov 01 '22 07:11

DShah


1 Answers

I'll assume that, when you say "manage all Rounded Button state", you mean you want to have a way to know the view has started animating, something like a viewHasMovedABitAutomatically callback :) If that's the case, as far as I know there's no implemented way in the CA framework to do so. However, have a look at this question: Core animation progress callback Here's a way to do a workaround that looks pretty nice, perhaps you can work on something starting from here.

Oh, and one thing. I see you're using a Pan gesture recognizer. I've just skimmed through both your code and tarmes' (the guy in the other thread ;) ) but I think he's using drawInContext to check the layer is being drawn. This might trigger as well if your view is draggable so, if that's the case, consider using some viewIsBeingDragged flag on the pan method, and check it on the callback.

EDIT: Sorry, that seems to have nothing to do with your actual problem. I understand that what you meant is to control the animation speed so them all are moving accordingly. Let's elaborate on that.

As far as I know, there's no such a thing as an animation "step". Animating an image to move it 20 pixels to the left doesn't necessarily imply that the image will be redrawn 20 times. The image will be rendered as many times as it's needed, so we can't rely on the render method to calculate that. As far as I know, the only way to control the animation speed is via the duration parameter of animateWithDuration:. And you don't know the duration, you want to make the speed constant so the duration is whatever it takes.

Basic physics: duration is equal to distance divided by speed, so if we had the distance we need to cover we could easily calculate the animation duration. At first I thought that maybe UIBezierPath had a method or an attribute to calculate the total length, but I was wrong. However, in this thread Get CGPath total length there's a code that calculates it by numeric integration, you should test it to see if it works. Once you have it, you can do something like this (I didn't test it, I'm just entering pseudocode)

#define SPEED_CALIBRATION 30.0 // or whatever

-(void)animateView:(UIView*)view withBezierPath:(UIBezierPath*)path {
    CGFloat distance = [self getBezierPathDistance:path];
    CGFloat duration = distance / SPEED_CALIBRATION;
    [self animateView:view withDuration:duration andBezierPath:path];
}

Something like this. getBezierPathDistance is the method from the thread above, assuming it works, and animateView:withDuration:andBezierPath: is your method to perform the non-linear animation using the CA documentation (I don't quite recall all the details, but I remember it was a pretty long chunk of code ;)).

I'm sorry if the answer is a bit vague, let's hope it helps!

like image 197
Bartserk Avatar answered Nov 11 '22 06:11

Bartserk