Swift 自定义带气泡 可渐变色 seekbar

代码仅供参考 全部在文末

语法为 swift 5

无图无真相!

第二个版本

需求分析

温度在 [37 , 57] 区间 ,气泡只在该区间提示
当滑动到 < 37 时,滑动到 OFF 处


未实现部分:

  • 刻度条上小短线(指示线)
  • 滑块形状等
  • 气泡形状等

因为设计不同,请自行参考修改


本代码存在问题

  • 同一个温度值,滑块存在多个frame(滑块移动到相近的两个位置,温度值是一个, 在block返回之前判断如果相同就不执行Block)


使用栗子

    lazy var seekBar1: LzSeekBar = {
        let style = LzSeekBarStyle(seekBarUnitPt: 8, seekBarHeight: 4, valMin: 37, valMax: 57, seekBarColor: .blue, seekBarBgColor: .gray, gradientColor: nil, gradientPosition: [0, 0.2, 0.4, 0.6, 0.8, 1], slideRadius: 8, sectionCount: 5, sectionTitles: ["OFF", "37℃", "42℃", "47℃", "52℃", "57℃"], sectionPosition: .top, showPop: true, marginLR: 30)
        let v = LzSeekBar()
        v.style = style
        return v
    }()
    lazy var seekBar2: LzSeekBar = {
        let style = LzSeekBarStyle(seekBarUnitPt: 8, seekBarHeight: 4, valMin: 37, valMax: 57, seekBarColor: .blue, seekBarBgColor: .gray, gradientColor: [.gray, AppConst.color_green, AppConst.color_orange, AppConst.color_purple, AppConst.color_red], gradientPosition: [0, 0.2, 0.4, 0.6, 0.8, 1], slideRadius: 8, sectionCount: 5, sectionTitles: ["OFF", "37℃", "42℃", "47℃", "52℃", "57℃"], sectionPosition: .top, showPop: true, marginLR: 30)
        let v = LzSeekBar(frame: .zero, style: style)
        return v
    }()
    ///
    func addSeekbar() {

        view.addSubview(seekBar1)
        seekBar1.frame = CGRect(x: 20, y: 100, width: AppConst.width_screen - 40, height: 50)
        seekBar1.slideBlock = { (val, sliding) in
            debugPrint("纯色SeekBar的值:\(val)")
        }
        
        view.addSubview(seekBar2)
        seekBar2.frame = CGRect(x: 20, y: 200, width: AppConst.width_screen - 40, height: 50)
        seekBar2.slideBlock = { (val, sliding) in
            debugPrint("渐变色SeekBar的值:\(val)")

        }
    }


    override func viewDidLoad() {
        super.viewDidLoad()

        addSeekbar()
    }
    

类文件

//
//  LzSeekBar.swift
//  xczn
//
//  Created by lg on 2020/10/9.
//  Copyright © 2020 lxf. All rights reserved.
//

import UIKit


/// 滑动回调
typealias LzSeekBarBlock = (_ val: Int, _ sliding: Bool) -> Void


/// 分段标记位置
enum LzSeekBarSectionPosition {
    case top
    case bottom
}


/// 样式设置
struct LzSeekBarStyle {
    var seekBarUnitPt: CGFloat = 8
    var seekBarHeight: CGFloat = 4
    var valMin = 37
    var valMax = 57
    
    var seekBarColor: UIColor = .blue
    var seekBarBgColor: UIColor = .lightGray
    
    var gradientColor: [UIColor]? = nil
    var gradientPosition: [NSNumber]? = nil
    
    var slideRadius: CGFloat = 8
    
    var sectionCount = 1
    var sectionTitles: [String]? = nil
    var sectionPosition: LzSeekBarSectionPosition = .top
    var showPop: Bool = false
    
    var marginLR: CGFloat = 30
}



/// seekbar
class LzSeekBar: UIView {
    
    fileprivate var value: Int = 0
    fileprivate var startValue: Int = 32
    fileprivate var disInMaxAndMin: Int = 1
    fileprivate var beginTouch = false
    
    fileprivate var markLabels: Array = Array()
    fileprivate var startPos: CGFloat = 0
    fileprivate var endPos: CGFloat = 0
    
    fileprivate var seekBarLen: CGFloat = 0
    
