iOS-模仿蘋果時鐘選擇控件

最近看了蘋果自帶應用時鐘上的時間選擇工具感覺挺巧妙的,就嘗試著模仿它做出一個控件工具。工程Demo運行效果如下:
AppleAlram.gif

根據(jù)時鐘選擇工具上面的功能,大概可以確定,圓環(huán)的繪制我們可以通過CAShapeLayer結合UIBezierPath繪制出來,當拖動起始點或者結束點View時,通過手勢判斷拖動的角度,從而改變UIBezierPath的角度,并且讓起始點或者結束點View根據(jù)拖動的角度對中心點進行公轉,并且自身進行自轉。如果拖動圓環(huán)的話,改變UIBezierPath角度的同時,還要讓起始點和結束點View同時進行公轉和自轉。

1.時鐘AlarmView

時鐘View是該控件的核心區(qū),里面包含了圖形的繪制和旋轉,旋轉類型的判斷等多種處理。
核心代碼:

-(CAShapeLayer *)alramLayer{
    
    if (!_alramLayer) {
        _alramLayer = [[CAShapeLayer alloc] init];
         _alramLayer.bounds = CGRectMake(0,0, self.bounds.size.width, self.bounds.size.height);
        _alramLayer.path = [self drawAlarmPathWithStartAngle:self.startAngle endAngle:self.endAngle].CGPath;
        _alramLayer.fillColor = UIColor.orangeColor.CGColor;
    }
    return _alramLayer;
}

#pragma mark - Method                  - Method -
-(void)beiginRotationWithAngle:(CGFloat)angle beiginPiont:(CGPoint)point{
    
    switch (self.rotationType) {
        case kRotationType_StartAngle:
            [self changeStartAngle:angle];
            break;
        case kRotationType_EndAngle:
            [self changeEndAngle:angle];
            break;
        case kRotationType_CircularingLocation:
            [self changeCircularingLocation:angle];
            break;
        default:
         break;
    }
}

/** 改變起始時間 */
-(void)changeStartAngle:(CGFloat)startAngle{
      NSLog(@"角度差 = %f",fabs(self.endAngle - self.startAngle - startAngle));
    if (fabs(self.endAngle - self.startAngle - startAngle) >360) {//修復BUG
        if (startAngle > 0) {
            startAngle = startAngle -360;
        }else{
            startAngle = startAngle +360;
        }
    }
    NSLog(@"角度差2 = %f",fabs(self.endAngle - self.startAngle - startAngle));
   self.sleepSuperView.transform = CGAffineTransformMakeRotation(kDgreesToRadoans(self.startAngle+startAngle));//公轉
   self.sleepView.transform = CGAffineTransformMakeRotation(-kDgreesToRadoans(self.startAngle+startAngle));//自轉
   self.alramLayer.path = [self drawAlarmPathWithStartAngle:startAngle+self.startAngle endAngle:self.endAngle].CGPath;
    
}

/** 改變結束時間 */
-(void)changeEndAngle:(CGFloat)endAngle{
    
    if (fabs(self.startAngle - self.endAngle - endAngle) >360) {
        if (endAngle > 0) {
            endAngle = endAngle -360;
        }else{
            endAngle = endAngle +360;
        }
    }
     NSLog(@"角度差 = %f",fabs(self.endAngle - self.startAngle - endAngle));
    self.ringSuperView.transform = CGAffineTransformMakeRotation(kDgreesToRadoans(self.endAngle+endAngle));
    self.ringView.transform = CGAffineTransformMakeRotation(-kDgreesToRadoans(self.endAngle+endAngle));
    self.alramLayer.path = [self drawAlarmPathWithStartAngle:self.startAngle endAngle:self.endAngle+endAngle].CGPath;
}

/** 改變圓環(huán)位置 */
-(void)changeCircularingLocation:(CGFloat)angle{
    
    self.sleepSuperView.transform = CGAffineTransformMakeRotation(kDgreesToRadoans(self.startAngle+angle));//公轉
    self.sleepView.transform = CGAffineTransformMakeRotation(-kDgreesToRadoans(self.startAngle+angle));//自轉
    self.ringSuperView.transform = CGAffineTransformMakeRotation(kDgreesToRadoans(self.endAngle+angle));
    self.ringView.transform = CGAffineTransformMakeRotation(-kDgreesToRadoans(self.endAngle+angle));
    self.alramLayer.path = [self drawAlarmPathWithStartAngle:self.startAngle+angle endAngle:self.endAngle +angle].CGPath;
}

