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

滑动验证码

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


笑不出来

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

[滑块验证视图,点我跳到仓库哟](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)

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

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

滑动验证视图

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

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

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

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

#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常用的方法,如下:

///高性能按图片名称检索本地图片
+(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多行代码,老司机就直接放全部代码了:

-(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有四个核心的方法,是用于控制事件追踪的。

///判断是否开始事件追踪
-(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的处理。

#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至实现,主要思想还是通过更改事件追踪后的赋值。

#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恩,就是这么好意思

图文无关

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • *7月8日上午 N:Block :跟一个函数块差不多,会对里面所有的内容的引用计数+1,想要解决就用__block...
    炙冰阅读 2,471评论 1 14
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,019评论 4 62
  • 代码创建UIWindow对象 Xcode7之后使用代码创建UIWindow对象: //创建UIWindow对象 s...
    云之君兮鹏阅读 1,306评论 0 2
  • 没有无缘无故的遇见,一切相遇即是重逢! 相约美丽的苏州河畔,在一所历史悠久,拥有上海韵味的画室,我们重逢了。 今天...
    Elian一莲阅读 367评论 2 4
  • 站在金字塔顶端的人是不是很耀眼?是不是让人心生羡慕?然而他们也是普通人凭借一步步铿锵有力的步伐登上金字塔的,我们要...
    玉儿说阅读 268评论 0 5