    var slideBlock: LzSeekBarBlock? = nil
    /// 用来编辑是否可以滑动
    var slidEnable = false
    var style: LzSeekBarStyle! {
        didSet {
            configByStyle()
        }
    }
    
    //圆形滑块
    lazy var slider: UIView = {
        let v = UIView()
        v.backgroundColor = .white
        v.layer.borderWidth = 1
        v.layer.borderColor = UIColor(red: 0xcc/255.0, green: 0xcc/255.0, blue: 0xcc/255.0, alpha: 1).cgColor
        return v
    }()
    
    //灰色
    lazy var bgSeek: UIView = {
        let v = UIView()
        return v
    }()
    
    lazy var mainSeek: UIView = {
        let v = UIView()
        return v
    }()
    
    lazy var lblPop: UILabel = {
        let lbl = UILabel()
        lbl.frame = CGRect(origin: .zero, size: CGSize(width: 30, height: 30))
        lbl.layer.masksToBounds = true
        lbl.layer.cornerRadius = 15
        lbl.backgroundColor = .black
        lbl.textAlignment = .center
        lbl.textColor = .white
        lbl.adjustsFontSizeToFitWidth = true
        lbl.isHidden = true
        return lbl
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        addViews()
    }
    
    
    convenience init(frame: CGRect, style: LzSeekBarStyle) {
        self.init(frame: frame)
        self.style = style
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    
    fileprivate func addViews() {
        addSubview(mainSeek)
        addSubview(bgSeek)
        addSubview(slider)
        addSubview(lblPop)
    }
    
    fileprivate func configByStyle() {
    
        startPos = style.marginLR
        endPos = startPos + seekBarLen
        bgSeek.layer.cornerRadius = style.seekBarHeight/2
        bgSeek.backgroundColor = style.seekBarBgColor
        mainSeek.layer.cornerRadius = style.seekBarHeight/2
        mainSeek.backgroundColor = style.seekBarColor
        slider.layer.cornerRadius = style.slideRadius
        
        disInMaxAndMin = style.valMax - style.valMin
        seekBarLen = CGFloat(disInMaxAndMin + 1) * style.seekBarUnitPt
        

        if let count = style.sectionTitles?.count {
            
            //如果有分组
            assert(count-1 == style.sectionCount, "分组数和名称不一致")
            startValue = style.valMin - disInMaxAndMin/(style.sectionCount - 1)
            
            seekBarLen = CGFloat(style.valMax - startValue + 1) * style.seekBarUnitPt
            
            if (markLabels.count == 0 ) {
                for i in 0 ..< count {
                    let t: String = style.sectionTitles![I]
                    let lbl = createMarkLabel(title: t)
                    addSubview(lbl)
                        
                    markLabels.append(lbl)
                }
            }

        }

    }

    
    /// 根据名称创建label
    /// - Parameter title: 名称
    fileprivate func createMarkLabel(title: String) -> UILabel {
        
        let lbl = UILabel()
        lbl.textAlignment = .center
        lbl.text = title
        lbl.font = .systemFont(ofSize: 12)
        lbl.adjustsFontSizeToFitWidth = true
        return lbl
    }
    
    
    
    //渐变色线条
    fileprivate func addGradient() {
        guard let colors = style.gradientColor else { return  }
        var cgcolors : [CGColor] = [CGColor]()
        for color in colors {
            cgcolors.append(color.cgColor)
        }
        let gl_start = CGPoint(x: 0, y: 0)
        let gl_end = CGPoint(x: 1, y: 0)
        let gl_rect = CGRect(x: 0, y: 0, width: seekBarLen, height: style.seekBarHeight)
        
        let gl = CAGradientLayer()
        gl.colors = cgcolors
        gl.locations = style.gradientPosition
        gl.startPoint = gl_start
        gl.endPoint = gl_end
        gl.frame = gl_rect
        mainSeek.layer.insertSublayer(gl, at: 1)
    
    }

    fileprivate var isInit: Bool = true
    fileprivate func updateAllFrame() {
        
        if isInit  {
            
            configByStyle()

            mainSeek.frame = CGRect(x: startPos, y: 30, width: seekBarLen, height: style.seekBarHeight)
            bgSeek.frame = CGRect(x: startPos, y: 30, width: seekBarLen, height: style.seekBarHeight)
            slider.frame = CGRect(x: 0, y: 0, width: style.slideRadius*2, height: style.slideRadius*2)
            slider.center = CGPoint(x: startPos, y: 30 + 2)
            
            if markLabels.count > 0 {
                let w = seekBarLen / CGFloat(style.sectionCount)
                for i in 0 ..< markLabels.count {
                    let lbl = markLabels[I]
                    lbl.frame = CGRect(x: 0, y: 0, width: w, height: 15)
                    lbl.center = CGPoint(x: startPos + w*CGFloat(i), y: (style.sectionPosition == .top ? 15 : 45) )
                }
                
            }
            isInit = false
        }

    }
    
    /// 设置 value 值 供外部调用
    func setSlideCenter(val: Int) {
        value = val
        let centerX = centerXFromValue(val: val)
        slider.center.x = centerX
        changeSeekBgFrame()
    }
    
    
    /// 修改 灰色条位置
    fileprivate func changeSeekBgFrame() {
        let x = slider.center.x
        bgSeek.frame = CGRect(x: x, y: bgSeek.frame.origin.y, width: seekBarLen - x + startPos, height: bgSeek.frame.size.height)
    }
    
    
    /// 显示气泡
    fileprivate func showPopView() {
        if style.showPop && value >= style.valMin && value <= style.valMax {
            lblPop.text = String(value)
            lblPop.center = CGPoint(x: slider.center.x, y: 0)
            lblPop.isHidden = false
        } else {
            lblPop.isHidden = true
        }
    }
    
    /**
            通过 设置 value 计算 ceter X 值
     */
    fileprivate func centerXFromValue(val: Int) -> CGFloat {
        //(center.x - startPos) / vLen = val / (config.maxValue - startValue)
            
        let vLen = endPos - startPos
        let x = (CGFloat(val) - CGFloat(startValue))/CGFloat(style.valMax - startValue) * vLen + startPos
        return x
    }
    
    
    /**
            通过 center x 计算 value 值
     */
    fileprivate func valueFromSlidePosition() -> Int {

        let vPos = slider.center.x - startPos
        let v = CGFloat(style.valMax - startValue) * vPos/seekBarLen + CGFloat(startValue)
        let vInt: Int = lroundf(Float(v))
        return vInt
    }
    
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        ///重新计算尺寸
        let needH = style.seekBarHeight + 2*30
        let needW = seekBarLen + style.marginLR*2
        
        var w = self.frame.size.width
        var h = self.frame.size.height
        if needW > w {
            w = needW
        }
        if needH > h {
            h = needH
        }
        debugPrint("宽度=\(w), 高度=\(h)")
        let newSize = CGSize(width: w, height: h)
        let newRect = CGRect(origin: self.frame.origin, size: newSize)
        self.frame = newRect
        
        configByStyle()
        updateAllFrame()
        addGradient()

    }
    
