iOS 玩转UISlider

前言
  • UISlider控件的常规使用想必大家在日常iOS开发中用的肯定是非常熟练了,其使用场景也比较广泛,比如:音量大小字体大小背光亮度播放进度拍照缩放等等。最近,笔者在做自定义相机功能模块中,就用UISlider控件来做拍照缩放的功能,主要是自定义UISlider,都是利用大家不常用到的API来实现的,有兴趣的童鞋可以看看。
  • 本文将着重讲解UISlider的使用,希望大家在看完本篇文章后,能够对UISlider有新的认识,真正运用到实际项目开发中去,争取玩转UISlider。文章仅供大家参考,若有不妥之处,还望不吝赐教,欢迎批评指正。
效果图
UISlider效果图.gif
常规操作

平常开发中,我们可以利用UISlider提供的相关属性API可以实现大部分的需求,话不多说,这里笔者先讲讲各个属性API的使用。

  • Property & API
这个值是介于滑块的最大值和最小值之间的,如果没有设置边界值,默认为0-1;
@property(nonatomic) float value; 

设置滑块最小边界值(默认为0)
@property(nonatomic) float minimumValue;  

设置滑块最大边界值(默认为1)
@property(nonatomic) float maximumValue;

设置滑块最左端显示的图片:
@property(nonatomic,retain) UIImage *minimumValueImage;

设置滑块最右端显示的图片:
@property(nonatomic,retain) UIImage *maximumValueImage;

设置滑块值是否连续变化(默认为YES),这个属性设置为YES则在滑动时,其value就会随时变化,设置为NO,则当滑动结束时,value才会改变。
@property(nonatomic,getter=isContinuous) BOOL continuous; 

设置滑块左边(小于部分)线条的颜色
@property(nonatomic,retain) UIColor *minimumTrackTintColor;

设置滑块右边(大于部分)线条的颜色
@property(nonatomic,retain) UIColor *maximumTrackTintColor;

设置滑块颜色(影响已划过一端的颜色)
注意这个属性:如果你没有设置滑块的图片,那个这个属性将只会改变已划过一段线条的颜色,不会改变滑块的颜色,如果你设置了滑块的图片,又设置了这个属性,那么滑块的图片将不显示,滑块的颜色会改变(IOS7)
@property(nonatomic,retain) UIColor *thumbTintColor;

手动设置滑块的值:
- (void)setValue:(float)value animated:(BOOL)animated;

设置滑块的图片:
- (void)setThumbImage:(UIImage *)image forState:(UIControlState)state;

设置滑块划过部分的线条图案
- (void)setMinimumTrackImage:(UIImage *)image forState:(UIControlState)state;

设置滑块未划过部分的线条图案
- (void)setMaximumTrackImage:(UIImage *)image forState:(UIControlState)state;

对应的几个get方法
- (UIImage *)thumbImageForState:(UIControlState)state;
- (UIImage *)minimumTrackImageForState:(UIControlState)state;
- (UIImage *)maximumTrackImageForState:(UIControlState)state;

对应的设置当前状态的响应属性的方法
@property(nonatomic,readonly) UIImage* currentThumbImage;
@property(nonatomic,readonly) UIImage* currentMinimumTrackImage;
@property(nonatomic,readonly) UIImage* currentMaximumTrackImage;

添加触发事件
[slider addTarget:self action:@selector(log:) forControlEvents:UIControlEventValueChanged];

比较常用的属性以及作用如下图所示:


UISlider常用属性.png
  • Usage
