contentOffset
the scrollView
. On this basis, we consider the angle of rotation. @property (assign, nonatomic) CGFloat currentAngle; @property (assign, nonatomic) CGPoint startPoint; @property (assign, nonatomic) CGFloat previousAngle;
@property (assign, nonatomic, readonly) CGFloat circleLength;
setter
. - (void)setCircleRadius:(CGFloat)circleRadius { _circleRadius = circleRadius; _circleLength = 2 * M_PI * circleRadius; }
startPoint
in the method - (void)commonInit
as the middle of contentOffset
, so that we can rotate in both directions. self.startPoint = self.scrollView.contentOffset;
- (CGFloat)deltaWithOffset:(CGPoint)offset { return sqrt(pow(self.startPoint.x - offset.x, 2) + pow(self.startPoint.y - offset.y, 2)); }
typedef NS_ENUM(NSUInteger, AYNCircleViewHalf) { AYNCircleViewHalfLeft, AYNCircleViewHalfRight, };
- (AYNCircleViewHalf)halfWithPoint:(CGPoint)point { return point.x > self.contentView.center.x ? AYNCircleViewHalfRight : AYNCircleViewHalfLeft; }
- (CGFloat)signWithOffset:(CGPoint)offset half:(AYNCircleViewHalf)half { CGFloat sign = offset.x > self.startPoint.x ? -1 : 1; BOOL isYDominant = fabs(offset.y - self.startPoint.y) > fabs(offset.x - self.startPoint.x); if (isYDominant) { sign = offset.y > self.startPoint.y ? -1 : 1; sign *= half == AYNCircleViewHalfLeft ? -1 : 1; } return sign; }
delta
. - (CGFloat)angleWithOffset:(CGPoint)offset half:(AYNCircleViewHalf)half { CGFloat delta = [self deltaWithOffset:offset] / self.circleLength; CGFloat sign = [self signWithOffset:offset half:half]; return sign * delta * 2 * M_PI; }
- (CGFloat)floorAngle:(CGFloat)angle { NSInteger times = floorf(fabs(angle) / (2 * M_PI)); NSInteger sign = angle > 0 ? -1 : 1; return angle + sign * times * 2 * M_PI; }
UIView +
- (void)animateWithDuration:animations:
- (void)rotateWithAngle:(CGFloat)angle { [UIView animateWithDuration:0.1 animations:^{ self.contentView.transform = CGAffineTransformMakeRotation(angle); }]; }
self
disappear.- (void)scrollViewDidScroll:
in which we will perform all calculations and state changes: - (void)scrollViewDidScroll:(UIScrollView *)scrollView { CGPoint point = [scrollView.panGestureRecognizer locationInView:self]; CGFloat tickOffset = [self angleWithOffset:scrollView.contentOffset half:[self halfWithPoint:point]]; self.currentAngle = [self floorAngle:(self.previousAngle + tickOffset)]; [self rotateWithAngle:self.currentAngle]; self.previousAngle = self.currentAngle; self.startPoint = scrollView.contentOffset; }
targetContentOffset
in the delegate method - (void)scrollViewWillEndDragging:withVelocity:targetContentOffset:
Let's add our mathematical apparatus.angleStep
. We define for this method. - (CGFloat)normalizeAngle:(CGFloat)angle { return lroundf(angle / self.angleStep) * self.angleStep; }
scrollView
stopping scrollView
. Method - (void)scrollViewWillEndDragging:withVelocity:targetContentOffset:
returns the contentOffset
that will scrollView
when scrollView
. Our goal is to change this CGPoint
. We start from the normalized offset length value.target
be targetContentOffset
. We calculate such a point normalizedContentOffset
so that the resulting length translates into a multiple of a angleStep
. Then the control will stop the rotation exactly on the number.startPoint
, targetPoint
, - (CGPoint)endPointWithTargetPoint:(CGPoint)targetPoint scrollView:(UIScrollView *)scrollView { CGPoint point = [scrollView.panGestureRecognizer locationInView:self]; CGFloat tickOffset = [self angleWithOffset:targetPoint half:[self halfWithPoint:point]]; CGFloat rotationAngle = self.previousAngle + tickOffset; CGFloat delta = [self deltaWithAngle:rotationAngle]; CGFloat normalizedRotationAngle = [self normalizeAngle:rotationAngle]; CGFloat normalizedDelta = [self deltaWithAngle:normalizedRotationAngle]; CGFloat inclination = [self inclinationWithOffset:targetPoint startPoint:self.startPoint]; CGFloat sign = normalizedRotationAngle <= 0 ? -1 : 1; CGPoint result = CGPointMake(targetPoint.x + sign * (normalizedDelta - delta) * cos(inclination), targetPoint.y + sign * (normalizedDelta - delta) * sin(inclination)); return result; }
- (CGFloat)inclinationWithOffset:(CGPoint)offset startPoint:(CGPoint)startPoint { CGFloat y = (offset.y - self.startPoint.y); CGFloat x = (offset.x - self.startPoint.x); if (!isnan(x) && x != 0) { return atan2(y, x); } return 0; }
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { *targetContentOffset = [self endPointWithTargetPoint:*targetContentOffset scrollView:scrollView]; }
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (!decelerate) { self.currentAngle = [self normalizeAngle:self.previousAngle]; [self rotateWithAngle:self.currentAngle]; } }
@property (nonatomic, readonly) NSInteger value;
- (NSInteger)value { NSInteger value = self.currentAngle > 0 ? floorf(self.currentAngle / self.angleStep) - self.numberOfLabels : floorf(self.currentAngle / self.angleStep); return labs(value) % self.numberOfLabels; }
@protocol AYNCircleViewDelegate <NSObject> @optional - (void)circleViewWillRotate:(AYNCircleView *)circleView; - (void)circleView:(AYNCircleView *)circleView didRotateWithValue:(NSUInteger)value; @end
AYNCircleView
: @property (weak, nonatomic) id<AYNCircleViewDelegate> delegate;
- (void)rotateWithAngle:
- (void)rotateWithAngle:(CGFloat)angle { if (self.delegate && [self.delegate respondsToSelector:@selector(circleViewWillRotate:)]) { [self.delegate circleViewWillRotate:self]; } [UIView animateWithDuration:0.1 animations:^{ self.contentView.transform = CGAffineTransformMakeRotation(angle); } completion:^(BOOL finished) { if (self.delegate && [self.delegate respondsToSelector:@selector(circleView:didRotateWithValue:)]) { [self.delegate circleView:self didRotateWithValue:self.value]; } }]; }
AYNViewController
. self.circleView.delegate = self;
Interface Builder
label
, which displays the value. @property (weak, nonatomic) IBOutlet UILabel *valueLabel;
#pragma mark - Circle View Delegate - (void)circleView:(AYNCircleView *)circleView didRotateWithValue:(NSUInteger)value { self.valueLabel.text = [NSString stringWithFormat:@"%ld", value]; }
Source: https://habr.com/ru/post/326388/
All Articles