晴川歷歷漢陽樹,芳草萋萋鸚鵡洲!<好運(yùn)蛋>
先上效果圖:
密碼是3548
-
開始思路分析:
前奏: 這個(gè)可以說是在簡易的畫板之上, 增加一些判斷就可以完成, 首先我們肯定需要九個(gè)圓圈的放置, 我思考的就是用九個(gè) UIView 循環(huán)的方法布置一下各自位置!
畫圖: 一個(gè)簡單的想法就是, 在九個(gè) View 所屬的大 View 畫線, 只要經(jīng)過任意一個(gè)圓圈所屬的范圍我們就把他的顏色改變, 并且記錄一下狀態(tài)(避免重復(fù)選中). 問題是第一筆如不在任一個(gè)圓圈的范圍的話, 那么我們就不能畫線, 這就是說要判斷起始點(diǎn)是否在九個(gè)圓圈的范圍中! 還有一些其他的問題我們可以遇到了在解決.
開始: 現(xiàn)在就是布局九個(gè) UIView, 每次依其中一個(gè)圓圈開始畫圖 , 觸及到一個(gè)圓圈就選中一個(gè)圓圈
上代碼解析:
// 重寫數(shù)組的 getter 方法 懶加載
- (NSMutableArray<UIBezierPath *> *)pathArray
{
if (!_pathArray)
{
_pathArray = [NSMutableArray arrayWithCapacity:0];
}
return _pathArray;
}```
- 第 1 步: 自定義一個(gè)承載的 UIView 類 這里是 SignView 此時(shí)我們聲明幾個(gè)屬性
```code
// 在這里先定義幾個(gè)屬性
{
// 開始是否選中了一個(gè) 圓圈 有的話 才能有下一步活動(dòng)
BOOL _isSelectStartPoint;
// 記錄每次 起點(diǎn)坐標(biāo)
CGPoint _pointForBegin;
// 記錄每次 終點(diǎn)坐標(biāo)
CGPoint _pointForEnd;
}
# 記錄路徑的數(shù)組 用于畫圖
@property (strong, nonatomic) NSMutableArray <UIBezierPath* > *pathArray;
# 定義一個(gè)零時(shí)路徑變量 接受中間游走的路徑 這個(gè)不能放到了路徑數(shù)組里面 用于不是兩個(gè)圓圈之間的畫線
@property (strong, nonatomic) UIBezierPath *tempPath;
# 每次點(diǎn)亮一個(gè)圓圈 我就放到數(shù)組里面 記錄選中順序 組合成密碼
@property (strong, nonatomic) NSMutableArray <UIView *> *runningNumViews;
注意: 我們獲取到密碼之后, 需要告訴外界我們的密碼是多少! 當(dāng)然我們可以寫個(gè). h屬性去記錄然后到最后圖案畫好之后傳遞個(gè)需要的地方就行, 這里我寫一個(gè)協(xié)議代理方法, 當(dāng)繪制完畢之后代理人可以通過方法得到想要的數(shù)據(jù)! 稍后有體現(xiàn)!
@protocol SignViewDelegate <NSObject>
# 聲明協(xié)議方法
- (void)SingnView:(SignView *)singnView getPassWordResultWith:(NSString *)signPassWord;
@end
# .h中聲明一個(gè)屬性代理
@property (assign, nonatomic) id<SignViewDelegate> delegate;
- 第 2 步: 構(gòu)建子視圖 也就是先把九個(gè)圓圈布局一下
- (void)createSubView
{
// 設(shè)置九個(gè)點(diǎn) (view) 的大小 (長寬)
CGFloat height = 50;
CGFloat width = 50;
// 設(shè)置九個(gè)view 的 frame 利用循環(huán)
// 算一下相鄰的view的間距 左右的話留的空隙和間距一樣的空算出水平間距 算出水平的間距
CGFloat lineSpace = (selfWIDTH - 50*3) / 4.0;
// 豎直間距 上下留的空隙和間距一樣
CGFloat columnSpace = (selfHEIGHT- 50*3) / 4.0;
for (int i = 0 ; i< 3 ; i++)
{
for (int j = 0 ; j < 3 ; j++)
{
// 算位置 并添加
UIView *tempView = [[UIView alloc] initWithFrame:CGRectMake(lineSpace + 50*j +lineSpace*j , columnSpace + 50*i + columnSpace*i, width, height)];
[self addSubview:tempView];
// 給 view 幾個(gè) tag 值加以區(qū)分 值是1000 + 1到9;
tempView.tag = 1001 + i*3 +j;
tempView.backgroundColor = [UIColor grayColor];
// 切成圓形
tempView.layer.cornerRadius = 25;
}
}
}
- 2.1 初始化方法中布局 建立九個(gè)view表示九個(gè)大點(diǎn)
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// 調(diào)用構(gòu)建視圖的方法
[self createSubView];
}
return self;
}```
- 并列2.1 可視化編程時(shí)候 構(gòu)建子視圖
```code
- (void)awakeFromNib
{
// 調(diào)用構(gòu)建視圖方法
[self createSubView];
}```
- 第 3 步: 在觸摸開始的方法中獲取開始點(diǎn)并且要判斷是否在九個(gè)圓圈的范圍中, 還要考慮的問題是, 第二次繪制的時(shí)候, 要在這里進(jìn)行對(duì)數(shù)據(jù)重新清空再次繪制不同任務(wù).
```code
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 記錄密碼的數(shù)組每次都要重新去記憶添加 view
self.runningNumViews = [NSMutableArray arrayWithCapacity:0];
// 先把存儲(chǔ)的路徑清空 第一次花圖案之后 再去畫的話 就把之前的路徑去掉
[self.pathArray removeAllObjects];
// 讓九個(gè)圓圈恢復(fù)原來狀態(tài) 顏色 和是 否選中 這里用交互的值去判斷是否選中
for (UIView *tempView in self.subviews)
{
tempView.userInteractionEnabled = 1;
tempView.backgroundColor = [UIColor grayColor];
}
// 獲取觸摸的第一個(gè)為開始點(diǎn)
_pointForBegin = [touches.anyObject locationInView:self];
// 遍歷檢查一下開始點(diǎn)是否是在 九個(gè)圓圈中某一個(gè)的范圍中
for (UIView *subView in self.subviews)
{
if (CGRectContainsPoint(subView.frame, _pointForBegin))
{
// 若是在 改變圓圈顏色
subView.backgroundColor = [UIColor greenColor];
// 記錄一下有沒有開始點(diǎn)
_isSelectStartPoint = 1;
// 更改開始點(diǎn)的坐標(biāo)
_pointForBegin = subView.center;
// 用 view 的交互去記錄是否選中
subView.userInteractionEnabled = 0;
[self.runningNumViews addObject:subView];
}
}
}
- 第 4 步: 在移動(dòng)的過程中 我們需要不斷的獲取終點(diǎn)畫直線 , 當(dāng)這個(gè)移動(dòng)點(diǎn)移動(dòng)到九個(gè)圓圈的范圍之內(nèi)的時(shí)候我們就把圓圈點(diǎn)亮, 并且要重新繪制直線以圓圈的中點(diǎn)為一個(gè)點(diǎn)和起始的圓圈中心點(diǎn)連線.具體參看代碼
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 獲取移動(dòng)中的終點(diǎn)
_pointForEnd = [touches.anyObject locationInView:self];
// 看看有沒有開始的圓圈被選中要是有的話才會(huì)有一系列的操作 否則不管
if (_isSelectStartPoint)
{
// 創(chuàng)建一個(gè)臨時(shí)的路徑再說
self.tempPath = [UIBezierPath bezierPath];
// 設(shè)置路徑起點(diǎn)
[self.tempPath moveToPoint:_pointForBegin];
// 移動(dòng)中的臨時(shí)終點(diǎn)線連起來
[self.tempPath addLineToPoint:_pointForEnd ];
// 判斷終點(diǎn)是否在 九個(gè)圓圈的范圍中
for (UIView *subView in self.subviews)
{
if (CGRectContainsPoint(subView.frame, _pointForEnd) && subView.userInteractionEnabled)
{
// 改變顏色 并關(guān)閉 交互 表示選中了
subView.backgroundColor = [UIColor colorWithRed:(arc4random()%345)/346.0 green:(arc4random()%345)/346.0 blue:(arc4random()%345)/346.0 alpha: 1];
subView.userInteractionEnabled = 0;
[self.runningNumViews addObject:subView];
// 重新規(guī)劃路徑
self.tempPath = [UIBezierPath new];
[self.tempPath moveToPoint:_pointForBegin];
[self.tempPath addLineToPoint:subView.center];
// 把路徑存放到數(shù)組中
[self.pathArray addObject:self.tempPath];
// 為找下一個(gè)圓圈位置做準(zhǔn)備 要以這個(gè)選中圓圈位置中心開始點(diǎn)
_pointForBegin = subView.center;
}
}
}
// 不要忘了 去渲染繪制一下
[self setNeedsDisplay];
}
- 第 5 步: 當(dāng)我們觸摸結(jié)束的時(shí)候, 要把多余的線條去掉 (不是連接兩個(gè)圓圈的線條) 而且移動(dòng)結(jié)束也意味著輸入密碼的結(jié)束! 我們外界可以通過代理方法得到當(dāng)前這次繪制的密碼.
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 清除當(dāng)前的路徑 目的是 把多余的沒有連接兩個(gè)圈的線 去掉
self.tempPath = nil;
// 設(shè)置沒有選中開始點(diǎn) 為下一次繪制做準(zhǔn)備
_isSelectStartPoint = 0;
// 繪制渲染一下
[self setNeedsDisplay];
NSMutableString *resulet = [NSMutableString string];
// 可用 tag 值 依據(jù)數(shù)組中放入 view 的順序得到密碼.
for (UIView *tempView in self.runningNumViews)
{
[resulet appendFormat:@"%ld",tempView.tag - 1000];
}
// 這里去調(diào)用代理方法 向外界傳遞繪制的結(jié)果
if (resulet && ![resulet isEqualToString:@""])
{
[self.delegate SingnView:self getPassWordResultWith:resulet];
}
}
- 第 6 步: 關(guān)鍵一步, 那就是繪制方法的完善
// 重新繪圖
- (void)drawRect:(CGRect)rect
{
// 找到所有連接兩個(gè)圓圈的路徑 渲染
for (UIBezierPath *path in self.pathArray)
{
[path setLineWidth:6];
[[UIColor redColor] set];
[path stroke];
}
// 臨時(shí)路徑渲染
self.tempPath.lineWidth = 6;
[[UIColor blueColor] set];
[self.tempPath stroke];
}
- 最后 1 步: 在ViewController 中使用這個(gè) SignView 遵循他的代理<SignViewDelegate>并實(shí)現(xiàn)方法即可:
- (void)SingnView:(SignView *)singnView getPassWordResultWith:(NSString *)signPassWord
{
NSLog(@"-------->%@",signPassWord);
if ([signPassWord isEqualToString:@"3548"])
{
[UIView animateWithDuration:1 animations:^{
self.signView.frame = CGRectMake(0, self.view.bounds.size.height, 0, 0);
} completion:^(BOOL finished) {
self.signView = nil;
}];
}
}