前言:看了幾篇簡書,九宮格密碼解鎖,看著不錯,拿來學習一下。
一、實現效果
實現效果
二、手勢解鎖實現過程
分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<span class="hljs-preprocessor">#<span class="hljs-number">1.</span>監聽手指在view上的移動,首先肯定需要自定義一個view,重寫touch began,touch move等方法,</span> 當手指移動到圈上時,讓其變亮。可以通過button按鈕來實現。 <span class="hljs-preprocessor">#<span class="hljs-number">2.</span>介面搭建--【九宮格】程式碼的方式建立<span class="hljs-number">9</span>個按鈕</span> <span class="hljs-number">1</span>).背景圖片 <span class="hljs-number">2</span>).九個按鈕 (把九個按鈕作為一個整體,使用一個大的view來管理這些小的view,這些小的view就是<span class="hljs-number">9</span>個button)。 <span class="hljs-number">3</span>).新建一個類,對自定義的view進行管理,這個view是從storyboard建立出來的。 會呼叫aweakFrameNib方法和layoutSubviews方法,前者創造控制元件,後者,設定按鈕frame。 <span class="hljs-number">4</span>).監聽手指的移動。分析程式,應該監聽手指的移動,而不是按鈕的點選,當手指移動到按鈕的範圍內時,讓按鈕變亮。 (<span class="hljs-number">1</span>)重寫touchesbegan...方法 <span class="hljs-number">1.</span>獲取按下的點 <span class="hljs-number">2.</span>判斷觸控的位置是否在按鈕的範圍內(使用<span class="hljs-keyword">for</span>迴圈) 提示: 一個判斷點是否在指定範圍內的方法——CGRectContainsPoint(,); (<span class="hljs-number">2</span>)重新touchesmoved...方法 說明:當手指移動到按鈕上的時候,按鈕變亮,因此需要重寫touchesmoved方法。 <span class="hljs-number">1.</span>獲取觸控的點 <span class="hljs-number">2.</span>判斷觸控的點是否在按鈕的範圍內。 提示:可以把上面兩個功能分別進行封裝,在使用的時候直接呼叫即可。 <span class="hljs-preprocessor">#<span class="hljs-number">3</span>繪製線段</span> 思路:獲取為選中狀態的按鈕,並把它們存到一個陣列中,重寫drawRect方法,從陣列中取出所有的按鈕,連線所有按鈕的中點。 注意:陣列中不能存空值,在儲存之前需要先進行判斷。 新的問題:已經被連過的按鈕,不能再連線。(在儲存按鈕的時候判斷,如果該按鈕已經被連線,那麼就不再新增到陣列中)。 繪製線段 <span class="hljs-number">1.</span>獲取上下文 <span class="hljs-number">2.</span>取出按鈕(起點和終點) <span class="hljs-number">3.</span>渲染 |
如圖所示:
①設定控制器view背景圖片
[解析]:拖入圖片素材,並設定控制器View的背景圖片
設定控制器view背景圖片
②自定義view並與控制器中新拖入的view進行關聯
自定義view並與控制器中新拖入的view進行關聯
③搭建UI
控制元件佈局
設定觸控點,實現兩個代理
④建立儲存選中按鈕的陣列,並把選中按鈕新增其中,畫線重繪
- 解決問題:已經被連過的按鈕,不能再連線。
[解析]:
1.由於每次畫線的時候,我們都會呼叫touchbegin和touchmove方法,如果每次選中的按鈕都在你觸控的範圍內,都會新增到選中按鈕的陣列中。這樣,就會造成重複新增按鈕。即第二次,觸控已經選中的按鈕,同樣也在你觸控的範圍內,這是同樣也會新增到選中按鈕的陣列中。為了解決這個問題,我們可以在touchbegin和touchmove的判斷中加一個條件 !btn.highlighted。如程式碼,意思是當你第二次,重複觸控同一個按鈕時,如果他在你觸控的範圍內且按鈕的狀態不是高亮狀態,即向下執行。
1 |
if (CGRectContainsPoint(btn.frame, loc)&& !btn.highlighted) |
2.還有個問題就是,當你在連線按鈕的過程中,在空白間隙停止觸控,這樣,會產生多餘的線。要解決這個問題,首先我們要宣告一個多餘線段的點CGpoint型別。其次,獲取多餘線段的點,多餘線段的點就等於你所觸控獲取的點,進行一下關聯。然後,把多餘的線段畫出來。最後,在touchend這個方法內,也就是當觸控完畢之後,那個多餘的點,就等於,選中按鈕陣列中最後一個按鈕的中心點。在重繪一下,就OK。
避免重複新增按鈕
多餘線段的解決圖1
多餘線段的解決圖2
多餘線段的解決圖3
④驗證密碼
[解析]:對與驗證密碼這塊,基本的思路是根據選中按鈕的tag值,來驗證使用者設定的手勢密碼是否與之對等。換句話來說,我們新增在自定義view的按鈕,當每個按鈕被觸碰時,都會變成高亮狀態,被新增到高亮狀態的陣列中。手勢密碼也就相當於(0~9)的密碼串排序。手勢密碼驗證是在,觸控結束後驗證的。所以我們要想驗證密碼,必須在touchend方法裡遍歷高亮陣列獲取按鈕的tag值。並存入可變字串陣列中,與自己設定的手勢密碼字串進行對比。
設定按鈕的tag值
密碼驗證正確:按鈕高亮狀態消失線消失
不正確:按鈕紅色,線消失:按鈕狀態消失
不正確:按鈕紅色,線消失:按鈕狀態消失
要想線消失
高亮狀態消失線消失
- 程式碼展示:
-
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296//1.介面 ,九個按鈕 , 設定frame//2. 設定按鈕的高亮狀態//3. 畫線//4. 驗證密碼是否正確//5. 正確: 按鈕高亮狀態消失, 線消失//6.不正確: 按鈕紅色, 線消失: 按鈕狀態消失//7. 多出來的一截線#import "CZView.h"@interface CZView ()//選中按鈕的陣列/*** <#Description#>*/@property (nonatomic,strong) NSMutableArray <UIButton *> *selectedArray;//線條顏色的屬性@property(nonatomic,strong)UIColor *lineColor;//接收 多餘的點@property(nonatomic,assign)CGPoint destdationPoint;@end@implementation CZView- (UIColor *)lineColor{if (!_lineColor) {_lineColor = [UIColor whiteColor];}return _lineColor;}- (NSMutableArray<UIButton *> *)selectedArray{if (!_selectedArray) {_selectedArray = [NSMutableArray array];}return _selectedArray;}#pragma mark - 3.0畫線//畫線- (void)drawRect:(CGRect)rect {//建立路徑UIBezierPath *path = [UIBezierPath bezierPath];for (NSInteger i = 0; i < self.selectedArray.count; i++) {//起點if (i == 0) {[path moveToPoint:self.selectedArray[i].center];}else{[path addLineToPoint:self.selectedArray[i].center];}//終點}//畫多出來的線if (self.selectedArray.count > 0) {[path addLineToPoint:self.destdationPoint];}//設定顏色[self.lineColor set];//渲染[path stroke];}#pragma mark - 2.0 設定按鈕的高亮//1. 觸控的位置//2. 觸控的按鈕 高亮//3. 判斷你觸控的點 是否在按鈕的範圍內: 如果是存在的 設定高亮- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{//1. 觸控的位置UITouch *touch = touches.anyObject;CGPoint loc = [touch locationInView:touch.view];self.destdationPoint = loc;//2. 觸控的按鈕 高亮//3. 判斷你觸控的點 是否在按鈕的範圍內: 如果是存在的 設定高亮for (NSInteger i = 0; i < self.subviews.count; i++) {UIButton *btn = self.subviews[i];//&& !btn.highlighted 避免重複新增 已經高亮的按鈕if (CGRectContainsPoint(btn.frame, loc) && !btn.highlighted) {//如果是存在的//設定高亮btn.highlighted = YES;//新增到選中按鈕中[self.selectedArray addObject:btn];}}//重繪[self setNeedsDisplay];}- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{//1. 觸控的位置UITouch *touch = touches.anyObject;CGPoint loc = [touch locationInView:touch.view];//接收 多出來的點self.destdationPoint = loc;//2. 觸控的按鈕 高亮//3. 判斷你觸控的點 是否在按鈕的範圍內: 如果是存在的 設定高亮for (NSInteger i = 0; i < self.subviews.count; i++) {UIButton *btn = self.subviews[i];if (CGRectContainsPoint(btn.frame, loc)&& !btn.highlighted) {//如果是存在的//設定高亮btn.highlighted = YES;//新增到選中按鈕中[self.selectedArray addObject:btn];}}//重繪[self setNeedsDisplay];}#pragma mark - 4.0 驗證密碼- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{//設定 多出來的點 在 手指抬起的時候為 選中按鈕集合的最後一個self.destdationPoint = [[self.selectedArray lastObject] center];//1.獲取密碼NSMutableString *pwd = [NSMutableString string];for(UIButton *btn in self.selectedArray){//拼接密碼[pwd appendFormat:@"%@",@(btn.tag)];}//2. 驗證if([pwd isEqualToString:@"012"]){//正確// 正確: 按鈕高亮狀態消失, 線消失[self clearPath];}else{//不正確: 按鈕紅色, 線消失: 按鈕狀態消失for (UIButton *btn in self.selectedArray) {//按鈕的狀態 不能同時存在btn.highlighted = NO;btn.selected = YES;}//設定 線條顏色 為紅色self.lineColor = [UIColor redColor];//重繪[self setNeedsDisplay];//關閉 互動self.userInteractionEnabled = NO;//延遲dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{[self clearPath];//開啟互動self.userInteractionEnabled = YES;});}}//清空畫線集合- (void)clearPath{//將原先的紅色 在設定為白色self.lineColor = [UIColor whiteColor];//取消按鈕的高亮for(UIButton *btn in self.selectedArray){btn.highlighted = NO;btn.selected = NO;}//清空 畫線的集合[self.selectedArray removeAllObjects];//重繪[self setNeedsDisplay];}#pragma mark - 1.0新增9個按鈕//1. aweakformnib//2. 懶載入//建立9個按鈕- (void)awakeFromNib{for(NSInteger i = 0;i < 9;i++){//建立按鈕UIButton *btn = [[UIButton alloc]init];//設定tag 是為了驗證密碼btn.tag = i;//關閉按鈕的互動 是為了 觸控事件btn.userInteractionEnabled = NO;//設定背景圖片[btn setBackgroundImage:[UIImage imageNamed:@"gesture_node_normal"] forState:UIControlStateNormal];//設定按鈕的高亮圖片[btn setBackgroundImage:[UIImage imageNamed:@"gesture_node_highlighted"] forState:UIControlStateHighlighted];//設定按鈕的選中狀態[btn setBackgroundImage:[UIImage imageNamed:@"gesture_node_error"] forState:UIControlStateSelected];//新增[self addSubview:btn];}}//設定按鈕的frame- (void)layoutSubviews{[super layoutSubviews];for (NSInteger i = 0; i < self.subviews.count; i++) {//九宮格佈局CGFloat W = self.bounds.size.width;CGFloat H = self.bounds.size.height;CGFloat btnW = 74;CGFloat btnH = 74;//計算間隔//列數NSInteger columns = 3;// 總寬度 - 3個按鈕的寬度 / 2CGFloat margW = (W - columns * btnW)/(columns - 1);CGFloat margH = margW;//列的索引NSInteger col = i % columns;//行的索引NSInteger row = i / columns;CGFloat btnX = col * (margW + btnW);CGFloat btnY = row * (margH + btnH);//設定按鈕的frameUIButton *btn = self.subviews[i];btn.frame = CGRectMake(btnX, btnY, btnW, btnH);}}
知識點補充
1.九宮格實現原理
12345678910介面是一個九宮格的佈局.九宮格實現思路.<span class="hljs-number">1.</span>先確定有多少列 cloum = <span class="hljs-number">3</span>;<span class="hljs-number">2.</span>計算出每列之間的距離<span class="hljs-number">2.1</span>計算為: CGFloat margin = (當前View的寬度 - 列數 * 按鈕的寬度) / (總列數 - <span class="hljs-number">1</span>)<span class="hljs-number">3.</span>每一列的X的值與它當前所在的列有關<span class="hljs-number">3.1</span>列號:curColum = i % cloum<span class="hljs-number">4.</span>每一行的Y的值與它當前所在的行有關<span class="hljs-number">4.1</span>行號:curRow = i / cloum<span class="hljs-number">5.</span>每一個按鈕的X值為, margin + 當前所在的列 * (按鈕的寬度+ 每個按鈕之間的間距)<span class="hljs-number">6.</span>每一個按鈕的Y值為 當前所在的行 * (按鈕的寬度 + 每個按鈕之間的距離)