【iOS】UISlider自定义样式(swift)

写在前面的:

在实际项目开发中,我们经常有定制Slider拖动控件样式的需求!

最近,在自己的项目中,正好也需要定制UISlider
这里将我自己的经验写下来,供需要的人参考!

最终效果:

动图效果

实现思路:

遇到需要定制控件样式的需求,我们往往一开始会想到用UIView来封装自己的控件,因为这样最直接,也最顺手!……但是,如果从效率角度来讲,自己封装控件,也不见得好,甚至在调试和优化,以及代码复用方面,可能还不如在原生控件上面进行修改!

所以,我最终还是选择在UISlider的基础上,重写部分现有方法,来实现想要的效果!

  • 重写thumbRect
  • 重写trackRect
  • 生成thumbImage图片
  • 生成trackImage图片

完整代码:

UISlider 定义部分

//
//  ColorSlider.swift
//  UIKit-basic
//
//  Created by Qire_er on 2021/11/27.
//

import UIKit

class ColorSlider: UISlider {
    
    var valueLabel: UILabel! // 定义【值】文本

    private var thumbColor: UIColor! // 定义【滑块】颜色
    private var trackHeight: CGFloat = 0 // 定义【track】高度
    private var colors: [CGColor]! // 定义【颜色】列表
    private var locations: [CGFloat]! // 定义【位置】列表
    
    init(frame: CGRect, trackHeight: CGFloat, colors: [CGColor], locations: [CGFloat]) {
        super.init(frame: frame)
        
        self.trackHeight = trackHeight
        self.colors = colors
        self.locations = locations
        
        addValueLabel() // 添加【值】文本
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    // 添加【值】文本
    private func addValueLabel() {
        self.valueLabel = UILabel()
        self.valueLabel.textAlignment = .center
        self.addSubview(valueLabel)
    }
    
    // 重写 draw 方法
    override func draw(_ rect: CGRect) {
        // 设置ThumbImage
        let thumbWidth = rect.height
        let thumbImg = makeThumbImage(rect: CGRect(x: 0, y: 0, width: thumbWidth, height: thumbWidth))
        self.setThumbImage(thumbImg, for: .normal)
        
        // 设置MinimumTrackImage
        let minTrackImg = makeTrackImage(rect: rect)  // 定义minimumTrackImage
        self.setMinimumTrackImage(minTrackImg, for: .normal)  // 设置minimumTrackImage
        
        // 设置MaximumTrackImage(如果不设置,只有一边有渐变底色)
        let maxTrackImg = makeTrackImage(rect: rect)
        self.setMaximumTrackImage(maxTrackImg, for: .normal)
    }
    
    // 生成thumbImage图片 方法
    private func makeThumbImage(rect: CGRect) -> UIImage {
        
        let lineWidth: CGFloat = 2
        UIGraphicsBeginImageContextWithOptions(rect.size, false, 0)
        let rect = rect.insetBy(dx: lineWidth / 2 + 3, dy: lineWidth / 2 + 3)

        self.thumbColor.setFill()
        let oval = UIBezierPath(ovalIn: rect)
        oval.fill()
        UIColor.white.setStroke()
        oval.lineWidth = lineWidth
        oval.stroke()

        let thumbImg = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return thumbImg
    }
    
    // 生成trackImage图片 方法
    private func makeTrackImage(rect: CGRect) -> UIImage {
        
        let rect = CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: self.trackHeight)
        UIGraphicsBeginImageContextWithOptions(rect.size, false, 0)
        
        let ctx = UIGraphicsGetCurrentContext()!
        
        // 创建并设置路径
        let cornerRadius: CGFloat = rect.height * 0.5
        let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
       
        // 添加路径到图形上下文
        ctx.addPath(path)
        ctx.clip()
        
        // 使用rgb颜色空间
        let colorSpace = CGColorSpaceCreateDeviceRGB()
    
        // 定义渐变色
        let gradient = CGGradient(
            colorsSpace: colorSpace,
            colors: self.colors as CFArray,
            locations: self.locations
        )!
        
        // 渐变开始位置
        let start = CGPoint(x: self.bounds.minX, y: self.bounds.minY)
        // 渐变结束位置
        let end = CGPoint(x: self.bounds.maxX, y: self.bounds.minY)
        
        // 绘制渐变
        ctx.drawLinearGradient(gradient, start: start, end: end, options: .drawsBeforeStartLocation)
        let trackImg = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        
        return trackImg
    }
    
