Search code examples
iosrotationtransformcgaffinetransformuicontrol

Why does my rotatable wheel control sometimes spin in the wrong direction?


I am making a rotatable wheel control. You can drag anywhere on it to spin the control by it's central point.

It's not behaving correctly. It usually works well, but for some portion of a rotation, it starts rotating in the opposite direction for a brief period, and then it starts rotating with your finger again. EDIT: Upon further testing, I realized that you don't need to rotate it 360 degrees for it to rotate in the wrong direction.

A possibly unrelated problem is that the control seems to move slower then your finger. If a solution to the original problem doesn't solve this problem, I'll ask a separate question.

I posted an example project. If you would prefer to reproduce the project yourself, here are the steps with code:

  1. Create a new single view application.
  2. Create a new UIView subclass (in Objective-C) called NNReadyIndicator
  3. Copy and paste this code in `NNReadyIndicator.m

#import "NNReadyIndicator.h"

@interface NNReadyIndicator ()

@property CGAffineTransform startTransform;

@property double originalAngle;

@end

@implementation NNReadyIndicator

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
 }
 */

#pragma mark tracking

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    CGPoint touchPoint = [touch locationInView:self];

    [self setOriginalAngle:[NNReadyIndicator calculateAngleWithX1:touchPoint.x x2:self.center.x y1:touchPoint.y y2:self.center.y]];
    [self setStartTransform:self.transform];

    NSLog(@"BEGIN TRACKING  | originalAngle: %f", self.originalAngle);

    return YES;
}

- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    CGPoint touchPoint = [touch locationInView:self];
    double angle = [NNReadyIndicator calculateAngleWithX1:touchPoint.x x2:self.center.x y1:touchPoint.y y2:self.center.y];
    CGFloat angleDifference = (CGFloat)(self.originalAngle - angle);

    [self setTransform: CGAffineTransformRotate(self.startTransform, -angleDifference)];

    NSLog(@"CONTINUE TRACKING |       angle: %f angleDifference: %f", angle, angleDifference);

    return YES;
}

#pragma mark -- Calculate Angle

+ (double)calculateAngleWithX1:(double)x1 x2:(double)x2 y1:(double)y1 y2:(double)y2 {
    double distanceX = [NNReadyIndicator calculateDistanceWithPoint:x1 point:x2];
    double distanceY = [NNReadyIndicator calculateDistanceWithPoint:y1 point:y2];

    return [self calculateAngleWithDistanceX:distanceX distanceY:distanceY];
}

+ (double)calculateDistanceWithPoint:(double)pointA point:(double)pointB {
    double distance = pointA - pointB;

    if (distance < 0) {
        distance = -distance;
    }

    return distance;
}

+ (double)calculateAngleWithDistanceX:(double)distanceX distanceY:(double)distanceY {
    return atan2(distanceY, distanceX);
}

@end

EDIT: Added logging

Why is the control not consistently rotating in the same direction (and at the same speed?) as a finger that is dragging it?


Solution

  • You need to flip the angle if it is above or below 360 degrees. Also, to make it easier to understand, you will have to convert it into degrees and then back. Based off of this answer: https://stackoverflow.com/a/19617316/2564682

    #define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)
    
    - (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
        CGPoint touchPoint = [touch locationInView:self];
        double angle = [NNReadyIndicator calculateAngleWithX1:touchPoint.x x2:self.center.x y1:touchPoint.y y2:self.center.y];
    
        double angleInDegrees = angle * 180 / M_PI;
    
        // Flip angle if above or below 360 degrees
        if (angleInDegrees > 180) {
            angleInDegrees -= 360;
        } else if (angleInDegrees < -180) {
            angleInDegrees += 360;
        }
    
        CGFloat angleDifference = (CGFloat)(self.originalAngle - angleInDegrees);
    
        [self.layer setTransform: CATransform3DRotate(self.layer.transform, (float)DEGREES_TO_RADIANS(-angleDifference), .0, .0, 1.0)];
    
        NSLog(@"CONTINUE TRACKING |       angle: %f angleDifference: %f", angleInRadians, angleDifference);
    
        return YES;
    }