#pragma mark - 常规操作
- (void)_generalOperations{
    
    /// 创建Slider 设置Frame
    UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake((MH_SCREEN_WIDTH - 247) * .5f, MH_SCREEN_HEIGHT*.5f- 50*.5f, 247, 50)];
    self.slider = slider;
    /// 添加Slider
    [self.view addSubview:slider];
    
    /// 属性配置
    // minimumValue  : 当值可以改变时,滑块可以滑动到最小位置的值,默认为0.0
    slider.minimumValue = 0.0;
    // maximumValue : 当值可以改变时,滑块可以滑动到最大位置的值,默认为1.0
    slider.maximumValue = 100.0;
    // 当前值,这个值是介于滑块的最大值和最小值之间的,如果没有设置边界值,默认为0-1;
    slider.value = 50;
    
    // continuous : 如果设置YES,在拖动滑块的任何时候,滑块的值都会改变。默认设置为YES
    [slider setContinuous:YES];
    
    UIImage * minimumValueImage = MHImageNamed(@"zoom-");
    UIImage * maximumValueImage = MHImageNamed(@"zoom+");
    // 滑块条最小值处设置的图片,默认为nil
    slider.minimumValueImage = minimumValueImage;
    // 滑块条最大值处设置的图片,默认为nil
    slider.maximumValueImage = maximumValueImage;
    
    // minimumTrackTintColor : 小于滑块当前值滑块条的颜色,默认为蓝色
    slider.minimumTrackTintColor = [UIColor redColor];
    // maximumTrackTintColor: 大于滑块当前值滑块条的颜色,默认为白色
    slider.maximumTrackTintColor = [UIColor blueColor];
    // thumbTintColor : 当前滑块的颜色,默认为白色
    slider.thumbTintColor = [UIColor yellowColor];
    
    // minimumTrackTintColor : 小于滑块当前值滑块条的颜色,默认为蓝色
    slider.minimumTrackTintColor = [UIColor redColor];
    // maximumTrackTintColor: 大于滑块当前值滑块条的颜色,默认为白色
    slider.maximumTrackTintColor = [UIColor blueColor];
    // thumbTintColor : 当前滑块的颜色,默认为白色
    slider.thumbTintColor = [UIColor yellowColor];
    
    /**  PS: 设置图片的优先级高于设置tintColor
     
    /// 设置滑块条最大值处设置的图片在不同的状态
    [slider setMaximumTrackImage:MHImageNamed(@"slider_bg") forState:UIControlStateNormal];
    /// 设置滑块条最小值处设置的图片在不同的状态
    [slider setMinimumTrackImage:MHImageNamed(@"slider_bg") forState:UIControlStateNormal];
    /// 设置滑块图片在不同的状态
    [slider setThumbImage:MHImageNamed(@"slider_thumb") forState:UIControlStateNormal];
    [slider setThumbImage:MHImageNamed(@"slider_thumb") forState:UIControlStateHighlighted];
     
     */
    
    /// currentMaximumTrackImage : 当前(状态)滑块条最大值处设置的图片
    /// currentMinimumTrackImage : 当前(状态)滑块条最小值处设置的图片
    /// currentThumbImage: 当前(状态)滑块的图片
    /// - (nullable UIImage *)thumbImageForState:(UIControlState)state; /// 获取某个(状态)滑块的图片
    /// - (nullable UIImage *)minimumTrackImageForState:(UIControlState)state; /// 获取某个(状态)滑块条最小值处设置的图片
    /// - (nullable UIImage *)maximumTrackImageForState:(UIControlState)state; /// 获取某个(状态)滑块条最大值处设置的图片
    
    /// 事件监听
    [slider addTarget:self action:@selector(_sliderValueDidChanged:) forControlEvents:UIControlEventValueChanged];

    /// label
    UILabel *lb = [UILabel mh_labelWithText:@"常规操作" fontSize:16 textColor:[UIColor whiteColor]];
    lb.textAlignment = NSTextAlignmentRight;
    [self.view addSubview:lb];
    CGFloat lbX = 0;
    CGFloat lbY = CGRectGetMinY(slider.frame);
    CGFloat lbW = CGRectGetMinX(slider.frame) - 20;
    CGFloat lbH = CGRectGetHeight(slider.frame);
    lb.frame = CGRectMake(lbX, lbY, lbW, lbH);
    
}

效果图如下:

UISlider常规使用.png
另类操作

常规操作想必大家都比较熟练,但是有些时候我们需要自定义UISlider内部的子控件的大小或位置等。首先我们先看看UISlider的子控件视图层级结构,如下所示:

UISlider层级结构.png

如果大家对其层级不了解的话,就会手忙脚乱,不知从何下手,以至于会直接弃用UISlider控件,自己重新自定义UIView来实现,很明显这就是对UISlider的使用不够熟练导致的,因为UISlider已经提供了相关的API,只要子类重写相关API来实现子控件的Rect

  • API
