题记

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

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


功能模块分析

根据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来分两类:

//检测手势密码答案情况 对/错/不够4个数字
typedef NS_ENUM(NSUInteger, ResultKindType) {
  ResultKindTypeTrue,
  ResultKindTypeFalse,
  ResultKindTypeNoEnough,
  ResultKindTypeClear
};

typedef NS_ENUM(NSUInteger, TeacKindType) {
  TeacKindTypeNoEnough,
  TeacKindTypeTrue
};

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

@protocol GestureLockDelegate <NSObject>

- (void)gestureLockView:(GestureLockView *)lockView drawRectFinished:(NSMutableString *)gesturePassword;

@end

属性:

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

方法:

- (void)clearLockView;//清除布局 重新开始

- (void)checkPwdResult:(ResultKindType)resultType;//检测学生端的结果

- (void)checkTeacResult:(TeacKindType)resultType;//检测老师端的结果

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

声明变量供下方使用:

@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

懒加载初始化数组

- (NSMutableArray *)selectBtns {
  if (!_selectBtns) {
    _selectBtns = [NSMutableArray array];
  }
  return _selectBtns;
}

- (NSMutableArray *)errorBtns {
  if (!_errorBtns) {
    _errorBtns = [NSMutableArray array];
  }
  return _errorBtns;
}

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

- (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];
  }
}

界面布局:

  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进行伸展的,这块是这个逻辑,应该可以通用的。

- (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;

#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;
  }
}

传递设置的手势密码方法

//传递设置的手势密码
- (NSMutableString *)transferGestureResult {
  //创建可变字符串
  NSMutableString *result = [NSMutableString string];
  for (UIButton *btn in self.selectBtns) {
    [result appendFormat:@"%ld", btn.tag - 1];
  }
  return result;
}

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

case ResultKindTypeFalse:
_errorBtns = [NSMutableArray arrayWithArray:self.selectBtns];
break;

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

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;也就不再继续往下进行了。

