服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - IOS - iOS实现手势滑动解锁功能简析

iOS实现手势滑动解锁功能简析

2021-04-02 18:07劉光軍_Shine IOS

本篇文章主要介绍了iOS实现手势滑动解锁功能简析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

题记

在平常的生活中,我们大概经常遇见手势滑动解锁---也就是九宫格啊,已经出现好久了,虽然随着apple的指纹解锁的发展手势解锁虽然还有但是因为其不如指纹解锁方便也用的也少了,但是在大多数app中这两种方式都是并存的,比如qq,微信,支付宝等等,最近项目里面也刚好有这个需求,趁着刚完成抽出时间来记录下来当时的一些思路,可能有的地方理解的不到位,还需多总结,闲言少叙了,看重点。

功能描述如图:大概说一下思路,这个功能用来做相当于密令,用于两端的匹配,教师端设置了路径生成密码,储存在本地,学生端用来滑动输入进行验证。然后根据各种情况上部的label给与各种提示信息,这里只是一个比较原始简陋的demo,也只是实现了我们最常见的功能,所以重在领会其中的精神了,哈哈哈。

iOS实现手势滑动解锁功能简析

功能模块分析

根据gif可以简单的把这块儿功能分为几个部分来理解,第一个就是首页:viewcontroller,首页里面有两个controller分别是studviewcontroller和teacviewcontroller用来分作不同功能的载体。在studviewcontroller中分为三部分上中下statuslabel gesturelockview clearbtn,其中gesturelockview是手势解锁界面。在teacviewcontroller中也是分为三部分,statuslabel gesturelockview bottomview下方的bottomview分为两个button resetbtn重置按钮和surebtn确定按钮。说完大体的结构,接下来分部分说一下每个功能的实现思路。

拆解分析

首先说一下gesturelockview这个view控件:

首先在.h 文件中

定义两个枚举:分别用来定义两端的不同类型,stu端用resultkindtype来对画的手势结果进行分类分为这四类,下面都会用到,并说明用途。teac端用teackindtype来分两类:

?
1
2
3
4
5
6
7
8
9
10
11
12
//检测手势密码答案情况 对/错/不够4个数字
typedef ns_enum(nsuinteger, resultkindtype) {
  resultkindtypetrue,
  resultkindtypefalse,
  resultkindtypenoenough,
  resultkindtypeclear
};
 
typedef ns_enum(nsuinteger, teackindtype) {
  teackindtypenoenough,
  teackindtypetrue
};

协议:用来监听手势变化时传出的转化的密码(这个密码是用button的tag值来表示的),因为手势是在变化的,所以这里用了nsmutablestring向外传递。

?
1
2
3
4
5
@protocol gesturelockdelegate <nsobject>
 
- (void)gesturelockview:(gesturelockview *)lockview drawrectfinished:(nsmutablestring *)gesturepassword;
 
@end

属性:

  1. @property (nonatomic, weak) id<gesturelockdelegate> delegate;
  2. @property (nonatomic, assign) bool isteac;//教师端 用来验证是否是教师端

方法:

?
1
2
3
4
5
- (void)clearlockview;//清除布局 重新开始
 
- (void)checkpwdresult:(resultkindtype)resulttype;//检测学生端的结果
 
- (void)checkteacresult:(teackindtype)resulttype;//检测老师端的结果

.m文件中(具体创建和应用的方法)

声明变量供下方使用:

?
1
2
3
4
5
6
7
8
9
10
@interface gesturelockview ()
 
@property (strong, nonatomic) nsmutablearray *selectbtns;//选中的按钮数组
@property (nonatomic, strong) nsmutablearray *errorbtns;//错误的按钮数组
@property(nonatomic, assign)bool finished;//是否完成
@property (nonatomic, assign) cgpoint currentpoint;//当前触摸点
@property (nonatomic, assign) resultkindtype resulttype;//学生端结果
@property (nonatomic, assign) teackindtype teacresulttype;//教师端结果
 
@end

懒加载初始化数组

?
1
2
3
4
5
6
7
8
9
10
11
12
13
- (nsmutablearray *)selectbtns {
  if (!_selectbtns) {
    _selectbtns = [nsmutablearray array];
  }
  return _selectbtns;
}
 
