5

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

Community
  • 1
  • 1
DShah
  • 9,768
  • 11
  • 71
  • 127

3 Answers3

2

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!

Community
  • 1
  • 1
Bartserk
  • 675
  • 6
  • 18
  • Hi, I do not want to get callback for animation completion. My requirement is that, I want to move all RoundButtons simultaneously with relative motion. Say if my first button path is 1 cm and Second button path is 2 cm, then first button should reach first and second button should take double time then first button. This animation should be simultaneously. – DShah Apr 16 '15 at 05:06
  • My bad, I didn't understand you at all :) I have an idea on how to do that, I'm editing my answer right now. – Bartserk Apr 16 '15 at 08:03
0

To achieve the things, I have used UIBezierPath+Points category. Apart from that, in my code following classes and logic is used:

In PlayGroundView, I edited the code in drawRect method, rest code is same:

- (void)drawRect:(CGRect)rect {
    [[UIColor redColor] setStroke];
    for (int i=0; i<[self subviews].count; i++) {
        RoundButton *tmpPlayerButton = [self.subviews objectAtIndex:i];
        for (UIBezierPath *bezierPath in tmpPlayerButton.allPathsOfPlayer) {
            [bezierPath strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
        }
    }
    [path stroke];
}

In my ViewController class, in Get More Buttons below code goes:

- (IBAction)getBallPressed:(id)sender {
    [self moveBalls];

//    self.playView.viewController = self;

    int xPos = self.view.frame.size.width-30;
    int yPos = self.view.frame.size.height-30;
    int btnXPos = arc4random_uniform(xPos);
    int btnYPos = arc4random_uniform(yPos);

    RoundButton *newButton = [[RoundButton alloc] initWithFrame:CGRectMake(btnXPos, btnYPos, 30, 30)];
    [newButton setBackgroundColor:[UIColor redColor]];
    newButton.layer.cornerRadius = newButton.frame.size.height/2;
    [newButton setTitle:[self randomStringWithLength:1] forState:UIControlStateNormal];

    UILongPressGestureRecognizer *longGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longGestureRecognized:)];
    [newButton addGestureRecognizer:longGesture];

    [self.playView.playerArray addObject:newButton];
    [self.playView addSubview:newButton];
}

- (void)moveBalls {
    for (int i=0; i<[self.playView subviews].count; i++) {
        RoundButton *playerButton = [self.playView.subviews objectAtIndex:i];
        if (playerButton.isEditPath) {
            id point = [[[playerButton.allPathsOfPlayer lastObject] points] lastObject];
            CGPoint lastPoint = [point CGPointValue];
            NSLog(@"lastPoint: %@", NSStringFromCGPoint(lastPoint));
            [playerButton setCenter:lastPoint];
        }
    }
}

In Move Button, below code goes, which allows me to move all my Round Button simultaneously which is my Exact requirement:

- (void)playAnimation {
    for (int i=0; i<self.playView.subviews.count; i++) {
        //Prepare the animation - we use keyframe animation for animations of this complexity
        CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        //Set some variables on the animation
        pathAnimation.calculationMode = kCAAnimationPaced;

        //We want the animation to persist - not so important in this case - but kept for clarity
        //If we animated something from left to right - and we wanted it to stay in the new position,
        //then we would need these parameters
        pathAnimation.fillMode = kCAFillModeForwards;
        pathAnimation.removedOnCompletion = NO;
        pathAnimation.duration = 2.0;
        pathAnimation.repeatCount = 0;

        RoundButton *tmpRoundButton;
        UIBezierPath *path;

        tmpRoundButton = [self.playView.subviews objectAtIndex:i];

        for (int j=0; j<tmpRoundButton.allPathsOfPlayer.count; j++) {
            if (path)
                [path appendPath:[tmpRoundButton.allPathsOfPlayer objectAtIndex:j]];
            else
                path = [tmpRoundButton.allPathsOfPlayer objectAtIndex:j];

            //Now we have the path, we tell the animation we want to use this path - then we release the path
            pathAnimation.path = [path CGPath];
            [tmpRoundButton.layer addAnimation:pathAnimation forKey:@"moveTheSquare"];
        }
        [path moveToPoint:[[[path points] firstObject] CGPointValue]];
    }
    [self.playView setNeedsDisplay];
}
DShah
  • 9,768
  • 11
  • 71
  • 127
0
let playerController = AVPlayerViewController()
    playerController.delegate = self
    player = AVPlayer(url: url as URL)
    playerController.player = player
    self.addChildViewController(playerController)
    self.videoview.addSubview(playerController.view)
    playerController.view.frame = self.videoview.bounds
    player.play()