//学生端状态改变
- (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状态,虽然不起眼,但是也是写出来了。

- (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根据返回的结果状态进行判断

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中取出按钮进行状态的改变。

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进行一些相关设置。

- (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

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

- (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了,接受并校验密码,

#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];
  }
}

两个按钮的点击事件

#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];
}

用本地存储进行模拟

#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代理方法 并校验对当前的手势密码和本地存储的教师端密码

#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 进行界面的重绘等调整

- (void)clearBtnClick:(UIButton *)btn {
  self.statusLabel.text = @"清除!!!";
  [self.gestureLockView checkPwdResult:ResultKindTypeClear];
  [self shakeAnimationForView:self.statusLabel];
}

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

#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

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

iOS实现手势滑动解锁功能简析的更多相关文章

  1. html5 canvas手势解锁源码分享

    这篇文章主要介绍了html5 canvas手势解锁源码分享,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下

  2. h5使用canvas画布实现手势解锁

    这篇文章主要介绍了h5使用canvas画布实现手势解锁的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  4. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  6. ios – Testflight无法安装应用程序

    我有几个测试人员注册了testflight并连接了他们的设备……他们有不同的ios型号……但是所有这些都有同样的问题.当他们从“safari”或“testflight”应用程序本身单击应用程序的安装按钮时……达到约90%并出现错误消息…

  7. Pan和2 Finger Pinch同步iOS – 同时 –

    2手势识别器:和:但同时捏和平底锅不起作用……

  8. ibm-mobilefirst – 在iOS 7.1上获取“无法安装应用程序,因为证书无效”错误

    当我的客户端将他们的设备更新到iOS7.1,然后尝试从AppCenter更新我们的应用程序时,我收到了上述错误.经过一番搜索,我找到了一个类似问题的帖子here.但是后来因为我在客户端使用AppCenter更新应用程序的环境中,我无法使用USB插件并为他们安装应用程序.在发布支持之前,是否有通过AppCenter进行下载的解决方法?

  9. ios – 视图的简单拖放?

    我正在学习iOS,但我找不到如何向UIView添加拖放行为.我试过了:它说“UIView没有可见的接口声明选择器addTarget”此外,我尝试添加平移手势识别器,但不确定这是否是我需要的它被称为,但不知道如何获得事件的坐标.在iOS中注册移动事件回调/拖放操作的标准简单方法是什么?

  10. ios – Swift 4添加手势:覆盖vs @objc

    我想在我的视图中添加一个手势,如下所示:但是,在Swift4中,我的编译器给出了以下错误:建议添加@objc以将此实例方法公开给Objective-C.实现此目的的另一个选项将覆盖touchesBegan()函数并使用它来处理点击.我试图以“Swift”的方式做到这一点,而不必带入Obj-C.有没有纯粹的Swift方式来添加这个轻击手势而不使用@objc?

随机推荐

  1. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  2. iOS – genstrings:无法连接到输出目录en.lproj

    使用我桌面上的项目文件夹,我启动终端输入:cd然后将我的项目文件夹拖到终端,它给了我路径.然后我将这行代码粘贴到终端中找.-name*.m|xargsgenstrings-oen.lproj我在终端中收到此错误消息:genstrings:无法连接到输出目录en.lproj它多次打印这行,然后说我的项目是一个目录的路径?没有.strings文件.对我做错了什么的想法?

  3. iOS 7 UIButtonBarItem图像没有色调

    如何确保按钮图标采用全局色调?解决方法只是想将其转换为根注释,以便为“回答”复选标记提供更好的上下文,并提供更好的格式.我能想出这个!

  4. ios – 在自定义相机层的AVFoundation中自动对焦和自动曝光

    为AVFoundation定制图层相机创建精确的自动对焦和曝光的最佳方法是什么?

  5. ios – Xcode找不到Alamofire,错误:没有这样的模块’Alamofire’

    我正在尝试按照github(https://github.com/Alamofire/Alamofire#cocoapods)指令将Alamofire包含在我的Swift项目中.我创建了一个新项目,导航到项目目录并运行此命令sudogeminstallcocoapods.然后我面临以下错误:搜索后我设法通过运行此命令安装cocoapodssudogeminstall-n/usr/local/bin

  6. ios – 在没有iPhone6s或更新的情况下测试ARKit

    我在决定下载Xcode9之前.我想玩新的框架–ARKit.我知道要用ARKit运行app我需要一个带有A9芯片或更新版本的设备.不幸的是我有一个较旧的.我的问题是已经下载了新Xcode的人.在我的情况下有可能运行ARKit应用程序吗?那个或其他任何模拟器?任何想法或我将不得不购买新设备?解决方法任何iOS11设备都可以使用ARKit,但是具有高质量AR体验的全球跟踪功能需要使用A9或更高版本处理器的设备.使用iOS11测试版更新您的设备是必要的.

  7. 将iOS应用移植到Android

    我们制作了一个具有2000个目标c类的退出大型iOS应用程序.我想知道有一个最佳实践指南将其移植到Android?此外,由于我们的应用程序大量使用UINavigation和UIView控制器,我想知道在Android上有类似的模型和实现.谢谢到目前为止,guenter解决方法老实说,我认为你正在计划的只是制作难以维护的糟糕代码.我意识到这听起来像很多工作,但从长远来看它会更容易,我只是将应用程序的概念“移植”到android并从头开始编写.

  8. ios – 在Swift中覆盖Objective C类方法

    我是Swift的初学者,我正在尝试在Swift项目中使用JSONModel.我想从JSONModel覆盖方法keyMapper,但我没有找到如何覆盖模型类中的Objective-C类方法.该方法的签名是:我怎样才能做到这一点?解决方法您可以像覆盖实例方法一样执行此操作,但使用class关键字除外:

  9. ios – 在WKWebView中获取链接URL

    我想在WKWebView中获取tapped链接的url.链接采用自定义格式,可触发应用中的某些操作.例如HTTP://我的网站/帮助#深层链接对讲.我这样使用KVO:这在第一次点击链接时效果很好.但是,如果我连续两次点击相同的链接,它将不报告链接点击.是否有解决方法来解决这个问题,以便我可以检测每个点击并获取链接?任何关于这个的指针都会很棒!解决方法像这样更改addobserver在observeValue函数中,您可以获得两个值

  10. ios – 在Swift的UIView中找到UILabel

    我正在尝试在我的UIViewControllers的超级视图中找到我的UILabels.这是我的代码:这是在Objective-C中推荐的方式,但是在Swift中我只得到UIViews和CALayer.我肯定在提供给这个方法的视图中有UILabel.我错过了什么?我的UIViewController中的调用:解决方法使用函数式编程概念可以更轻松地实现这一目标.

返回
顶部