美文网首页iOS开源项目iOS开发iOS月报
iOS-模仿苹果闹钟时间选择工具

iOS-模仿苹果闹钟时间选择工具

作者: DevinWu | 来源:发表于2018-09-11 20:24 被阅读5次

最近看了苹果自带应用时钟上的时间选择工具感觉挺巧妙,就尝试着模仿它做出一个控件工具。下面是做出来的效果。


AppleAlram.gif

根据时钟选择工具上面的功能,大概可以确定,圆环的绘制我们可以通过CAShapeLayer结合UIBezierPath绘制出来,当拖动起始点或者结束点View时,通过手势判断拖动的角度,从而改变UIBezierPath的角度,并且让起始点或者结束点View根据拖动的角度对中心点进行公转,并且自身进行自转。如果拖动圆环的话,改变UIBezierPath角度的同时,还要让起始点和结束点View同时进行公转和自转。

1.时钟AlarmView

时钟View是该控件的核心区,里面包含了图形的绘制和旋转,旋转类型的判断等多种处理。
核心代码:

-(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;
}

/** 改变圆环位置 */
-(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

根据上面圆盘的效果,拖动手势时,我们需要知道起始点与拖动点之间的角度和方向。
核心代码:

/**拖动结束后自动重置 */
- (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)²+(x2-x1)²=d²  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传送门
如果喜欢请不吝赐您的小心心,谢谢。

相关文章

  • iOS-模仿苹果闹钟时间选择工具

    最近看了苹果自带应用时钟上的时间选择工具感觉挺巧妙,就尝试着模仿它做出一个控件工具。下面是做出来的效果。 根据时钟...

  • 81讲 选择软硬件的一些基本理念。

    1-工具要选择,尽量全平台的工具。 ominifoucs为苹果系统 2-选择最新的,最好的和最贵的,不要把时间浪费...

  • 苹果闹钟

    最近半个月以来,几乎每天早上都是五点半到六点左右就起床了。 起床、晨跑听书、洗澡、做早饭,一步一步不紧不慢地进行着...

  • 选择软硬件的基本理念

    1,工具尽量选择全平台的。硬件选择苹果。 2,软件要选择最新,最贵,最好的。新意味着工具最全面的。 3,要选择好用...

  • 【慢点俱乐部】易效能初级学习100天第一阶

    二组 陈华 2017年11月1日 【81】-【90】工具运用 【81】苹果系列工具的选择 Apple watch,...

  • 81 82

    81选择软硬件的一些基本理念 工具选择全平台 最新最贵最好 选择一个好用的,不要选太多82苹果各设备工具推荐 手机...

  • GTD,时间管理

    GTD,时间管理 GTD工具的选择 适合自己的就是最好的。日常使用,推荐苹果自带的提醒事项,虽然功能不多,但是很直...

  • AVPlayer音乐锁屏功能

    [iOS]iOS AudioSession详解 Category选择 听筒扬声器切换iOS- 关于AVAudioS...

  • iOS-模仿颜色渐变

    原图效果 我的实现 // *颜色渐变的常用代码

  • 时间管理课34讲 第四课:轻松早起的三种方法

    时间管理,自我管理最重要的是使用外部工具来管理自己。 工具方法一:双闹钟法 双闹钟法,顾名思义,需要你有两个闹钟。...

网友评论

本文标题:iOS-模仿苹果闹钟时间选择工具

本文链接:https://www.haomeiwen.com/subject/iliygftx.html