// 子类重写
/// 设置minimumValueImage的rect
- (CGRect)minimumValueImageRectForBounds:(CGRect)bounds;

/// 设置maximumValueImage的rect
- (CGRect)maximumValueImageRectForBounds:(CGRect)bounds;

/// 设置track(滑条)尺寸
- (CGRect)trackRectForBounds:(CGRect)bounds;

/// 设置thumb(滑块)尺寸
- (CGRect)thumbRectForBounds:(CGRect)bounds trackRect:(CGRect)rect value:(float)value;

关于这几个API的作用如下:

UISlider_API作用.png
  • Usage
#pragma mark - 另类操作
- (void)_alternativeOperation{
    
    MHCameraZoomSlider *slider = [[MHCameraZoomSlider alloc] init];
    slider.hidden = YES;
    [self.view addSubview:slider];
    /// 逆时针旋转90度
    slider.transform = CGAffineTransformMakeRotation(-M_PI_2);
    /// 事件监听
    [slider addTarget:self action:@selector(_sliderValueDidChanged:) forControlEvents:UIControlEventValueChanged];
    /// 设置Frame
    CGFloat sliderW = 247;
    CGFloat sliderH = 36;
    CGFloat sliderX = (self.view.mh_width - sliderH) *.5f;
    CGFloat sliderY = CGRectGetMaxY(self.slider.frame) + 100;
    slider.frame = CGRectMake(sliderX, sliderY, sliderH, sliderW);
    
    
    /// label
    UILabel *lb = [UILabel mh_labelWithText:@"另类操作" fontSize:16 textColor:[UIColor whiteColor]];
    lb.textAlignment = NSTextAlignmentRight;
    [self.view addSubview:lb];
    CGFloat lbX = 0;
    CGFloat lbY = 0;
    CGFloat lbW = CGRectGetMinX(self.slider.frame) - 20;
    CGFloat lbH = 40;
    lb.frame = CGRectMake(lbX, lbY, lbW, lbH);
    lb.mh_centerY = slider.mh_centerY;
}


MHCameraZoomSlider.m 内容如下:

#import "MHCameraZoomSlider.h"

@interface MHCameraZoomSlider ()

/// 是否设置过layer
@property (nonatomic , readwrite , assign) BOOL didSetLayer;

@end


@implementation MHCameraZoomSlider

- (instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        // 初始化
        [self _setup];
        
        // 创建自控制器
        [self _setupSubViews];
        
        // 布局子控件
        [self _makeSubViewsConstraints];
    }
    return self;
}

#pragma mark - 事件处理Or辅助方法

#pragma mark - Private Method
- (void)_setup{
    self.minimumValue = 1;
    self.maximumValue = 5;
}

#pragma mark - 创建自控制器
- (void)_setupSubViews
{
    self.minimumTrackTintColor = [[UIColor whiteColor] colorWithAlphaComponent:.4];
    self.maximumTrackTintColor = self.minimumTrackTintColor;
    
    UIImage * minimumValueImage = MHImageNamed(@"zoom-");
    self.minimumValueImage = [minimumValueImage imageByRotateRight90];
    UIImage * maximumValueImage = MHImageNamed(@"zoom+");
    self.maximumValueImage = [maximumValueImage imageByRotateRight90];
    
    UIImage *norImage = MHImageNamed(@"slider_dot");
    UIImage *highImage = MHImageNamed(@"slider_dot_pressed");
    /// 图片合成
    UIGraphicsBeginImageContextWithOptions(highImage.size , NO, highImage.scale);
    [highImage drawInRect:CGRectMake(0, 0, highImage.size.width, highImage.size.height)];
    CGFloat w = norImage.size.width;
    CGFloat h = norImage.size.height;
    CGFloat x = (highImage.size.width - w) * .5f;
    CGFloat y = (highImage.size.height - h) * .5f;
    [norImage drawInRect:CGRectMake(x, y, w, h) withContentMode:UIViewContentModeScaleAspectFit clipsToBounds:NO];
    highImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    [self setThumbImage:norImage forState:UIControlStateNormal];
    [self setThumbImage:highImage forState:UIControlStateHighlighted];
}

#pragma mark - 布局子控件
- (void)_makeSubViewsConstraints{
    
}



