Search code examples
iosiphoneuikituislidercagradientlayer

iOS UISLider with gradient layer


I'm building an iOS application in which I have to implement a custom UISlider. The problem is that the built-in UISlider doesn't support gradient track. Another issue is my UI style guide shows that the current tracking value rectangle should be a gradient of two colors as shown in the image

enter image description here

How can I build a customized version of the UISlider? I have thought of either subclassing the existing one or by building a UIControl subclass.

I'm using xcode 9.4 and swift 4.2

Thanks in advance!


Solution

  • I ended up solving the problem by setting the gradient layer as an image for the slider minimum track image as following:

    @IBDesignable
    class GradientSlider: UISlider {
    
        @IBInspectable var thickness: CGFloat = 20 {
            didSet {
                setup()
            }
        }
    
        @IBInspectable var sliderThumbImage: UIImage? {
            didSet {
                setup()
            }
        }
    
        func setup() {
            let minTrackStartColor = Palette.SelectiveYellow
            let minTrackEndColor = Palette.BitterLemon
            let maxTrackColor = Palette.Firefly
            do {
                self.setMinimumTrackImage(try self.gradientImage(
                size: self.trackRect(forBounds: self.bounds).size,
                colorSet: [minTrackStartColor.cgColor, minTrackEndColor.cgColor]),
                                      for: .normal)
                self.setMaximumTrackImage(try self.gradientImage(
                size: self.trackRect(forBounds: self.bounds).size,
                colorSet: [maxTrackColor.cgColor, maxTrackColor.cgColor]),
                                      for: .normal)
                self.setThumbImage(sliderThumbImage, for: .normal)
            } catch {
                self.minimumTrackTintColor = minTrackStartColor
                self.maximumTrackTintColor = maxTrackColor
            }
        }
    
        func gradientImage(size: CGSize, colorSet: [CGColor]) throws -> UIImage? {
            let tgl = CAGradientLayer()
            tgl.frame = CGRect.init(x:0, y:0, width:size.width, height: size.height)
            tgl.cornerRadius = tgl.frame.height / 2
            tgl.masksToBounds = false
            tgl.colors = colorSet
            tgl.startPoint = CGPoint.init(x:0.0, y:0.5)
            tgl.endPoint = CGPoint.init(x:1.0, y:0.5)
    
            UIGraphicsBeginImageContextWithOptions(size, tgl.isOpaque, 0.0);
            guard let context = UIGraphicsGetCurrentContext() else { return nil }
            tgl.render(in: context)
            let image = 
    
        UIGraphicsGetImageFromCurrentImageContext()?.resizableImage(withCapInsets: 
            UIEdgeInsets.init(top: 0, left: size.height, bottom: 0, right: size.height))
            UIGraphicsEndImageContext()
            return image!
        }
    
        override func trackRect(forBounds bounds: CGRect) -> CGRect {
            return CGRect(
                x: bounds.origin.x,
                y: bounds.origin.y,
                width: bounds.width,
                height: thickness
            )
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            setup()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setup()
        }
    
    
    }