- (nsmutablearray *)errorbtns {
  if (!_errorbtns) {
    _errorbtns = [nsmutablearray array];
  }
  return _errorbtns;
}

子视图初始化:在这里创建9个按钮并将它们add到self中,并给self 添加uipangesturerecognizer *pan手势

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)initsubviews {
  self.backgroundcolor = [uicolor clearcolor];
  uipangesturerecognizer *pan = [[uipangesturerecognizer alloc] initwithtarget:self action:@selector(pan:)];
  [self addgesturerecognizer:pan];
  
  //创建9个按钮
  for (nsinteger i = 0; i < 9; i++) {
    uibutton *btn = [uibutton buttonwithtype:uibuttontypecustom];
    btn.userinteractionenabled = no;
    [btn setimage:[uiimage imagenamed:@"sign_img_circle_n"] forstate:uicontrolstatenormal];
    [btn setimage:[uiimage imagenamed:@"sign_img_circle_s"] forstate:uicontrolstateselected];
    btn.tag = i+1;
    [self addsubview:btn];
  }
}

界面布局:

?
1
2
3
4
cgfloat minwidth = min(self.bounds.size.height, self.bounds.size.width);
cgfloat boundswidth = self.bounds.size.width;
cgfloat margin = (minwidth - cols * w) / (cols + 1);//间距
cgfloat xmargin = (boundswidth-2*margin-3*w)/2;

这里这一块是对不同机型进行的适配,因为这个控件有可能会被添加在一个height<width的view上,所以在这里margin是指比较短的长度也就是上下的height,xmargin是用来对width进行伸展的,这块是这个逻辑,应该可以通用的。

?
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
27
28
29
30
31
32
33
34
- (void)layoutsubviews {
  [super layoutsubviews];
  nsuinteger count = self.subviews.count;
  int cols = 3;//总列数
  cgfloat x = 0,y = 0,w = 0,h = 0;
  if (screen_width == 320) {
    w = 50;
    h = 50;
  } else {
    w = 60;
    h = 60;
  }
  cgfloat minwidth = min(self.bounds.size.height, self.bounds.size.width);
  cgfloat boundswidth = self.bounds.size.width;
  cgfloat margin = (minwidth - cols * w) / (cols + 1);//间距
  cgfloat xmargin = (boundswidth-2*margin-3*w)/2;
  
  cgfloat col = 0;
  cgfloat row = 0;
  for (int i = 0; i < count; i++) {
    col = i % cols;
    row = i / cols;
    if (i == 0 || i == 3 || i == 6) {
      x = xmargin;
    } else if (i == 1 || i == 4 || i == 7) {
      x = xmargin + w + margin;
    } else {
      x = xmargin + 2 * (margin+w);
    }
    y = (w+margin)*row;
    uibutton *btn = self.subviews[i];
    btn.frame = cgrectmake(x, y, w, h);
  }
}

手势代理方法: 在手势代理方法中监听手势滑动位置的改变,当pan.state == uigesturerecognizerstatebegan开始滑动的时候,从盛放显示错误状态的button改变为普通初始状态,并且将self.errorbtns数组清除。将_currentpoint = [pan locationinview:self];检测当滑动时当前的位置point,如果划过的位置包含在button的范围内,如果button.selected == no那么将button.selected = yes;//设置为选中并且将button添加到self.selectbtns数组中[self.selectbtns addobject:button];调用[self setneedsdisplay];方法进行重绘,调用setneedsdisplay方法系统会自动调用drawreact方法进行界面的重新布局。最后监听手指是否松开//监听手指松开 if (pan.state == uigesturerecognizerstateended) { self.finished = yes; }如果松开将self.finished = yes;

?
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
27
28
29
30
31
#pragma mark 手势
- (void)pan:(uipangesturerecognizer *)pan {
  
  if (pan.state == uigesturerecognizerstatebegan) {
    for (uibutton *btn in _errorbtns) {
      [btn setimage:[uiimage imagenamed:@"sign_img_circle_n"] forstate:uicontrolstatenormal];
      [btn setimage:[uiimage imagenamed:@"sign_img_circle_s"] forstate:uicontrolstateselected];
    }
    [self.errorbtns removeallobjects];
  }
  _currentpoint = [pan locationinview:self];
  
  for (uibutton *button in self.subviews) {
    if (cgrectcontainspoint(button.frame, _currentpoint)) {
      if (button.selected == no) {
        //点在按钮上
        button.selected = yes;//设置为选中
        [self.selectbtns addobject:button];
      } else {
        
      }
    }
  }
  
  //重绘
  [self setneedsdisplay];
  //监听手指松开
  if (pan.state == uigesturerecognizerstateended) {
    self.finished = yes;
  }
}