    override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        debugPrint("------触摸开始")
        if slidEable {
            for touch in touches {
                if touch.view == slider {
                    beginTouch = true
                }
            }
        }

    }
    
    override func touchesMoved(_ touches: Set, with event: UIEvent?) {
        debugPrint("------触摸移动")

        if beginTouch && slidEable {
            for touch in touches {
                let loc = touch.location(in: self)
                let preLoc = touch.previousLocation(in: self)
                let x = loc.x - preLoc.x

                if slider.center.x >= endPos && x > 0 {
                    slider.center.x = endPos
                } else if slider.center.x <= startPos && x < 0 {
                    slider.center.x = startPos
                } else {
                    slider.center.x += x
                }
                
                let val = valueFromSlidePosition()
                if value != val {
                    value = val
                    showPopView()
                    if let block = slideBlock {
                        block(val, true)
                    }
                }
                changeSeekBgFrame()

            }
        }

    }

    
    override func touchesEnded(_ touches: Set, with event: UIEvent?) {
        debugPrint("------触摸结束")
        if beginTouch && slidEable  {
            if let block = slideBlock {
                block(value, false)
            }
            beginTouch = false
            lblPop.isHidden = true

        }
    }
    
}

你可能感兴趣的:(Swift 自定义带气泡 可渐变色 seekbar)