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

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

根據(jù)時鐘選擇工具上面的功能送讲,大概可以確定济竹,圓環(huán)的繪制我們可以通過CAShapeLayer結(jié)合UIBezierPath繪制出來,當(dāng)拖動起始點(diǎn)或者結(jié)束點(diǎn)View時冤寿,通過手勢判斷拖動的角度歹苦,從而改變UIBezierPath的角度,并且讓起始點(diǎn)或者結(jié)束點(diǎn)View根據(jù)拖動的角度對中心點(diǎn)進(jìn)行公轉(zhuǎn)督怜,并且自身進(jìn)行自轉(zhuǎn)暂氯。如果拖動圓環(huán)的話,改變UIBezierPath角度的同時亮蛔,還要讓起始點(diǎn)和結(jié)束點(diǎn)View同時進(jìn)行公轉(zhuǎn)和自轉(zhuǎn)。

1.時鐘AlarmView

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

-(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) {//修復(fù)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));//公轉(zhuǎn)
   self.sleepView.transform = CGAffineTransformMakeRotation(-kDgreesToRadoans(self.startAngle+startAngle));//自轉(zhuǎn)
   self.alramLayer.path = [self drawAlarmPathWithStartAngle:startAngle+self.startAngle endAngle:self.endAngle].CGPath;
    
}

/** 改變結(jié)束時間 */
-(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));//公轉(zhuǎn)
    self.sleepView.transform = CGAffineTransformMakeRotation(-kDgreesToRadoans(self.startAngle+angle));//自轉(zhuǎn)
    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;
}

/** 旋轉(zhuǎn)類型 */
-(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ù)上面圓盤的效果动遭,拖動手勢時芬探,我們需要知道起始點(diǎn)與拖動點(diǎn)之間的角度和方向。
核心代碼:

/**拖動結(jié)束后自動重置 */
- (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];
    }
}
計算兩點(diǎn)間角度和距離的類別 UIView+DWAngle

核心代碼:

/** 兩個坐標(biāo)點(diǎn)的角度 */
+ (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;
}

/** 兩點(diǎn)的距離 */
+(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);
}

/** 某點(diǎn)和原點(diǎn)間的角度 */
+(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;
}

/** 點(diǎn)的象限 */
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進(jìn)行查閱:Github傳送門
希望項目工程對您有所幫助偷仿,謝謝閱讀哩簿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市酝静,隨后出現(xiàn)的幾起案子节榜,更是在濱河造成了極大的恐慌,老刑警劉巖别智,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宗苍,死亡現(xiàn)場離奇詭異,居然都是意外死亡薄榛,警方通過查閱死者的電腦和手機(jī)讳窟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來敞恋,“玉大人丽啡,你說我怎么就攤上這事∮裁ǎ” “怎么了补箍?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長浦徊。 經(jīng)常有香客問我馏予,道長,這世上最難降的妖魔是什么盔性? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任霞丧,我火速辦了婚禮,結(jié)果婚禮上冕香,老公的妹妹穿的比我還像新娘蛹尝。我一直安慰自己,他們只是感情好悉尾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布突那。 她就那樣靜靜地躺著,像睡著了一般构眯。 火紅的嫁衣襯著肌膚如雪愕难。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天惫霸,我揣著相機(jī)與錄音猫缭,去河邊找鬼。 笑死壹店,一個胖子當(dāng)著我的面吹牛猜丹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播硅卢,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼射窒,長吁一口氣:“原來是場噩夢啊……” “哼藏杖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起脉顿,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蝌麸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后弊予,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祥楣,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年汉柒,在試婚紗的時候發(fā)現(xiàn)自己被綠了误褪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡碾褂,死狀恐怖兽间,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情正塌,我是刑警寧澤嘀略,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站乓诽,受9級特大地震影響帜羊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鸠天,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一讼育、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧稠集,春花似錦奶段、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至晦鞋,卻和暖如春蹲缠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背悠垛。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工线定, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鼎文。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像因俐,于是被迫代替她去往敵國和親拇惋。 傳聞我的和親對象是個殘疾皇子周偎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 在iOS中隨處都可以看到絢麗的動畫效果,實現(xiàn)這些動畫的過程并不復(fù)雜撑帖,今天將帶大家一窺ios動畫全貌蓉坎。在這里你可以看...
    每天刷兩次牙閱讀 8,485評論 6 30
  • 常用的表格圖繪制主要用到折線圖和餅圖。也有不錯的第三方框架胡嘿,比如:Charts蛉艾。如果不是專門做統(tǒng)計的,沒有必要引入...
    張小西的BUG閱讀 998評論 0 1
  • 前言 本文只要描述了iOS中的Core Animation(核心動畫:隱式動畫衷敌、顯示動畫)勿侯、貝塞爾曲線、UIVie...
    GitHubPorter閱讀 3,621評論 7 11
  • 目錄 ** UIView 動畫 ** ** Core Animation ** ** FaceBook POP動畫...
    方向_4d0d閱讀 1,596評論 0 3
  • 今天感恩節(jié)哎缴罗,感謝一直在我身邊的親朋好友助琐。感恩相遇!感恩不離不棄面氓。 中午開了第一次的黨會兵钮,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,562評論 0 11