#pragma mark - Override
/// 设置minimumValueImage的rect
- (CGRect)minimumValueImageRectForBounds:(CGRect)bounds{
    CGFloat X = 0;
    CGFloat H = 21;
    CGFloat Y =( bounds.size.height - H ) *.5f;
    CGFloat W = H;
    return CGRectMake(X, Y, W, H);
    
}
/// 设置maximumValueImage的rect
- (CGRect)maximumValueImageRectForBounds:(CGRect)bounds{
    CGFloat H = 21;
    CGFloat Y =( bounds.size.height - H ) *.5f;
    CGFloat W = H;
    CGFloat X = bounds.size.width - W;
    return CGRectMake(X, Y, W, H);
}

/// 设置track(滑条)尺寸
- (CGRect)trackRectForBounds:(CGRect)bounds{
    CGRect minimumValueImageRect = [self minimumValueImageRectForBounds:bounds];
    CGRect maximumValueImageRect = [self maximumValueImageRectForBounds:bounds];
    CGFloat margin = 2;
    CGFloat H = 6;
    CGFloat Y =( bounds.size.height - H ) *.5f;
    CGFloat X = CGRectGetMaxX(minimumValueImageRect) + margin;
    CGFloat W = CGRectGetMinX(maximumValueImageRect) - X - margin;
    return CGRectMake(X, Y, W, H);
}

/// 设置thumb(滑块)尺寸
- (CGRect)thumbRectForBounds:(CGRect)bounds trackRect:(CGRect)rect value:(float)value{
    
    CGFloat WH = 30;
    CGFloat margin = WH *.5f - 21 *.5f + 2;
    /// 滑块的滑动区域宽度
    CGFloat maxWidth = CGRectGetWidth(rect) + 2 * margin;
    /// 每次偏移量
    CGFloat offset = (maxWidth - WH)/(self.maximumValue - self.minimumValue);
    
    CGFloat H = WH;
    CGFloat Y = (bounds.size.height - H ) *.5f;
    CGFloat W = H;
    CGFloat X = CGRectGetMinX(rect) - margin + offset *(value-self.minimumValue);
    CGRect r =  CGRectMake(X, Y, W, H);
    return r;
}


#pragma mark - 布局
- (void)layoutSubviews{
    [super layoutSubviews];
    
    if (self.didSetLayer) {
        return;
    }
    BOOL didSetLayer = NO;
    for (UIView *v in self.subviews) {
        if (v.mh_height <= 6 &&  self.mh_height > 0) {
            v.layer.borderWidth = 0.5f;
            v.layer.borderColor = MHColorFromHexString(@"#2C2E30").CGColor;
            v.layer.cornerRadius = MHConvertToFitPt(6) *.5f;
            v.layer.masksToBounds = YES;
            didSetLayer = YES;
        }
    }
    self.didSetLayer = didSetLayer;
}

@end

效果图如下:

UISlider另类使用.png

以上就是笔者实际项目中用于拍照放大缩小的小控件(MHCameraZoomSlider),相比于自定义UIView来实现的话,这种重写的方法是不是会更加简单、高效。其实平常开发中我们都可以在系统提供的控件基础上,去进行修改和调整,而不是一味地去自定义UIView。总之,多多熟练,百炼成钢。

期待
  • 文章若对您有些许帮助,请给个喜欢❤️,毕竟码字不易;若对您没啥帮助,请给点建议💗,切记学无止境。
  • 针对文章所述内容,阅读期间任何疑问;请在文章底部评论指出,我会火速解决和修正问题。
  • GitHub地址:https://github.com/CoderMikeHe
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,566评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,472评论 2 59
  • 书名:《自控力》 2018.05.31 《自控力》让我们在未来的人生道路上,能保持科学家的心态:集中注意力。 (经...
    云中子之恋阅读 191评论 0 1
  • 导语:多年以前,先后看到了江老师的三本书《中国法理念》、《法的自然精神导论》和《互助与自足》之后,我叹为...
    水木心艺阅读 580评论 0 5
  • 东京女王说下周又要出国了。 “去哪儿?”好友惠美问。 “东京啊,最新冬装发表了,想去表参道逛逛,买些新货色回来;听...
    季子弘阅读 752评论 0 2