天天看點

iOS UIBezierPath實作手勢解鎖

一、先來看一下最終效果

iOS UIBezierPath實作手勢解鎖
iOS UIBezierPath實作手勢解鎖

二、需要用到的主要知識

  1. viewController中點選,移動,點選結束事件的處理
  2. UIBezierPath的使用
  3. 重寫drawRect的使用

三、實作的具體步驟

1.ViewController中

我們直接使用view的layer的contents屬性來設定背景圖檔

- (void)viewDidLoad {
    [super viewDidLoad];
    //設定背景
    self.view.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"Home_refresh_bg"].CGImage);
}
           

 這裡需要注意的是CGImage,同時還需要前面的 (__bridge id _Nullable) 橋接。

2.自定義類UnlockView

2.1 畫線

為什麼要自定義UnlockView呢?為什麼不直接在ViewContrller中寫呢?是,是可以寫到ViewController中,但是那樣子產品性就不強,如果以後需要用到這個類的時候,直接就可以使用已經寫好的類的了,同時新寫一個類可以讓我們的代碼更加專注于管理這個類,如果寫在ViewController中就會很冗雜。

UnlockView.m
-(void)awakeFromNib{
    [super awakeFromNib];
    self.selectedArray = [NSMutableArray array];
    self.backgroundColor = [UIColor clearColor];
    for(int i =0;i<9;i++){
       //建立按鈕對象
        UIButton * button = [[UIButton alloc]initWithFrame:CGRectZero];
        //設定圖檔
        [button setImage:[UIImage imageNamed:@"gesture_node_normal"]             forState:UIControlStateNormal];
        [button setImage:[UIImage imageNamed:@"gesture_node_selected"] forState:UIControlStateSelected];
        //關閉按鈕的互動能力
        button.userInteractionEnabled = NO;
        button.tag = i;
        [self addSubview:button];
    }
}
           

由于我們的UnlockView的整個背景View是在storyboard中拖拽上去的,當我們需要進行按鈕的初始化的時候,需要在awakeFromNib方法中寫,在這個方法中的 selectArray 表示的是我們選擇到的按鈕。為什麼建立按鈕時給的frame是 CGRectzero呢?因為控件是通過storyboard或者xib來建立的,需要自己添加的控件的frame在awakeFromNib中可能因為擷取不及時而導緻不正确,最終布局出現偏差,是以我們在  layoutSubviews中做按鈕的布局:

-(void)layoutSubviews{
    for(int i = 1;i<self.subviews.count;i++){
        UIButton * button = [self.subviews objectAtIndex:i];
        //确定是第幾列
        int column = (i-1)%3;
        int row = (i-1)/3;
        button.frame = CGRectMake(kPadding+(74+kPadding)*column, kPadding+(74+kPadding)*row, 74, 74);
    }
}
           

這裡就是典型的九宮格布局,不用多說。至于為什麼從1開始,因為最後我們還需要加一個提示的UILabel,這個UILabel是加在按鈕的前面的,是以從1開始。

按鈕布局好了之後,我們就開始做響應按鈕點選的事件,有三個,touchesBegain,touchMoved,touchesEnded:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //擷取觸摸點坐标
    UITouch * touch = [touches anyObject];
    CGPoint location = [touch locationInView:self];
    //判斷某一個點是否在區域内
    for (UIButton *button in self.subviews) {
        if (CGRectContainsPoint(button.frame, location)) {
            //設定按鈕的狀态
            button.selected = YES;
            [self.selectedArray addObject:button];
        }
    }
}
           

在剛剛點選上去的時候,我們需要判斷目前的點選點是否包含在button的區域内,如果在就改變button的selected屬性,然後将這個選中的按鈕加入selectArray數組中。

-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //擷取觸摸點坐标
    UITouch * touch = [touches anyObject];
    CGPoint location = [touch locationInView:self];
    //儲存目前手的觸摸點
    self.lastPoint = location;
    //判斷某一個點是否在區域内
    for (UIButton *button in self.subviews) {
        if (CGRectContainsPoint(button.frame, location)) {
            if (button.selected==NO) {
                //設定按鈕的狀态
                button.selected = YES;
                [self.selectedArray addObject:button];
            }
        }
    }
    //重新整理界面
    //相當于觸發drawRect方法
    [self setNeedsDisplay];
}
           

在touchesMoved方法中,同樣判斷是否點選在了button中,同時為了保證點亮過的button不會重複加入selectArray中,還需要加一個判斷,判斷目前的點選點所在的button是否已經被點亮過了。隻有當目前button沒有被點亮過的情況下才将它點亮,然後加入selectArray數組中。在這個方法中,我們還加入了一個lastpoint變量,它用來記錄每次move的坐标,實際上就是每次手在滑動的時的位置,在劃線的保證畫到我們手的點選處。最後的setNeedDisplay方法調用  drawRect方法,我們在drawRect方法中寫具體的畫線路徑,以達動态畫線的效果。