    // 重写 beginTracking 方法
    override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
        let tracking = super.beginTracking(touch, with: event)
        let thumbWidth = frame.height
        let thumbImg = makeThumbImage(rect: CGRect(x: 0, y: 0, width: thumbWidth, height: thumbWidth))
        self.setThumbImage(thumbImg, for: .normal)
        return tracking
    }
    
    // 重写 continueTracking 方法
    override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
        let tracking = super.continueTracking(touch, with: event)
        let thumbWidth = frame.height
        let thumbImg = makeThumbImage(rect: CGRect(x: 0, y: 0, width: thumbWidth, height: thumbWidth))
        self.setThumbImage(thumbImg, for: .normal)
        return tracking
    }
    
    // 重写 endTracking 方法
    override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
        super.endTracking(touch, with: event)
        let thumbWidth = frame.height
        let thumbImg = makeThumbImage(rect: CGRect(x: 0, y: 0, width: thumbWidth, height: thumbWidth))
        self.setThumbImage(thumbImg, for: .normal)
    }
    
    // 重写【thumb】显示区域 方法
    override func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect {
        let rect = super.thumbRect(forBounds: bounds, trackRect: rect, value: value)
        
        self.thumbColor = UIColor(hue: CGFloat(self.value), saturation: 1, brightness: 1, alpha: 1)
        
        // 添加【值】文本 label
        if let valueLabel = self.valueLabel {
            let offsetY: CGFloat = 25
            valueLabel.frame = CGRect(x: rect.minX, y: rect.minY - offsetY, width: rect.width, height: rect.height)
            valueLabel.text = String(Int(self.value * 100))
        }
        
        // 设置thumb投影效果
        // (这样设置的投影效果,如果调用setValue(_:animated:)的时候,animated参数为true,会有阴影和thumb不同步的问题,目前还不知到怎么解决)
        self.layer.shadowColor = UIColor.black.cgColor
        self.layer.shadowOffset = CGSize(width: 0, height: 0)
        self.layer.shadowOpacity = 0.15
        self.layer.shadowRadius = 3
        self.layer.shadowPath = UIBezierPath(cgPath: CGPath(ellipseIn: rect.insetBy(dx: 3, dy: 3), transform: nil)).cgPath
        
        return rect
    }
    
    // 重写【track】显示区域 方法
    override func trackRect(forBounds bounds: CGRect) -> CGRect {
        return CGRect(x: 0, y: 0, width: bounds.width, height: self.trackHeight)
    }
}

UISlider 调用部分

//
//  ColorSliderVC.swift
//  UIKit-basic
//
//  Created by Qire_er on 2021/11/27.
//

import UIKit

class ColorSliderVC: UIViewController {

    let width = UIScreen.main.bounds.width // 屏幕【宽度】
    let height = UIScreen.main.bounds.height // 屏幕【高度】
    lazy var sliderWidth: CGFloat = width - 60 // slider【宽度】
    let slidermHeight: CGFloat = 36 // slider【高度】
    
    let colors = [
        UIColor.red.cgColor,
        UIColor.yellow.cgColor,
        UIColor.green.cgColor,
        UIColor.cyan.cgColor,
        UIColor.blue.cgColor,
        UIColor.magenta.cgColor,
        UIColor.red.cgColor
    ] // 定义【颜色】列表
    
    var locations = [
        CGFloat(0)/CGFloat(360),
        CGFloat(60)/CGFloat(360),
        CGFloat(120)/CGFloat(360),
        CGFloat(180)/CGFloat(360),
        CGFloat(240)/CGFloat(360),
        CGFloat(300)/CGFloat(360),
        CGFloat(360)/CGFloat(360)
    ] // 定义【位置】列表
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let colorSlider = ColorSlider(
            frame: CGRect(x: (width - sliderWidth) * 0.5, y: (height - slidermHeight) * 0.5, width: sliderWidth, height: slidermHeight),
            trackHeight: 8,
            colors: self.colors,
            locations: self.locations
        )  // 放置在屏幕中心
        
        colorSlider.setValue(0.5, animated: true) // 设置slider【初始值】
     
        view.addSubview(colorSlider) // 添加slider 到 主视图
        view.backgroundColor = UIColor.white // 设置 主视图背景色
    }

}

(==完==)

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

推荐阅读更多精彩内容