前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >老司机出品———疯狂造轮子之滑动验证码

老司机出品———疯狂造轮子之滑动验证码

作者头像
老司机Wicky
发布2018-08-22 11:56:38
9020
发布2018-08-22 11:56:38
举报
文章被收录于专栏:老司机的简书老司机的简书

滑动验证码

消失了好久,大家放心,我还活着。 要问我为什么消失了这么久,如果你知道什么叫封闭开发或许你会懂我。

笑不出来

然而最近一直也没时间搞什么飞机,也没有什么能拿出来跟大家分享的,就把最近开发过程中写的一些小东西贴出来给大家看吧。 因为东西比较少,而且没有什么新鲜的技术点,所以老司机先把效果图放出来,这样的话如果你不感兴趣可能看到这就够了。

[滑块验证视图,点我跳到仓库哟](https://github.com/CodeWicky/DWSlideCaptchaView)
[滑块验证视图,点我跳到仓库哟](https://github.com/CodeWicky/DWSlideCaptchaView)

[滑块验证视图,点我跳到仓库哟](https://github.com/CodeWicky/DWSlideCaptchaView)

[步进滑竿,点我跳到仓库哟](https://github.com/CodeWicky/Components/tree/master/%E6%AD%A5%E8%BF%9B%E6%BB%91%E7%AB%BF)
[步进滑竿,点我跳到仓库哟](https://github.com/CodeWicky/Components/tree/master/%E6%AD%A5%E8%BF%9B%E6%BB%91%E7%AB%BF)

[步进滑竿,点我跳到仓库哟](https://github.com/CodeWicky/Components/tree/master/%E6%AD%A5%E8%BF%9B%E6%BB%91%E7%AB%BF)

在这篇文章中,你会看到以下内容:

  • 滑动验证视图
  • 继承UIControl重新实现一个Slider
  • 步进Slider

滑动验证视图

看到这了相信你可能是对这个滑动验证有些兴趣。 之所以写这个控件,是因为需求用到了,然而当前有没有相应的类库能让我拿来直接用。

所有效果是仿照日常网页中的效果去做的,所以我们还是应该首先分析一下我们需要什么。

  • 首先,我们需要一张底图
  • 第二,我们要从底图上截取一小部分作为一个滑块
  • 最后,当滑块位置改变并且最终与截取的位置重合时应该验证成功

需求在这了,可能唯一的技术点就在于如何截取图片了。

代码语言:javascript
复制
#pragma mark - 截取当前image对象rect区域内的图像
- (UIImage *)dw_SubImageWithRect:(CGRect)rect {
    ///防止处理过image的scale不为1情况rect错误
    CGFloat scale = self.scale;
    CGRect scaleRect = CGRectMake(rect.origin.x * scale, rect.origin.y * scale, rect.size.width * scale, rect.size.height * scale);
    CGImageRef newImageRef = CGImageCreateWithImageInRect(self.CGImage, scaleRect);
    UIImage *newImage = [[UIImage imageWithCGImage:newImageRef] dw_RescaleImageToSize:rect.size];
    CGImageRelease(newImageRef);
    return newImage;
}

#pragma mark - 压缩图片至指定尺寸
- (UIImage *)dw_RescaleImageToSize:(CGSize)size
{
    CGRect rect = (CGRect){CGPointZero, size};
    
    UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
    
    [self drawInRect:rect];
    
    UIImage *resImage = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    return resImage;
}

这些代码是老司机写在一个UIImage的category里面的,所以你应该知道所有的self都是一个UIImage实例。恩,在这个分类里面老司机封装了很多UIImage常用的方法,如下:

代码语言:javascript
复制
///高性能按图片名称检索本地图片
+(UIImage *)dw_ImageNamed:(NSString *)name;
///高性能返回无延迟立即解压的图片实例
+(UIImage *)dw_ImageWithUrl:(NSURL *)url;
///转换图片为Base64字符串
-(NSString *)dw_ImageToBase64String;
///Base64转换为图片
+ (UIImage *)dw_ImageWithBase64String:(NSString *)base64String;
///取图片某点颜色
-(UIColor *)dw_ColorAtPoint:(CGPoint)point;
///按给定颜色生层图片
+(UIImage *)dw_ImageWithColor:(UIColor *)color;
///以灰色空间生成图片
-(UIImage *)dw_ConvertToGrayImage;
///生成图片的反色图片对象
-(UIImage *)dw_ConvertToReversedColor;
///以给定颜色生成图像剪影
-(UIImage *)dw_ConvertToSketchWithColor:(UIColor *)color;
///生成处理每像素颜色后的图片
-(UIImage *)dw_ConvertImageWithPixelHandler:(void(^)(UInt8 * pixel,int x,int y))handler;
///获取带圆角的图片
-(UIImage *)dw_CornerRadius:(CGFloat)radius withWidth:(CGFloat)width contentMode:(DWContentMode)mode;
///按给定path剪裁图片
-(UIImage *)dw_ClipImageWithPath:(UIBezierPath *)path mode:(DWContentMode)mode;
///获取旋转角度的图片
-(UIImage *)dw_RotateImageWithAngle:(CGFloat)angle;
///按给定的方向旋转图片
-(UIImage*)dw_RotateWithOrient:(UIImageOrientation)orient;
///垂直翻转
-(UIImage *)dw_FlipVertical;
///水平翻转
-(UIImage *)dw_FlipHorizontal;
///压缩图片至指定尺寸
-(UIImage *)dw_RescaleImageToSize:(CGSize)size;
///压缩图片至指定像素
-(UIImage *)dw_RescaleImageToPX:(CGFloat )toPX;
///纠正图片方向
-(UIImage *)dw_FixOrientation;
///截取当前image对象rect区域内的图像
-(UIImage *)dw_SubImageWithRect:(CGRect)rect;
///在指定的size里面生成一个平铺的图片
-(UIImage *)dw_GetTiledImageWithSize:(CGSize)size;
///UIView转化为UIImage
+(UIImage *)dw_ImageFromView:(UIView *)view;
///将两个图片生成一张图片
+(UIImage*)dw_MergeImage:(UIImage*)firstImage withImage:(UIImage*)secondImage;

这个分类还是比较全的,老司机还是比较推荐的,给个传送门,觉得好的话给我个star吧。

所以说借助这个分类,你应该可以从一整张图片上截取一部分图片了,接下来你只需要:

  • 随意生成一个区域将它定为验证区域,并在该区域覆盖滑块形状的白色半透明的覆盖层
  • 创建一个与上面的区域形状相同的Layer,将截取好的图片赋给Layer,同时用贝塞尔曲线将Layer绘制成滑块的形状
  • 最后当验证视图滑块的位置改变至验证区域时,验证成功即可。

思路比较简单,实现起来也比较简单,300多行代码,老司机就直接放全部代码了:

代码语言:javascript
复制
-(instancetype)initWithFrame:(CGRect)frame {
    NSAssert((frame.size.width >= 100 && frame.size.height >= 40), @"To get a better experience,you may set the width more than 100 and height more than 50.");
    if (self = [super initWithFrame:frame]) {
        _useRandomValue = YES;
        _targetValue = DWSlideCaptchaUndefineValue;
        _thumbCenterY = DWSlideCaptchaUndefineValue;
        _tolerance = DWSlideCaptchaUndefineValue;
        _thumbSize = puzzlePath().bounds.size;
    }
    return self;
}

-(instancetype)initWithFrame:(CGRect)frame bgImage:(UIImage *)bgImage {
    if (self = [self initWithFrame:frame]) {
        [self beginConfiguration];
        self.bgImage = bgImage;
        [self commitConfiguration];
    }
    return self;
}

-(void)beginConfiguration {
    _configurating = YES;
    self.resetTargetPoint = YES;
}

-(void)commitConfiguration {
    if (!self.configurating) {
        return;
    }
    _configurating = NO;
    self.layer.contents = (id)self.bgImage.CGImage;
    [self handlePositionLayer];
    [self handleThumbLayer];
    [self hideThumbWithAnimated:NO];
}

-(void)reset {
    _successed = NO;
    [self beginConfiguration];
    [self commitConfiguration];
}

-(void)indentifyWithAnimated:(BOOL)animated result:(void(^)(BOOL success))result {
    BOOL isSuccess = fabs(self.targetPoint.x - self.currentPoint.x) < self.tolerance;
    isSuccess &= fabs(self.targetPoint.y - self.currentPoint.y) < self.tolerance;
    _successed = isSuccess;
    _indentified = YES;
    if (isSuccess) {
        if (animated) {
            if (self.delegate && [self.delegate respondsToSelector:@selector(dw_CaptchaView:animationWillStartWithSuccess:)]) {///动画开始回调
                [self.delegate dw_CaptchaView:self animationWillStartWithSuccess:YES];
            }
            if (!self.successAnimation) {///未指定动画使用默认动画
                [self.layer addAnimation:defaultSuccessAnimaiton(self) forKey:@"successAnimation"];
                [self hideThumbWithAnimated:NO];
                DWLayerTransactionWithAnimation(NO, ^(){
                    self.positionLayer.opacity = 0;
                });
            } else {///使用指定动画
                [self.thumbLayer addAnimation:self.successAnimation forKey:@"successAnimation"];
            }
        }
        if (result) result(YES);
    } else {
        if (animated) {
            if (self.delegate && [self.delegate respondsToSelector:@selector(dw_CaptchaView:animationWillStartWithSuccess:)]) {///动画开始回调
                [self.delegate dw_CaptchaView:self animationWillStartWithSuccess:YES];
            }
            if (!self.failAnimation) {///未指定动画则使用默认动画
                [self.thumbLayer addAnimation:defaultFailAnimation(self) forKey:@"failAnimation"];
            } else {///使用指定动画
                [self.thumbLayer addAnimation:self.failAnimation forKey:@"failAnimation"];
            }
        }
        if (result) result(NO);
    }
}

-(void)moveToPoint:(CGPoint)point animated:(BOOL)animated {
    if (self.successed) {
        return;
    }
    _indentified = NO;
    point = fixPointWithLimit(point, self.validSize, self.thumbSize);
    self.currentPoint = point;
    if (self.thumbLayer.opacity != 1) {
        [self showThumbWithAnimated:YES];
    }
    DWLayerTransactionWithAnimation(animated, ^(){
        self.thumbLayer.position = transformLocation2Center(point, self.thumbSize);
    });
}

-(void)setValue:(CGFloat)value animated:(BOOL)animated {
    CGFloat x = value * self.validSize.width + self.thumbSize.width / 2;
    CGFloat y = self.targetPoint.y + self.thumbSize.height / 2;
    [self moveToPoint:CGPointMake(x, y) animated:animated];
}

-(void)showThumbWithAnimated:(BOOL)animated {
    DWLayerTransactionWithAnimation(animated,^(){
        self.thumbLayer.opacity = 1;
    });
}

-(void)hideThumbWithAnimated:(BOOL)animated {
    DWLayerTransactionWithAnimation(animated, ^(){
        self.thumbLayer.opacity = 0;
    });
}

#pragma mark --- tool Method ---
-(void)handleThumbLayer {
    UIImage * thumbImage = [self.bgImage dw_SubImageWithRect:self.positionLayer.frame];
    thumbImage = [thumbImage dw_ClipImageWithPath:self.thumbShape mode:(DWContentModeScaleToFill)];
    self.thumbLayer.contents = (id)thumbImage.CGImage;
    self.thumbLayer.frame = self.positionLayer.frame;
    [self setValue:0 animated:NO];
    if (!self.thumbLayer.superlayer) {
        [self.layer addSublayer:self.thumbLayer];
    }
}

-(void)handlePositionLayer {
    UIBezierPath * path = [self.thumbShape copy];
    self.positionLayer.fillColor = [UIColor colorWithWhite:1 alpha:0.7].CGColor;
    self.positionLayer.path = path.CGPath;
    self.positionLayer.frame = CGRectMake(self.targetPoint.x, self.targetPoint.y, (int)path.bounds.size.width, (int)path.bounds.size.height);
    if (!self.positionLayer.superlayer) {
        [self.layer addSublayer:self.positionLayer];
    }
}

#pragma mark --- animation delegate ---
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    if (self.isSuccessed) {
        if (self.delegate && [self.delegate respondsToSelector:@selector(dw_CaptchaView:animationCompletionWithSuccess:)]) {
            [self.delegate dw_CaptchaView:self animationCompletionWithSuccess:YES];
        }
    } else {
        if (self.delegate && [self.delegate respondsToSelector:@selector(dw_CaptchaView:animationCompletionWithSuccess:)]) {
            [self.delegate dw_CaptchaView:self animationCompletionWithSuccess:NO];
        }
    }
}

#pragma mark --- 内联方法 ---
///默认滑块形状
static inline UIBezierPath * puzzlePath (){
    UIBezierPath * path = [UIBezierPath bezierPathWithPathMaker:^(DWPathMaker *maker) {
        maker.MoveTo(0, 8).
        AddLineTo(12, 8).AddArcWithPoint(12, 8, 20, 8, 5, YES, YES).AddLineTo(32, 8).
        AddLineTo(32, 20).AddArcWithPoint(32, 20, 32, 28, 5, YES, YES).AddLineTo(32, 40).
        AddLineTo(20, 40).AddArcWithPoint(20, 40, 12, 40, 5, NO, YES).AddLineTo(0, 40).
        AddLineTo(0, 28).AddArcWithPoint(0, 28, 0, 20, 5, NO, YES).ClosePath();
    }];
    return path;
}

///指定尺寸内的随机点
static inline CGPoint randomPointInSize(CGSize size) {
    CGPoint point = CGPointZero;
    point.x = randomValueInLength((int)size.width);
    point.y = randomValueInLength((int)size.height);
    return point;
}

///指定范围内的随机值
static inline int randomValueInLength(int length) {
    return arc4random() % ((int)(length + 1));
}

///修正centerY值合适的值
static inline CGFloat fixCenterYWithSize(CGSize thumbSize,CGSize validSize,CGFloat centerY) {
    CGFloat y = centerY - thumbSize.height / 2;
    return fixValueWithLimit(y, validSize.height);
}

///将值修正至指定范围
static inline CGFloat fixValueWithLimit(CGFloat value,CGFloat limitLength) {
    return value < 0 ? 0 : (value > limitLength ? limitLength : value);
}

///将点修正值有效范围内
static inline CGPoint fixPointWithLimit(CGPoint point,CGSize validSize,CGSize thumbSize) {
    CGFloat x = point.x - thumbSize.width / 2;
    CGFloat y = point.y - thumbSize.height / 2;
    return CGPointMake(fixValueWithLimit(x, validSize.width), fixValueWithLimit(y, validSize.height));
}

///将验证位置转换为layer中心点
static inline CGPoint transformLocation2Center(CGPoint origin,CGSize thumbSize) {
    return CGPointMake(origin.x + thumbSize.width / 2, origin.y + thumbSize.height / 2);
}

///Point转value
static inline NSValue * valueOfPoint(CGPoint point) {
    return [NSValue valueWithCGPoint:point];
}

///默认成功动画
static inline CAAnimation * defaultSuccessAnimaiton(id<CAAnimationDelegate> delegate) {
    CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    animation.duration = 0.2;
    animation.autoreverses = YES;
    animation.fromValue = @1;
    animation.toValue = @0;
    animation.removedOnCompletion = YES;
    animation.delegate = delegate;
    return animation;
}

///默认失败动画
static inline CAAnimation * defaultFailAnimation(id<CAAnimationDelegate> delegate) {
    DWSlideCaptchaView * captcha = (DWSlideCaptchaView *)delegate;
    CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    CGFloat a = 3;
    CGPoint Cp = captcha.thumbLayer.position;
    CGPoint Lp = CGPointMake(Cp.x - a, Cp.y);
    CGPoint Rp = CGPointMake(Cp.x + a, Cp.y);
    animation.values = @[valueOfPoint(Cp),valueOfPoint(Lp),valueOfPoint(Rp),valueOfPoint(Cp)];
    animation.repeatCount = 2;
    animation.removedOnCompletion = YES;
    animation.duration = 0.2;
    animation.delegate = captcha;
    return animation;
}

#pragma mark --- setter/getter ---
-(CAShapeLayer *)positionLayer {
    if (!_positionLayer) {
        _positionLayer = [CAShapeLayer layer];
    }
    return _positionLayer;
}

-(CAShapeLayer *)thumbLayer {
    if (!_thumbLayer) {
        _thumbLayer = [CAShapeLayer layer];
    }
    return _thumbLayer;
}

-(void)setThumbShape:(UIBezierPath *)thumbShape {
    SafeConfiguration
    CGSize size = thumbShape.bounds.size;
    if (!(size.width >= 40 && size.height >= 40)) {
        NSAssert(NO, @"To get a better experience,the width and height of thumbShape both should be more than 40.");
        return;
    }
    
    _thumbShape = thumbShape;
    _thumbSize = size;
}

-(UIBezierPath *)thumbShape {
    if (!_thumbShape) {
        return puzzlePath();
    }
    return _thumbShape;
}

-(void)setTargetValue:(CGFloat)targetValue {
    SafeConfiguration
    _targetValue = fixValueWithLimit(targetValue, 1);
}

-(void)setThumbCenterY:(CGFloat)thumbCenterY {
    SafeConfiguration
    _thumbCenterY = thumbCenterY;
}

-(void)setUseRandomValue:(BOOL)useRandomValue {
    SafeConfiguration
    _useRandomValue = useRandomValue;
}

-(void)setTolerance:(CGFloat)tolerance {
    SafeConfiguration
    _tolerance = tolerance;
}

-(CGFloat)tolerance {
    if (_tolerance < 0) {
        return 3;
    }
    return _tolerance;
}

-(void)setSuccessAnimation:(CAAnimation *)successAnimation {
    SafeConfiguration
    _successAnimation = successAnimation;
    _successAnimation.delegate = self;
}

-(void)setFailAnimation:(CAAnimation *)failAnimation {
    SafeConfiguration
    _failAnimation = failAnimation;
    _failAnimation.delegate = self;
}

-(void)setBgImage:(UIImage *)bgImage {
    SafeConfiguration
    if (bgImage) {
        _bgImage = [bgImage dw_RescaleImageToSize:self.frame.size];
    } else {
        _bgImage = nil;
    }
}

-(void)setThumbSize:(CGSize)thumbSize {
    SafeConfiguration
    if (!CGSizeEqualToSize(_thumbSize, thumbSize)) {
        _thumbSize = thumbSize;
    }
}

-(CGPoint)targetPoint {
    if (!self.resetTargetPoint) {
        return _targetPoint;
    }
    self.resetTargetPoint = NO;
    if (self.useRandomValue) {
        _targetPoint = randomPointInSize(self.validSize);
        return _targetPoint;
    }
    CGFloat x = (self.targetValue != DWSlideCaptchaUndefineValue) ? self.targetValue : randomValueInLength((int)self.validSize.width);
    CGFloat y = (self.thumbCenterY != DWSlideCaptchaUndefineValue) ? fixCenterYWithSize(self.thumbSize, self.validSize, self.thumbCenterY) : randomValueInLength((int)self.validSize.height);
    _targetPoint = CGPointMake(x, y);
    return _targetPoint;
}

-(CGSize)validSize {
    return CGSizeMake(self.bounds.size.width - self.thumbSize.width, self.bounds.size.height - self.thumbSize.height);
}

恩,这个库老司机不仅上传到GitHub上面,还让他支持了Cocoapods,你可以通过 pod 'DWSlideCaptchaView', '~> 1.0.2'来集成这个视图,觉得好请Star。

说到这里老司机说一下,现在你可以通过pod search wicky命令搜索老司机上传到Cocoapods上面的类库,目前有三个:

当前支持Cocoapods的库

老司机这里还是主推DWCoreTextLabel,他是一个基于CoreText的异步绘制的图文混排控件,并且支持图片的异步加载与缓存,基本上可以完美的实现图文混排需求。你值得使用。

DWCheckBox就是单选复选框了,也是一个快捷使用并且有着高定制型的类库。


继承UIControl重新实现一个Slider

广告打完了咱们来看第二环节,slider。 最初的时候其实我就是想实现后面那个步进Slider,最初的想法继承UISlider去重写,奈何转了一大圈,各种私有属性用一遍也无法完美的完成我的需求。

主要是由于UISlider中对于滑块和滑竿的定制性很困难,所以自己重写一个Slider吧。

所以为什么想到继承自UIControl去写呢?第一是UISlider继承自UIControl,第二是UIControl封装了-addTarget:selector:events以及事件追踪的一系列方法。

其实UIControl有四个核心的方法,是用于控制事件追踪的。

代码语言:javascript
复制
///判断是否开始事件追踪
-(BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event;
///判断事件追踪是否继续
-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event;
///事件追踪取消时处理
-(void)cancelTrackingWithEvent:(UIEvent *)event;
///事件追踪结束时处理
-(void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event;

使用方法无非就是判断当视图接收到事件是如何追踪,可以看一下老司机写Slider的处理。

代码语言:javascript
复制
#pragma mark --- tracking Method ---
-(BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    CGPoint location = [touch locationInView:self];
    location = [self.thumbLayer convertPoint:location fromLayer:self.layer];
    if ([PathWithBounds(self.thumbLayer.bounds, FitCornerRadius(self.thumbLayer, self.thumbCornerRadius)) containsPoint:location]) {
        self.clickOnThumb = YES;
        return YES;
    }
    location = [self.trackBgLayer convertPoint:location fromLayer:self.thumbLayer];
    if ([PathWithBounds(self.trackBgLayer.bounds, FitCornerRadius(self.trackBgLayer, self.trackCornerRadius)) containsPoint:location]) {
        self.clickOnThumb = NO;
        return YES;
    }
    return NO;
}

-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    CGPoint location = [touch locationInView:self];
    CGFloat margin = FitMarginForThumb(self.thumbSize, [self thumbMarginForBounds:self.bounds]);
    location.x -= margin;
    CGFloat actualW = CGRectGetWidth([self trackRectForBounds:self.bounds]) - margin * 2;
    if (location.x < 0) {
        location.x = 0;
    } else if (location.x > actualW) {
        location.x = actualW;
    }
    CGFloat percent = location.x / actualW;
    CGFloat value = self.minimumValue + (self.maximumValue - self.minimumValue) * percent;
    if (value == self.value) {
        return YES;
    }
    _value = value;
    [self sendActionsForControlEvents:UIControlEventValueChanged];
    if (self.clickOnThumb) {
        [self updateValueAnimated:NO];
        return YES;
    } else {
        [self updateValueAnimated:YES];
        self.clickOnThumb = NO;
        return NO;
    }
}

-(void)cancelTrackingWithEvent:(UIEvent *)event {
    self.clickOnThumb = NO;
}

-(void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    self.clickOnThumb = NO;
}

处理过事件追踪,我们只要处理好视图相关的内容即可。

此处可以分为两种思路,一种是通过DrawRect方法去追踪行为后不断绘制,另一种是通过Layer展示各个图层并追踪行为。这里呢,老司机更加推荐使用Layer去处理图层,因为本身DrawRect方法中的代码是使用CPU进行预算然后将bitmap提交给GPU,他处理绘制的速度远不如CALayer直接使用GPU来的快。

图层的绘制老司机在CoreAnimation系列中已经写得很细了,在这也就不多写了。 老司机重写的DWSlider是一个UISlider的替换类,它具备UISlider的所有功能,并且还能自由定制你的Slider的各个属性,相比UISlider来讲可玩性更强,老司机这里放一个传送门


步进Slider

DWStepSlider是一个分段的Slider,继承自DWSlider。 主要是实现分段的Slider至实现,主要思想还是通过更改事件追踪后的赋值。

代码语言:javascript
复制
#pragma mark --- tracking Method ---
-(BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    CGPoint location = [touch locationInView:self];
    location = [self.thumbLayer convertPoint:location fromLayer:self.layer];
    if ([PathWithBounds(self.thumbLayer.bounds, FitCornerRadius(self.thumbLayer, self.thumbCornerRadius)) containsPoint:location]) {
        self.clickOnThumb = YES;
        return YES;
    }
    location = [self.trackBgLayer convertPoint:location fromLayer:self.thumbLayer];
    if ([PathWithBounds(self.trackBgLayer.bounds, FitCornerRadius(self.trackBgLayer, self.trackCornerRadius)) containsPoint:location]) {
        self.clickOnThumb = NO;
        return YES;
    }
    return NO;
}

-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    CGPoint location = [touch locationInView:self];
    CGFloat margin = FitMarginForThumb(self.thumbSize, [self thumbMarginForBounds:self.bounds]);
    location.x -= margin;
    CGFloat actualW = CGRectGetWidth([self trackRectForBounds:self.bounds]) - margin * 2;
    if (location.x < 0) {
        location.x = 0;
    } else if (location.x > actualW) {
        location.x = actualW;
    }
    CGFloat percent = location.x / actualW;
    CGFloat value = self.minimumValue + (self.maximumValue - self.minimumValue) * percent;
    if (value == self.value) {
        return YES;
    }
    [self setValue:value updateThumb:NO];
    [self sendActionsForControlEvents:UIControlEventValueChanged];
    if (self.clickOnThumb) {
        [self updateValueAnimated:NO];
        return YES;
    } else {
        [self setValue:FixValue(value, _nodes.count) updateThumb:NO];
        [self updateValueAnimated:YES];
        return NO;
    }
}

-(void)cancelTrackingWithEvent:(UIEvent *)event {
    if (self.clickOnThumb) {
        self.value = FixValue(self.value, _nodes.count);
        self.clickOnThumb = NO;
    }
}

-(void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    if (self.clickOnThumb) {
        self.value = FixValue(self.value, _nodes.count);
        self.clickOnThumb = NO;
    }
}

至于图形还是CAShapeLayer的各种形状,老司机也早就说过了,还是传送门吧。


好吧,今天其实也没什么新鲜内容,毕竟都是一些UI控件的封装。 不过也是捋一下思路,控件要如何封装,所以还是不要脸的发出来了。 喜欢哪个给哪个Star吧恩,就是这么好意思

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.04.18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 滑动验证视图
  • 继承UIControl重新实现一个Slider
  • 步进Slider
相关产品与服务
验证码
腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档