传递设置的手势密码方法

?
1
2
3
4
5
6
7
8
9
//传递设置的手势密码
- (nsmutablestring *)transfergestureresult {
  //创建可变字符串
  nsmutablestring *result = [nsmutablestring string];
  for (uibutton *btn in self.selectbtns) {
    [result appendformat:@"%ld", btn.tag - 1];
  }
  return result;
}

两端的外部操作对内部状态的改变:

?
1
2
3
case resultkindtypefalse:
_errorbtns = [nsmutablearray arraywitharray:self.selectbtns];
break;

这里如果绘制错误,将之前选中的按钮放在 _errorbtns盛放错误按钮的数组中

?
1
2
3
4
5
6
7
8
9
10
case resultkindtypeclear:
{
[[uicolor clearcolor] set];
for (int i = 0; i < self.errorbtns.count; i++) {
uibutton *btn = [self.errorbtns objectatindex:i];
[btn setimage:[uiimage imagenamed:@"sign_img_circle_n"] forstate:uicontrolstatenormal];
}
[self.errorbtns removeallobjects];
}
break;

当外界改变状态为“清除”时,将路径置为透明 并改变errorbtns数组中button的背景色状态,并且将errorbtns清空,这里为什么在这里做这些改变呢?这是因为在学生端进行清除操作的时候,手势的状态已经是finish并且这是后代理方法已经走完,并且我们在调用clearlockview方法的时候也将selectbtns数组清空了,因此在setneedsdisplay被调用,drawreact进行重绘的时候,检测到if (_selectbtns.count == 0) return;也就不再继续往下进行了。

?
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//学生端状态改变
- (void)checkpwdresult:(resultkindtype)resulttype {
  self.resulttype = resulttype;
  switch (resulttype) {
    case resultkindtypefalse:
      _errorbtns = [nsmutablearray arraywitharray:self.selectbtns];
      break;
      case resultkindtypetrue:
      break;
      case resultkindtypenoenough:
      break;
      case resultkindtypeclear:
    {
      [[uicolor clearcolor] set];
      for (int i = 0; i < self.errorbtns.count; i++) {
        uibutton *btn = [self.errorbtns objectatindex:i];
        [btn setimage:[uiimage imagenamed:@"sign_img_circle_n"] forstate:uicontrolstatenormal];
      }
      [self.errorbtns removeallobjects];
    }
      break;
    default:
      break;
  }
  [self clearlockview];
}
//教师端状态改变
- (void)checkteacresult:(teackindtype)resulttype {
  self.teacresulttype = resulttype;
  switch (resulttype) {
    case teackindtypetrue:
      break;
    case teackindtypenoenough:{
      [self clearlockview];
    }
      break;
    default:
      break;
  }
}

清除方法: 这里将self.finished = no;因为如果是通过代理将值传到外界并且外界对该值进行了校验,对内容的resulttype进行改变,这时候其实还是在drawreact方法中,并且已经走完了self.finished 方法,在这里将其设置为no 是为了改变下一次绘制的finished状态,虽然不起眼,但是也是写出来了。

?
1
2
3
4
5
6
7
8
9
10
11
- (void)clearlockview {
  self.finished = no;
  //遍历所有选中的按钮
  for (uibutton *btn in self.selectbtns) {
    //取消选中状态
    btn.selected = no;
  }
  [self.selectbtns removeallobjects];
  //
  [self setneedsdisplay];
}

利用贝塞尔曲线绘制路径,并根据对应的状态修改路径颜色,按钮颜色,当系统调用这个方法的时候会进行重绘,重绘时,从self.selectbtns数组中取出选中的button,当button是第一个的时候将其设置为bezierpath的起点[path movetopoint:btn.center];其余的按钮绘制路径[path addlinetopoint:btn.center];。在这里判断是否松开手指,在这个判断里逻辑判断比较多,大致捋一下,当self.finished == yes的时候,就将创建的密码利用先前声明的代理传递出去在外部进行检验。根据外部返回的操作状态,如果是self.isteac根据返回的结果状态进行判断