接下來的touchededEnd主要是判斷密碼的正确與否的邏輯代碼,我們後面再說,先來看看畫線的代碼:

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    //在這裡面畫線
    //1.确定畫的路徑
    UIBezierPath * bpath = [UIBezierPath bezierPath];
    //确定線的起始點數組裡面第一個按鈕
    for (int i =0; i<self.selectedArray.count; i++) {
        UIButton * button = [self.selectedArray objectAtIndex:i];
        //如果是第一個 隻需要将path的起始點設到這個按鈕的中心
        if (i==0) {
            [bpath moveToPoint:button.center];
        }else{
            //否則就需要畫線到按鈕的中心點
            [bpath addLineToPoint:button.center];
        }
    }
    //在最後一個按鈕和目前觸摸點之間畫一條線
    [bpath addLineToPoint:_lastPoint];
    //2.畫上去
    //設定線條的寬度
    bpath.lineWidth = 5;
    //設定線條的連接配接處樣式  為圓滑
    bpath.lineJoinStyle = kCGLineJoinRound;
    //設定線條的顔色
    [[UIColor whiteColor]set];
    //按照路徑畫線條
    [bpath stroke];
    
}
           

這裡我們重寫drawRect方法,使用UIBezierPath進行路徑的定義。首先我們我們需要确定我們畫線的起點,我們畫線的起點應該是我們加入到selectArray中的第一個button,然後我們将路徑的起點移到這個button中心點(可以了解為将畫筆移到這個button的中心點),然後其餘的點就依次 addLineto 就可以了。這樣隻有選中的button之間才有連接配接的線,我們希望手指移到button的外部時,也能在selectArray中的最後一個按鈕與目前觸摸點之間有一條線,是以我們在循環外面又加上了一個 addlineto ,到達的點就是我們之前在touchesMoved裡面定義的lastPoint。之後就是關于畫上去的線條的一些設定,不用多說。

重寫的drawRect方法會在程式加載起來的時候調用一次,如果需要手動調用,我們需要 使用  setNeedsDisplay 方法。

2.2 密碼操作

這樣一來畫線的操作就算完成了,接下來就是記錄和設定密碼的操作。首先我們建立一個label來提示使用者的操作:

//UnlockView.m
self.titleLabel = [self viewWithTag:1000];
           

我們的label是通過stroyboard添加上去的,我們用tag值将它取出。

接下來需要确定label需要顯示的内容,我們從 NSUserdefaults 中取出按照 "pwd"鍵名存入的密碼,這裡需要加入一步判斷,如果取出來有東西,說明之前設定過密碼了;反之就沒有密碼,labeld的内容根據密碼的有無來确定:

//UnlockView.m
   self.oldPassword = [[NSUserDefaults standardUserDefaults]objectForKey:@"pwd"];
   if (!self.oldPassword) {
        //請繪制密碼
        self.titleLabel.text = @"請設定圖案密碼";
        
    }else{
        //有密碼啦,請輸入密碼
        self.titleLabel.text = @"請繪制密碼";
    }
           

接下來就是繪制圖案時記錄選中的按鈕,由于我們之前給按鈕設定了tag值,是以最終的密碼可以由一組tag值确定(我們這裡設定的tag值都是一位的):

//touchesBegin
//判斷某一個點是否在區域内
    for (UIButton *button in self.subviews) {
        if (CGRectContainsPoint(button.frame, location)) {
            //設定按鈕的狀态
            button.selected = YES;
            [self.selectedArray addObject:button];
            //記錄密碼
            [self.pwdString appendFormat:@"%d",(int)button.tag];
        }
    }


//touchesMoved
for (UIButton *button in self.subviews) {
        if (CGRectContainsPoint(button.frame, location)) {
            if (button.selected==NO) {
                //設定按鈕的狀态
                button.selected = YES;
                [self.selectedArray addObject:button];
                //記錄密碼
                [self.pwdString appendFormat:@"%d",(int)button.tag];
            }
        }
    }
           

我們使用一個 pwdString來儲存繪制的密碼。

最後當手指離開螢幕時,說明密碼繪制完成了,這時我們需要判斷此時是第一次設定密碼還是輸入解鎖的密碼:

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    if (self.oldPassword.length>0) {
        if ([_oldPassword isEqualToString:self.pwdString]) {
            self.titleLabel.text = @"解鎖成功";
        }else{
            self.titleLabel.text = @"解鎖失敗,請重新繪制";
        }
    }else{
        if (self.firstString.length==0) {
            self.firstString = [NSString stringWithString:self.pwdString];
            self.titleLabel.text = @"請确認剛剛繪制的密碼圖案";
        }else{
            if (![self.firstString isEqualToString:self.pwdString]) {
                self.titleLabel.text = @"兩次密碼繪制不相同,請重新繪制";
                self.firstString = @"";
            }else{
                self.titleLabel.text = @"密碼設定成功";
                [[NSUserDefaults standardUserDefaults]setObject:self.firstString forKey:@"pwd"];
            }
        }
    }
    
    //現将所有點亮的按鈕的狀态改變一下
    for (UIButton * button in self.selectedArray) {
        button.selected = NO;
    }
    //i清空數組
    [self.selectedArray removeAllObjects];
    //重新整理螢幕
    [self setNeedsDisplay];
    [self.pwdString setString:@""];
}
           

基本的邏輯就是首先判斷是第一次設定密碼,還是直接繪制密碼解鎖,而在前一種情況下還要判斷是第一次設定密碼,還是第二次确認密碼,而在第二次确認密碼的時候,又要分确認密碼正确和确認密碼錯誤。密碼的邏輯操作之後我們需要将所有按鈕的選中狀态設定為no,清空數組,重新整理螢幕,還要把臨時性的 pwdString設定為 “”。

具體demo位址:iOS UIBezierPath實作手勢解鎖

繼續閱讀