|
@@ -11,6 +11,19 @@
|
|
|
|
|
|
#import "IACircularSliderTrackLayer.h"
|
|
#import "IACircularSliderTrackLayer.h"
|
|
|
|
|
|
|
|
+@interface IACircularSlider ()
|
|
|
|
+
|
|
|
|
+@property (nonatomic, assign) CGFloat radius; //半径
|
|
|
|
+@property (nonatomic, assign) CGPoint drawCenter; //绘制圆的圆心
|
|
|
|
+@property (nonatomic, assign) CGPoint circleStartPoint; //起始位置
|
|
|
|
+@property (nonatomic, assign) CGFloat angle; //转过的角度
|
|
|
|
+@property (nonatomic, assign) CGFloat circleRadius;
|
|
|
|
+
|
|
|
|
+@property (nonatomic, assign) BOOL lockClockwise; //禁止顺时针转动
|
|
|
|
+@property (nonatomic, assign) BOOL lockAntiClockwise; //禁止逆时针转动
|
|
|
|
+
|
|
|
|
+@end
|
|
|
|
+
|
|
|
|
|
|
@implementation IACircularSlider
|
|
@implementation IACircularSlider
|
|
{
|
|
{
|
|
@@ -22,6 +35,9 @@
|
|
BOOL _isInitiallySet;
|
|
BOOL _isInitiallySet;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+- (void)dealloc {
|
|
|
|
+ [self removeObserver:self forKeyPath:@"angle"];
|
|
|
|
+}
|
|
|
|
|
|
- (void)resetInitially {
|
|
- (void)resetInitially {
|
|
_isInitiallySet = NO;
|
|
_isInitiallySet = NO;
|
|
@@ -93,6 +109,16 @@
|
|
|
|
|
|
_isInitiallySet = NO;
|
|
_isInitiallySet = NO;
|
|
[self updateLayers];
|
|
[self updateLayers];
|
|
|
|
+
|
|
|
|
+ self.radius = 103;
|
|
|
|
+ self.drawCenter = CGPointMake(233/2.0f, 233 / 2.0);
|
|
|
|
+ self.circleStartPoint = CGPointMake(self.drawCenter.x, self.drawCenter.y - self.radius);
|
|
|
|
+ self.lockAntiClockwise = NO;
|
|
|
|
+ self.lockClockwise = NO;
|
|
|
|
+ [self addObserver:self
|
|
|
|
+ forKeyPath:@"angle"
|
|
|
|
+ options:NSKeyValueObservingOptionNew
|
|
|
|
+ context:nil];
|
|
}
|
|
}
|
|
|
|
|
|
-(CGFloat)radianForValue:(CGFloat)value{
|
|
-(CGFloat)radianForValue:(CGFloat)value{
|
|
@@ -198,7 +224,7 @@
|
|
return val;
|
|
return val;
|
|
}
|
|
}
|
|
|
|
|
|
--(CGFloat)transformedStartAngle{
|
|
|
|
|
|
+-(CGFloat)transformedStartAngle {
|
|
CGFloat sA = self.startAngle;
|
|
CGFloat sA = self.startAngle;
|
|
CGFloat offset = (2*M_PI - [self distance])/2;
|
|
CGFloat offset = (2*M_PI - [self distance])/2;
|
|
if (sA>self.endAngle) {
|
|
if (sA>self.endAngle) {
|
|
@@ -284,18 +310,14 @@
|
|
#pragma mark - Setters
|
|
#pragma mark - Setters
|
|
|
|
|
|
-(void)setStartAngle:(CGFloat)startAngle{
|
|
-(void)setStartAngle:(CGFloat)startAngle{
|
|
-// if (startAngle>2*M_PI||startAngle<0) {
|
|
|
|
-// [NSException raise:@"Invalid value of startAngle" format:@"startAngle must be more than 0 and less than 2PI"];
|
|
|
|
-// }
|
|
|
|
|
|
+
|
|
_startAngle = startAngle;
|
|
_startAngle = startAngle;
|
|
[self calculateForTouch:_lastTouch];
|
|
[self calculateForTouch:_lastTouch];
|
|
[self updateLayers];
|
|
[self updateLayers];
|
|
}
|
|
}
|
|
|
|
|
|
-(void)setEndAngle:(CGFloat)endAngle{
|
|
-(void)setEndAngle:(CGFloat)endAngle{
|
|
-// if (endAngle>2*M_PI||endAngle<0) {
|
|
|
|
-// [NSException raise:@"Invalid value of endAngle" format:@"endAngle must be more than 0 and less than 2PI"];
|
|
|
|
-// }
|
|
|
|
|
|
+
|
|
_endAngle = endAngle;
|
|
_endAngle = endAngle;
|
|
[self calculateForTouch:_lastTouch];
|
|
[self calculateForTouch:_lastTouch];
|
|
[self updateLayers];
|
|
[self updateLayers];
|
|
@@ -365,6 +387,9 @@
|
|
-(void)setFrame:(CGRect)frame{
|
|
-(void)setFrame:(CGRect)frame{
|
|
[super setFrame:frame];
|
|
[super setFrame:frame];
|
|
_center = CGPointMake(CGRectGetWidth(frame)/2, CGRectGetHeight(frame)/2);
|
|
_center = CGPointMake(CGRectGetWidth(frame)/2, CGRectGetHeight(frame)/2);
|
|
|
|
+ self.radius = CGRectGetWidth(frame)/2 - self.trackWidth;
|
|
|
|
+ self.drawCenter = CGPointMake(CGRectGetWidth(frame)/2, CGRectGetHeight(frame)/2);
|
|
|
|
+ self.circleStartPoint = CGPointMake(self.drawCenter.x, self.drawCenter.y - self.radius);
|
|
_isInitiallySet = NO;
|
|
_isInitiallySet = NO;
|
|
[self updateLayers];
|
|
[self updateLayers];
|
|
|
|
|
|
@@ -385,17 +410,46 @@
|
|
}
|
|
}
|
|
|
|
|
|
-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{
|
|
-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{
|
|
-
|
|
|
|
- [self calculateForTouch:touch];
|
|
|
|
- _lastTouch = touch;
|
|
|
|
- [self updateLayers];
|
|
|
|
-
|
|
|
|
- [self sendActionsForControlEvents:UIControlEventValueChanged];
|
|
|
|
-
|
|
|
|
- return YES;
|
|
|
|
|
|
+ if ([self calculateForTouch:touch]) {
|
|
|
|
+ _lastTouch = touch;
|
|
|
|
+ [self updateLayers];
|
|
|
|
+ [self sendActionsForControlEvents:UIControlEventValueChanged];
|
|
|
|
+ return YES;
|
|
|
|
+ }
|
|
|
|
+ return NO;
|
|
}
|
|
}
|
|
|
|
|
|
--(void)calculateForTouch:(UITouch*)touch{
|
|
|
|
|
|
+-(BOOL)calculateForTouch:(UITouch*)touch{
|
|
|
|
+ CGPoint starTouchPoint = [touch locationInView:self];
|
|
|
|
+
|
|
|
|
+ CGFloat centerX = self.drawCenter.x;
|
|
|
|
+ CGFloat centerY = self.drawCenter.y;
|
|
|
|
+
|
|
|
|
+ CGFloat moveX = starTouchPoint.x;
|
|
|
|
+ CGFloat moveY = starTouchPoint.y;
|
|
|
|
+ //到300度,禁止移动到第一,二,三象限
|
|
|
|
+ if (self.lockClockwise) {
|
|
|
|
+ if ((moveX >= centerX && moveY <= centerY) ||
|
|
|
|
+ (moveX >= centerX && moveY >= centerY) ||
|
|
|
|
+ (moveX <= centerX && moveY >= centerY)) {
|
|
|
|
+ self.angle = 360;
|
|
|
|
+ self.radian = self.endAngle;
|
|
|
|
+ self.value = self.maximumValue;
|
|
|
|
+ return YES;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //小于60度的时候,禁止移动到第二,三,四象限
|
|
|
|
+ if (self.lockAntiClockwise) {
|
|
|
|
+ if ((moveX <= centerX && moveY >= centerY) ||
|
|
|
|
+ (moveX <= centerX && moveY <= centerY) ||
|
|
|
|
+ (moveX >= centerX && moveY >= centerY)) {
|
|
|
|
+ self.angle = 0;
|
|
|
|
+ self.radian = self.startAngle;
|
|
|
|
+ self.value = self.minimumValue;
|
|
|
|
+ return YES;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
CGFloat preRad = [self radianForPoint:[touch locationInView:self]]; //radian from east
|
|
CGFloat preRad = [self radianForPoint:[touch locationInView:self]]; //radian from east
|
|
|
|
|
|
CGFloat transN = [self transformRadianForCurrentOptions:preRad forStartAngle:[self transformedStartAngle]];
|
|
CGFloat transN = [self transformRadianForCurrentOptions:preRad forStartAngle:[self transformedStartAngle]];
|
|
@@ -404,9 +458,30 @@
|
|
|
|
|
|
CGFloat reversedBoundValue = [self reverseTransformForModifiedStartAngleToDefault:boundRadianTN];
|
|
CGFloat reversedBoundValue = [self reverseTransformForModifiedStartAngleToDefault:boundRadianTN];
|
|
|
|
|
|
- self.radian = reversedBoundValue;
|
|
|
|
|
|
+ CGPoint lastPoint = [self mapRadianToPoint:reversedBoundValue];
|
|
|
|
+ CGFloat angle = [IACircularSlider calculateAngleWithRadius:self.radius
|
|
|
|
+ center:self.drawCenter
|
|
|
|
+ startCenter:self.circleStartPoint
|
|
|
|
+ endCenter:lastPoint];
|
|
|
|
+
|
|
|
|
+ if (angle >= 300) {
|
|
|
|
+ //当当前角度大于等于300度时禁止移动到第一、二、三象限
|
|
|
|
+ self.lockClockwise = YES;
|
|
|
|
+ } else {
|
|
|
|
+ self.lockClockwise = NO;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (angle <= 60.0) {
|
|
|
|
+ //当当前角度小于等于60度时,禁止移动到第二、三、四象限
|
|
|
|
+ self.lockAntiClockwise = YES;
|
|
|
|
+ } else {
|
|
|
|
+ self.lockAntiClockwise = NO;
|
|
|
|
+ }
|
|
|
|
+ self.angle = angle;
|
|
|
|
|
|
|
|
+ self.radian = reversedBoundValue;
|
|
self.value = [self boundValueForValue:[self valueForRadian:transN]];
|
|
self.value = [self boundValueForValue:[self valueForRadian:transN]];
|
|
|
|
+ return YES;
|
|
}
|
|
}
|
|
|
|
|
|
-(void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{
|
|
-(void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{
|
|
@@ -414,8 +489,63 @@
|
|
[_thumbLayer setNeedsDisplay];
|
|
[_thumbLayer setNeedsDisplay];
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
-
|
|
|
|
|
|
+/**
|
|
|
|
+ 计算圆上两点间的角度
|
|
|
|
+
|
|
|
|
+ @param radius 半径
|
|
|
|
+ @param center 圆心
|
|
|
|
+ @param startCenter 起始点坐标
|
|
|
|
+ @param endCenter 结束点坐标
|
|
|
|
+ @return 圆上两点间的角度
|
|
|
|
+ */
|
|
|
|
++ (CGFloat)calculateAngleWithRadius:(CGFloat)radius
|
|
|
|
+ center:(CGPoint)center
|
|
|
|
+ startCenter:(CGPoint)startCenter
|
|
|
|
+ endCenter:(CGPoint)endCenter {
|
|
|
|
+ //a^2 = b^2 + c^2 - 2bccosA;
|
|
|
|
+ CGFloat cosA = (2 * radius * radius - powf([IACircularSlider distanceBetweenPointA:startCenter pointB:endCenter], 2)) / (2 * radius * radius);
|
|
|
|
+ CGFloat angle = 180 / M_PI * acosf(cosA);
|
|
|
|
+ if (startCenter.x > endCenter.x) {
|
|
|
|
+ angle = 360 - angle;
|
|
|
|
+ }
|
|
|
|
+ return angle;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ 两点间的距离
|
|
|
|
+
|
|
|
|
+ @param pointA 点A的坐标
|
|
|
|
+ @param pointB 点B的坐标
|
|
|
|
+ @return 两点间的距离
|
|
|
|
+ */
|
|
|
|
++ (double)distanceBetweenPointA:(CGPoint)pointA pointB:(CGPoint)pointB {
|
|
|
|
+ double x = fabs(pointA.x - pointB.x);
|
|
|
|
+ double y = fabs(pointA.y - pointB.y);
|
|
|
|
+ return hypot(x, y);//hypot(x, y)函数为计算三角形的斜边长度
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#pragma mark - KVO
|
|
|
|
+
|
|
|
|
+//对angle添加KVO,有时候手势过快在continueTrackingWithTouch方法中不能及时限定转动,所以需要通过KVO对angle做实时监控
|
|
|
|
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
|
|
|
|
+ IACircularSlider *circleSlider = (IACircularSlider *)object;
|
|
|
|
+ NSNumber *newAngle = [change valueForKey:@"new"];
|
|
|
|
+ if ([keyPath isEqualToString:@"angle"]) {
|
|
|
|
+ if (newAngle.doubleValue >= 300 ||
|
|
|
|
+ circleSlider.angle >= 300) {
|
|
|
|
+ self.lockClockwise = YES;
|
|
|
|
+ } else {
|
|
|
|
+ self.lockClockwise = NO;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (newAngle.doubleValue <= 60 ||
|
|
|
|
+ circleSlider.angle <= 60) {
|
|
|
|
+ self.lockAntiClockwise = YES;
|
|
|
|
+ } else {
|
|
|
|
+ self.lockAntiClockwise = NO;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
/*
|
|
/*
|
|
// Only override drawRect: if you perform custom drawing.
|
|
// Only override drawRect: if you perform custom drawing.
|
|
// An empty implementation adversely affects performance during animation.
|
|
// An empty implementation adversely affects performance during animation.
|