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

滑动验证码

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

笑不出来

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

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

[步进滑竿,点我跳到仓库哟](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吧恩,就是这么好意思

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏coding...

iOS开发实战-时光记账Demo 本地数据库版效果分析Demo地址

由于主页只是一个展示的时光轴界面,UIScrollView加几个按钮就能完成,需要读取数据库内容,所以我们先把内页-增加账单 完成。

13920
来自专栏一“技”之长

iOS开发一款小巧简洁的日历控件 原

        日 历是iOS开发中有时会用到的一个UI控件,网上开源的代码也很多,我浏览过一些,大致有两种模式,一种是日历的逻辑由开发者自己实现,通过计算闰年...

50620
来自专栏DannyHoo的专栏

cell高度的缓存1——利用字典进行缓存

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010105969/article/details/...

13010
来自专栏Python疯子

UITableViewCell自适应网络不规则图片和文字组合的高度

有时我们会需要对cell的图片和文字进行显示并完美自适配其大小,下面用我有限的知识做了个适配,看着好像还能用,哈哈 直接上code 001 在tablev...

36520
来自专栏DannyHoo的专栏

iOS开发中利用runtime设置UITextView的默认文字

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010105969/article/details/...

8420
来自专栏wOw的Android小站

[iOS] 小问题记录

iOS 往数据库里写保存文件路径的时候,不要写全路径,因为软件更新或者重新安装沙盒路径会变

11920
来自专栏Alice

demo2动态加载显示商品详情页

/* 要求:实现 头像+昵称(多余7位用...)           商品图片(根据商品实际的图片的大小进行动态的展示。按照一定的比例进行展示。)       ...

21690
来自专栏iOS122-移动混合开发研究院

Colours–颜色库,包含100种预定义的颜色和方法

简介 Colours–颜色库,包含各种100种预定义的颜色和方法,可以简化颜色相关的开发工作. 最新示例: 点击下载 快速入门 安装 通过Cocoapods安装...

45180
来自专栏移动端开发

常用开发技巧系列(一)

前言:         在我们iOS开发的过程中,你要是知道一些特别的小技巧的话,其实是可以帮你省很多事的,当然这东西也不需要我们专门去记,估计没有几个开发人员...

214100
来自专栏coding...

Objective-C AVPlayer播放视频的使用与封装大致效果界面搭建Demo地址

看下成员变量就知道我怎么搭建的了,这里我将video播放层的size作为参照量,对所有控件的size按照其video的size宽高进行比例缩放

17940

扫码关注云+社区

领取腾讯云代金券