Revolving animation, that follows finger, of uibuttons that follows outer path of circle

后端 未结 1 1799
故里飘歌
故里飘歌 2021-01-13 10:28

I am looking for a little guidance to start figuring out an animation that tracks finger movement and moves a collection of UIButtons along the outer path of a circle

相关标签:
1条回答
  • 2021-01-13 10:46

    (Sample code on GitHub)

    It's not really that difficult, there's just a lot of trigonometry involved.

    Now, what I'm going to describe now, is not an animation, since you requested for it to track the position of your finger in the title. An animation would involve its own timing function, but since you're using the touch gesture, we can use the inherent timing that this event has and just rotate the view accordingly. (TL;DR: The user keeps the time of the movement, not an implicit timer).

    Keeping track of the finger

    First of all, let's define a convenient class that keeps track of the angle, I'm gonna call it DialView. It's really just a subclass of UIView, that has the following property:

    DialView.h

    @interface DialView : UIView
    
    @property (nonatomic,assign) CGFloat angle;
    
    @end
    

    DialView.m

    - (void)setAngle:(CGFloat)angle
    {
        _angle = angle;
        self.transform = CGAffineTransformMakeRotation(angle);
    }
    

    The UIButtons can be contained within this view (I'm not sure if you want the buttons to be responsible for the rotation? I'm gonna use a UIPanGestureRecognizer, since it's the most convenient way).

    Let's build the view controller that will handle a pan gesture inside our DialView, let's also keep a reference to DialView.

    MyViewController.h

    @class DialView;
    
    @interface ViewController : UIViewController
    
    // The previously defined dial view
    @property (nonatomic,weak) IBOutlet DialView *dial;
    
    // UIPanGesture selector method    
    - (IBAction)didReceiveSpinPanGesture:(UIPanGestureRecognizer*)gesture;
    
    @end
    

    It's up to you on how you hook up the pan gesture, personally, I made it on the nib file. Now, the main body of this function:

    MyViewController.m

    - (IBAction)didReceiveSpinPanGesture:(UIPanGestureRecognizer*)gesture
    {
        // This struct encapsulates the state of the gesture
        struct state
        {
            CGPoint touch;          // Current touch position
            CGFloat angle;          // Angle of the view
            CGFloat touchAngle;     // Angle between the finger and the view
            CGPoint center;         // Center of the view
        };
        
        // Static variable to record the beginning state
        // (alternatively, use a @property or an _ivar)
        static struct state begin;
        
        CGPoint touch = [gesture locationInView:nil];
        
        if (gesture.state == UIGestureRecognizerStateBegan)
        {
            begin.touch  = touch;
            begin.angle  = self.dial.angle;
            begin.center = self.dial.center;
            
            begin.touchAngle = CGPointAngle(begin.touch, begin.center);
        }
        else if (gesture.state == UIGestureRecognizerStateChanged)
        {
            struct state now;
            now.touch   = touch;
            now.center  = begin.center;
            
            // Get the current angle between the finger and the center
            now.touchAngle = CGPointAngle(now.touch, now.center);
            
            // The angle of the view shall be the original angle of the view
            // plus or minus the difference between the two touch angles
            now.angle = begin.angle - (begin.touchAngle - now.touchAngle);
            
            self.dial.angle = now.angle;
        }
        else if (gesture.state == UIGestureRecognizerStateEnded)
        {
            // (To be Continued ...)
        }
    }
    

    CGPointAngle is a method invented by me, it's just a nice wrapper for atan2 (and I'm throwing CGPointDistance in if you call NOW!):

    CGFloat CGPointAngle(CGPoint a, CGPoint b)
    {
        return atan2(a.y - b.y, a.x - b.x);
    }
    
    CGFloat CGPointDistance(CGPoint a, CGPoint b)
    {
        return sqrt(pow((a.x - b.x), 2) + pow((a.y - b.y), 2));
    }
    

    The key here, is that there's two angles to keep track:

    1. The angle of the view itself
    2. The angle formed between your finger and the center of the view.

    In this case, we want to have the view's angle be originalAngle + deltaFinger, and that's what the above code does, I just encapsulate all the state in a struct.

    Checking the radius

    If that you want to keep track on "the border of the view", you should use the CGPointDistance method and check if the distance between the begin.center and the now.finger is an specific value.

    That's homework for ya!

    Snapping back

    Ok, now, this part is an actual animation, since the user no longer has control of it.

    The snapping can be achieved by having a set defined of angles, and when the finger releases the finger (Gesture ended), snap back to them, like this:

    else if (gesture.state == UIGestureRecognizerStateEnded)
    {
        // Number of "buttons"
        NSInteger buttons = 8;
        
        // Angle between buttons
        CGFloat angleDistance = M_PI*2 / buttons;
        
        // Get the closest angle
        CGFloat closest = round(self.dial.angle / angleDistance) * angleDistance;
        
        [UIView animateWithDuration:0.15 animations:^{
            self.dial.angle = closest;
        }];
    }
    

    The buttons variable, is just a stand-in for the number of buttons that are in the view. The equation is super simple actually, it just abuses the rounding function to approximate to the closes angle.

    0 讨论(0)
提交回复
热议问题