?
1
2
3
4
5
6
7
8
9
case teackindtypenoenough:
{
[[uicolor clearcolor] set];
}
break;
case teackindtypetrue:
{
[[uicolor colorwithred:94/255.0 green:195/255.0 blue:49/255.0 alpha:0.8] set];
}

如果画的点不足4个,那么清除选中的点并且将绘制路径的颜色透明(或者如果考虑到性能的话,最好用和背景色一样的颜色),如果是绘制完成,用“绿色”填充,这里我选择了教师绘制完毕不清楚绘制路径,以便查看。
如果是学生端的话:绘制正确清除路径,错误用“红色”标识路径并从盛放错误按钮的数组self.errorbtns中取出按钮进行状态的改变。

?
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
switch (self.resulttype) {
case resultkindtypetrue:
{
//正确
[[uicolor clearcolor] set];
}
break;
case resultkindtypefalse:
{
//错误
[[uicolor redcolor] set];
for (int i = 0; i < self.errorbtns.count; i++) {
uibutton *btn = [self.errorbtns objectatindex:i];
[btn setimage:[uiimage imagenamed:@"sign_img_circle_p"] forstate:uicontrolstatenormal];
}
break;
case resultkindtypenoenough:
{
[[uicolor clearcolor] set];
}
break;
case resultkindtypeclear:
break;
default:
break;
}

之后便是若没有finish 就是在绘制路径了,对path进行一些相关设置。

?
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
- (void)drawrect:(cgrect)rect {
  if (_selectbtns.count == 0) return;
  // 把所有选中按钮中心点连线
  uibezierpath *path = [uibezierpath bezierpath];
  for (int i = 0; i < self.selectbtns.count; i ++) {
    uibutton *btn = self.selectbtns[i];
    if (i == 0) {
      [path movetopoint:btn.center]; // 设置起点
    } else {
      [path addlinetopoint:btn.center];
    }
  }
  
  //判断是否松开手指
  if (self.finished) {
    //松开手
    nsmutablestring *pwd = [self transfergestureresult];//传递创建的密码
    [[uicolor colorwithred:94/255.0 green:195/255.0 blue:49/255.0 alpha:0.8] set];
    if ([self.delegate respondstoselector:@selector(gesturelockview:drawrectfinished:)]) {
      [self.delegate gesturelockview:self drawrectfinished:pwd];
    }
    
    if (self.isteac) {
      //教师端
      switch (self.teacresulttype) {
        case teackindtypenoenough:
        {
          [[uicolor clearcolor] set];
        }
          break;
        case teackindtypetrue:
        {
          [[uicolor colorwithred:94/255.0 green:195/255.0 blue:49/255.0 alpha:0.8] set];
        }
          break;
        default:
          break;
      }
    } else {
      switch (self.resulttype) {
        case resultkindtypetrue:
        {
          //正确
          [[uicolor clearcolor] set];
        }
          break;
        case resultkindtypefalse:
        {
          //错误
          [[uicolor redcolor] set];
          for (int i = 0; i < self.errorbtns.count; i++) {
            uibutton *btn = [self.errorbtns objectatindex:i];
            [btn setimage:[uiimage imagenamed:@"sign_img_circle_p"] forstate:uicontrolstatenormal];
          }
          break;
        case resultkindtypenoenough:
          {
            [[uicolor clearcolor] set];
          }
          break;
        case resultkindtypeclear:
          break;
        default:
          break;
        }
      }   
    }
  } else {
    [path addlinetopoint:self.currentpoint];
    [[uicolor colorwithred:94/255.0 green:195/255.0 blue:49/255.0 alpha:0.8] set];
  }
  path.linewidth = 1;
  path.linejoinstyle = kcglinecapround;
  path.linecapstyle = kcglinecapround;
  [path stroke];
}

viewcontroller

这个比较容易理解:分别跳转两个界面:

?
1
2
3
4
5
6
7
8
9
- (void)tableview:(uitableview *)tableview didselectrowatindexpath:(nsindexpath *)indexpath {
  if (indexpath.row == 0) {
    teacviewcontroller *vc = [[teacviewcontroller alloc] init];
    [self.navigationcontroller pushviewcontroller:vc animated:yes];
  } else {
    studviewcontroller *vc = [[studviewcontroller alloc] init];
    [self.navigationcontroller pushviewcontroller:vc animated:yes];
  }
}

teacviewcontroller

主要的应用就是调用gesturelockviewdelegate了,接受并校验密码,

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma mark gesturelockview代理事件
- (void)gesturelockview:(gesturelockview *)lockview drawrectfinished:(nsmutablestring *)gesturepassword {
  [self creategesturespassword:gesturepassword];
}
 
//创建手势密码
- (void)creategesturespassword:(nsmutablestring *)gesturepassword {
  if (self.lastgesturepsw.length == 0) {
    if (gesturepassword.length < 4) {
      self.lastgesturepsw = nil;
      [self.gesturelockview checkteacresult:teackindtypenoenough];
      self.statuslabel.text = @"至少连接4个点,重新输入";
      [self shakeanimationforview:self.statuslabel];
      return;
    }
    self.lastgesturepsw = gesturepassword;
    [self.gesturelockview checkteacresult:teackindtypetrue];
    nslog(@"---%@", self.lastgesturepsw);
    self.statuslabel.text = [nsstring stringwithformat:@"密码是%@", gesturepassword];
  }
}

两个按钮的点击事件

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma mark 按钮点击事件
//重置按钮
- (void)resetbtnclick:(uibutton *)btn {
  self.lastgesturepsw = nil;
  [teacviewcontroller addgesturepassword:@""];
  [self.gesturelockview checkteacresult:teackindtypenoenough];
  self.statuslabel.text = @"请绘制手势密码";
  nslog(@"resetpwd == %@, resetuserdefaultspwd == %@", self.lastgesturepsw, [teacviewcontroller gesturepassword]);
}
//确定按钮
- (void)surebtnclick:(uibutton *)btn {
  if (!self.lastgesturepsw) {
    self.statuslabel.text = @"请绘制手势密码";
    return;
  }
  [teacviewcontroller addgesturepassword:self.lastgesturepsw];
  [self.gesturelockview checkteacresult:teackindtypetrue];
  self.statuslabel.text = @"密码设置成功";
  nslog(@"resetpwd == %@, resetuserdefaultspwd == %@", self.lastgesturepsw, [teacviewcontroller gesturepassword]);
//  [self.navigationcontroller popviewcontrolleranimated:yes];
}

用本地存储进行模拟

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma mark 本地存储模拟
+ (void)deletegestuespassword {
  [[nsuserdefaults standarduserdefaults] removeobjectforkey:gespwd];
  [[nsuserdefaults standarduserdefaults] synchronize];
}
 
+ (void)addgesturepassword:(nsstring *)gesturepassword {
  [[nsuserdefaults standarduserdefaults] setobject:gesturepassword forkey:gespwd];
  [[nsuserdefaults standarduserdefaults] synchronize];
}
 
+ (nsstring *)gesturepassword {
  return [[nsuserdefaults standarduserdefaults] objectforkey:gespwd];
}

studviewcontroller

gesturelockdelegate代理方法 并校验对当前的手势密码和本地存储的教师端密码

?
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
27
28
29
30
31
32
33
34
35
36
37
38
#pragma mark 手势密码界面代理
- (void)gesturelockview:(gesturelockview *)lockview drawrectfinished:(nsmutablestring *)gesturepassword {
  [self validategesturepassword:gesturepassword];
}
 
//校验手势密码
- (void)validategesturepassword:(nsmutablestring *)gesturepassword {
  
  if (gesturepassword.length < 4) {
    self.statuslabel.text = @"至少连接4个点,重新输入";
    [self.gesturelockview checkpwdresult:resultkindtypenoenough];
    [self shakeanimationforview:self.statuslabel];
    return;
  }
  self.lastgesturepsw = gesturepassword;
  
  
  /*滑完直接校验*/
  nslog(@"validpwd == %@, validuserdefaultspwd == %@", self.lastgesturepsw, [studviewcontroller gesturepassword]);
  static nsinteger errorcount = 5;
  
  if ([self.lastgesturepsw isequaltostring:[studviewcontroller gesturepassword]]) {
    [self.gesturelockview checkpwdresult:resultkindtypetrue];
    self.statuslabel.text = @"密码校验成功";
    [self shakeanimationforview:self.statuslabel];
  } else {
    [self.gesturelockview checkpwdresult:resultkindtypefalse];
    errorcount = errorcount - 1;
    if (errorcount == 0) {
      //已经输错5次
      self.statuslabel.text = @"请重新输入密码";
      errorcount = 5;
      return;
    }
    self.statuslabel.text = [nsstring stringwithformat:@"密码错误, 还可以再输入%ld次", errorcount];
    [self shakeanimationforview:self.statuslabel];
  }
}

清除按钮点击事件 改变self.gesturelockview中的resulttype 进行界面的重绘等调整

?
1
2
3
4
5
- (void)clearbtnclick:(uibutton *)btn {
  self.statuslabel.text = @"清除!!!";
  [self.gesturelockview checkpwdresult:resultkindtypeclear];
  [self shakeanimationforview:self.statuslabel];
}

本地存储:这里也有一个本地存储 和教师端对应 (其实可以单独封装出来的)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma mark 本地存储模拟
+ (void)deletegestuespassword {
  [[nsuserdefaults standarduserdefaults] removeobjectforkey:gespwd];
  [[nsuserdefaults standarduserdefaults] synchronize];
}
 
+ (void)addgesturepassword:(nsstring *)gesturepassword {
  [[nsuserdefaults standarduserdefaults] setobject:gesturepassword forkey:gespwd];
  [[nsuserdefaults standarduserdefaults] synchronize];
}
 
+ (nsstring *)gesturepassword {
  return [[nsuserdefaults standarduserdefaults] objectforkey:gespwd];
}

以上,就是这个功能实现的大体流程了,捋顺了思路来看其实还是蛮明确的,当时做的时候也是走路挖坑填坑的,发现这样把自己的思路写下来还真是蛮有收获的,希望自己更好的进步,如果您看到这儿觉得有不合理的地方欢迎随时和我沟通哈。

源代码连接:https://github.com/irembeu/lgjgesturelockdemo.git

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:http://www.jianshu.com/p/8dc4a9b66db8

延伸 · 阅读

精彩推荐
  • IOSiOS通过逆向理解Block的内存模型

    iOS通过逆向理解Block的内存模型

    自从对 iOS 的逆向初窥门径后,我也经常通过它来分析一些比较大的应用,参考一下这些应用中某些功能的实现。这个探索的过程乐趣多多,不仅能满足自...

    Swiftyper12832021-03-03
  • IOSiOS中tableview 两级cell的展开与收回的示例代码

    iOS中tableview 两级cell的展开与收回的示例代码

    本篇文章主要介绍了iOS中tableview 两级cell的展开与收回的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    J_Kang3862021-04-22
  • IOSIOS 屏幕适配方案实现缩放window的示例代码

    IOS 屏幕适配方案实现缩放window的示例代码

    这篇文章主要介绍了IOS 屏幕适配方案实现缩放window的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要...

    xiari5772021-06-01
  • IOS解析iOS开发中的FirstResponder第一响应对象

    解析iOS开发中的FirstResponder第一响应对象

    这篇文章主要介绍了解析iOS开发中的FirstResponder第一响应对象,包括View的FirstResponder的释放问题,需要的朋友可以参考下...

    一片枫叶4662020-12-25
  • IOS关于iOS自适应cell行高的那些事儿

    关于iOS自适应cell行高的那些事儿

    这篇文章主要给大家介绍了关于iOS自适应cell行高的那些事儿,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    daisy6092021-05-17
  • IOSiOS布局渲染之UIView方法的调用时机详解

    iOS布局渲染之UIView方法的调用时机详解

    在你刚开始开发 iOS 应用时,最难避免或者是调试的就是和布局相关的问题,下面这篇文章主要给大家介绍了关于iOS布局渲染之UIView方法调用时机的相关资料...

    windtersharp7642021-05-04
  • IOSiOS 雷达效果实例详解

    iOS 雷达效果实例详解

    这篇文章主要介绍了iOS 雷达效果实例详解的相关资料,需要的朋友可以参考下...

    SimpleWorld11022021-01-28
  • IOSIOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解

    这篇文章主要介绍了IOS开发之字典转字符串的实例详解的相关资料,希望通过本文能帮助到大家,让大家掌握这样的方法,需要的朋友可以参考下...

    苦练内功5832021-04-01