/** 旋轉類型 */
-(kRotationType)rotationTypeWithPiont:(CGPoint)piont{
    
    CGPoint alarmViewCenter = CGPointMake(kAlarmViewRadius, kAlarmViewRadius);
    CGPoint startCenter = CGPointMake(cos(((_currentStartAngle-90)/180)*M_PI)*(kAlarmViewRadius-kIconViewHW/2) +kAlarmViewRadius, sin(((_currentStartAngle-90)/180)*M_PI)*(kAlarmViewRadius-kIconViewHW/2) +kAlarmViewRadius);
    CGPoint endCenter = CGPointMake(cos(((_currentEndAngle-90)/180)*M_PI)*(kAlarmViewRadius-kIconViewHW/2) +kAlarmViewRadius, sin(((_currentEndAngle-90)/180)*M_PI)*(kAlarmViewRadius-kIconViewHW/2) +kAlarmViewRadius);
    
    if ([UIView distanceBetweenPointA:alarmViewCenter AndPiontB:piont] >= kAlarmViewRadius-kIconViewHW && [UIView distanceBetweenPointA:alarmViewCenter AndPiontB:piont] <= kAlarmViewRadius) {
        if ([UIView distanceBetweenPointA:startCenter AndPiontB:piont] < kIconViewHW/2) {
            return kRotationType_StartAngle;
        }else if ([UIView distanceBetweenPointA:endCenter AndPiontB:piont] < kIconViewHW/2){
            return kRotationType_EndAngle;
        }else{
            return kRotationType_CircularingLocation;
        }
    }
    return kRotationType_None;
}

/** 繪制BezierPath */
-(UIBezierPath *)drawAlarmPathWithStartAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle{
    
    CGRect circleRect = CGRectMake(kAlarmViewRadius,kAlarmViewRadius, self.bounds.size.width, self.bounds.size.height);
    UIBezierPath* circlePath = [UIBezierPath bezierPath];
    [circlePath addArcWithCenter: CGPointMake(CGRectGetMidX(circleRect), CGRectGetMidY(circleRect)) radius: circleRect.size.width/2 startAngle: kDgreesToRadoans(startAngle) endAngle: kDgreesToRadoans(endAngle) clockwise: YES];
    [circlePath addLineToPoint: CGPointMake(CGRectGetMidX(circleRect), CGRectGetMidY(circleRect))];
    [circlePath closePath];
    
    _currentStartAngle = fmodf(startAngle,360);
    _currentEndAngle = fmodf(endAngle, 360);
    
    self.costTimeLbl.attributedText = [self timeBlockWithAngle:_currentEndAngle - _currentStartAngle];
    self.beginTime = [self beginTimeWithAngle:_currentStartAngle];
    self.endTime = [self endTimeWithAngle:_currentEndAngle];
    
    if (self.delegate && [self.delegate respondsToSelector:@selector(alramViewIsChangedWithBeginTime:endTime:)]) {
        [self.delegate alramViewIsChangedWithBeginTime:self.beginTime endTime:self.endTime];
    }
    return circlePath;
}

2.手勢的處理類 DWRotaionGestureRecognizer

根據(jù)上面圓盤的效果,拖動手勢時,我們需要知道起始點與拖動點之間的角度和方向。
核心代碼:

/**拖動結束后自動重置 */
- (void)reset
{
    [super reset];
//    _previousRotation = [self rotation];
    _previousRotation = 0;
    _currentRotation = 0;
}

#pragma mark - eventResponse                - Method -
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    
    self.startingPoint = [[touches anyObject] locationInView:self.view];
    self.state = UIGestureRecognizerStateBegan;
    
    if (self.rotaionGestureRecognizerDelegate && [self.rotaionGestureRecognizerDelegate respondsToSelector:@selector(touchesBegan:withEvent:)]) {
        [self.rotaionGestureRecognizerDelegate touchesBegan:touches withEvent:event];
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
    
    CGPoint point = [[touches anyObject] locationInView:self.view];
     self.currentRotation = [UIView angleBetweenPoint1:self.startingPoint point2:point AndCenter:self.center];
    self.state = UIGestureRecognizerStateChanged;
    
    if (self.rotaionGestureRecognizerDelegate && [self.rotaionGestureRecognizerDelegate respondsToSelector:@selector(touchesMoved:withEvent:)]) {
        [self.rotaionGestureRecognizerDelegate touchesMoved:touches withEvent:event];
    }
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    
    self.endPoint = [[touches anyObject] locationInView:self.view];
    self.currentRotation = [UIView angleBetweenPoint1:self.startingPoint point2:self.endPoint AndCenter:self.center];
    self.state = UIGestureRecognizerStateEnded;
    
    if (self.rotaionGestureRecognizerDelegate && [self.rotaionGestureRecognizerDelegate respondsToSelector:@selector(touchesEnded:withEvent:)]) {
        [self.rotaionGestureRecognizerDelegate touchesEnded:touches withEvent:event];
    }
}

- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesCancelled:touches withEvent:event];
    self.state = UIGestureRecognizerStateCancelled;
    
    if (self.rotaionGestureRecognizerDelegate && [self.rotaionGestureRecognizerDelegate respondsToSelector:@selector(touchesCancelled:withEvent:)]) {
        [self.rotaionGestureRecognizerDelegate touchesCancelled:touches withEvent:event];
    }
}
計算兩點間角度和距離的類別 UIView+DWAngle

核心代碼:

/** 兩個坐標點的角度 */
+ (CGFloat)angleBetweenPoint1:(CGPoint)first point2:(CGPoint)second AndCenter:(CGPoint)center{
     // θ=arctan[(y2-y0)/(x2-x0)]-arctan[(y1-y0)/(x1-x0)];
    CGPoint centeredPoint1 = CGPointMake(first.x - center.x, first.y - center.y);
    CGPoint centeredPoint2 = CGPointMake(second.x - center.x, second.y - center.y);
    
    CGFloat firstAngle = angleBetweenOriginAndPointA(centeredPoint1);
    CGFloat secondAngle = angleBetweenOriginAndPointA(centeredPoint2);
    
    CGFloat rads = secondAngle - firstAngle;
    
    return rads;
}

/** 兩點的距離 */
+(CGFloat)distanceBetweenPointA:(CGPoint)pointA AndPiontB:(CGPoint)pointB{
    // (y2-y1)2+(x2-x1)2=d2  sqrt(<#double#>)   pow(5, 2)
    CGFloat a = pow(pointB.x-pointA.x, 2);
    CGFloat b = pow(pointB.y-pointA.y, 2);
    
    return sqrt(a+b);
}

/** 某點和原點間的角度 */
+(CGFloat)angleBetweenOriginAndPointA:(CGPoint)p{
    return angleBetweenOriginAndPointA(p);
}

CGFloat angleBetweenOriginAndPointA(CGPoint p) {
    if (p.x  == 0) {
        return signA(p.y) * M_PI;
    }
    
    CGFloat angle = atan(-p.y / p.x); // '-' because negative ordinates are positive in UIKit
    
    // atan() is defined in [-pi/2, pi/2], but we want a value in [0, 2*pi]
    // so we deal with these special cases accordingly
    switch (quadrantForPointA(p)) {
        case 1:
        case 2: angle += M_PI; break;
        case 3: angle += 2* M_PI; break;
    }
    return angle;
}

/** 點的象限 */
NSInteger quadrantForPointA(CGPoint p) {
    if (p.x > 0 && p.y < 0) {
        return 0;
    } else if (p.x < 0 && p.y < 0) {
        return 1;
    } else if (p.x < 0 && p.y > 0) {
        return 2;
    } else if (p.x > 0 && p.y > 0)  {
        return 3;
    }
    return 0;
}

NSInteger signA(CGFloat num) {
    if (num == 0) {
        return 0;
    } else if (num > 0) {
        return 1;
    } else {
        return -1;
    }
}

以上是核心代碼部分,感興趣的讀者可以到Github進行查閱:Github傳送門
希望項目工程對您有所幫助,謝謝閱讀。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 在iOS中隨處都可以看到絢麗的動畫效果,實現(xiàn)這些動畫的過程并不復雜,今天將帶大家一窺ios動畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,686評論 6 30
  • 常用的表格圖繪制主要用到折線圖和餅圖。也有不錯的第三方框架,比如:Charts。如果不是專門做統(tǒng)計的,沒有必要引入...
    張小西的BUG閱讀 1,082評論 0 1
  • 前言 本文只要描述了iOS中的Core Animation(核心動畫:隱式動畫、顯示動畫)、貝塞爾曲線、UIVie...
    GitHubPorter閱讀 3,735評論 7 11
  • 目錄 ** UIView 動畫 ** ** Core Animation ** ** FaceBook POP動畫...
    方向_4d0d閱讀 1,814評論 0 3
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉變要...
    余生動聽閱讀 10,798評論 0 11

友情鏈接更多精彩內(nèi)容