swiftgradientuislidercagradientlayer

How do I extract gradient color of minimum track image at the thumb position?


I want to change the thumb color same as the minimum track color. I need to extract color at the thumb location as it moves along the slider. I want my slider thumb to look something like this, and change color in accordance with the minimum track gradient color as it moves along the slider.

thumb

Following is my code for the gradient I created

func setSlider(slider:UISlider) {
            let tgl = CAGradientLayer()
            let frame = CGRect(x: 0.0, y: 0.0, width: slider.bounds.width, height: 10.0)
            tgl.frame = frame
            tgl.colors = [UIColor.black.cgColor, UIColor.red.cgColor, UIColor.yellow.cgColor, UIColor.green.cgColor]
            tgl.borderWidth = 1.0
            tgl.borderColor = UIColor.gray.cgColor
            tgl.cornerRadius = 5.0
            tgl.endPoint = CGPoint(x: 1.0, y:  1.0)
            tgl.startPoint = CGPoint(x: 0.0, y:  1.0)
            UIGraphicsBeginImageContextWithOptions(tgl.frame.size, false, 10.0)
            tgl.render(in: UIGraphicsGetCurrentContext()!)
            let backgroundImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            slider.setMaximumTrackImage(getBgImage(width: slider.bounds.width), for: .normal)
            slider.setMinimumTrackImage(backgroundImage, for: .normal)
}
I tried to fetch color using the following code:

    let color = sliderRating.minimumTrackImage(for: .normal)?.getPixelColor(point: CGPoint(x: Int(sender.value), y: 1))
    
    func getPixelColor(point: CGPoint) -> UIColor? {
                   guard let cgImage = cgImage else { return nil }
        
                   let width = Int(size.width)
                   let height = Int(size.height)
                   let colorSpace = CGColorSpaceCreateDeviceRGB()
        
                   guard let context = CGContext(data: nil,
                                                 width: width,
                                                 height: height,
                                                 bitsPerComponent: 8,
                                                 bytesPerRow: width * 4,
                                                 space: colorSpace,
                                                 bitmapInfo: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue)
                       else {
                           return nil
                   }
     
                   context.draw(cgImage, in: CGRect(origin: .zero, size: size))
        
                   guard let pixelBuffer = context.data else { return nil }
        
                   let pointer = pixelBuffer.bindMemory(to: UInt32.self, capacity: width * height)
                   let pixel = pointer[Int(point.y) * width + Int(point.x)]
        
                   let r: CGFloat = CGFloat(red(for: pixel))   / 255
                   let g: CGFloat = CGFloat(green(for: pixel)) / 255
                   let b: CGFloat = CGFloat(blue(for: pixel))  / 255
                   let a: CGFloat = CGFloat(alpha(for: pixel)) / 255
        
                return UIColor(red: r, green: g, blue: b, alpha: a)
               }
        
               private func alpha(for pixelData: UInt32) -> UInt8 {
                   return UInt8((pixelData >> 24) & 255)
               }
        
               private func red(for pixelData: UInt32) -> UInt8 {
                   return UInt8((pixelData >> 16) & 255)
               }
        
               private func green(for pixelData: UInt32) -> UInt8 {
                   return UInt8((pixelData >> 8) & 255)
               }
        
               private func blue(for pixelData: UInt32) -> UInt8 {
                   return UInt8((pixelData >> 0) & 255)
               }
        
               private func rgba(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) -> UInt32 {
                   return (UInt32(alpha) << 24) | (UInt32(red) << 16) | (UInt32(green) << 8) | (UInt32(blue) << 0)
}

Here is a similar question I found on stack overflow for more reference:

How can I extract the uislider gradient color at the thumb position?

I tried extracting pixel color from the image but it gives me only white, gray, and darker gray shades but my track has colors ranging from black to green.

I'm getting output like this:

ss 1

ss 2


Solution

  • The issue is how you're determining the point for your color.

    The code you show:

    let color = sliderRating.minimumTrackImage(for: .normal)?.getPixelColor(point: CGPoint(x: Int(sender.value), y: 1))
    

    is difficult to debug.

    Let's split that up:

        // safely unwrap the optional to make sure we get a valid image
        if let minImg = sender.minimumTrackImage(for: .normal) {
            let x = Int(sender.value)
            let y = 1
            let point = CGPoint(x: x, y: y)
            
            // to debug this:
            print("point:", point)
    
            // safely unwrap the optional returned color
            if let color = minImg.getPixelColor(pos: point) {
                // do something with color
            }
        }
    

    By default, a slider has values between 0.0 and 1.0. So as you drag the thumb, you'll see this output in the debug console:

    // lots of these
    point: (0.0, 1.0)
    point: (0.0, 1.0)
    point: (0.0, 1.0)
    point: (0.0, 1.0)
    point: (0.0, 1.0)
    // then when you final get all the way to the right
    point: (1.0, 1.0)
    

    As you see, you're not getting the point that you want on your image.

    You don't mention it, but if did something like this:

        sliderRating.minimumValue = 100
        sliderRating.maximumValue = 120
    

    Your x will range from 100 to 120. Again, not the point you want.

    Instead of using the .value, you want to get the horizontal center of the thumb rect for x, and the vertical center of the image size for y.

    Try it like this:

    @objc func sliderRatingValueChanged(_ sender: UISlider) {
    
        // get the slider's trackRect
        let trackR = sender.trackRect(forBounds: sender.bounds)
        // get the slider's thumbRect
        let thumbR = sender.thumbRect(forBounds: sender.bounds, trackRect: trackR, value: sender.value)
    
        // get the slider's minimumTrackImage
        if let minImg = sender.minimumTrackImage(for: .normal) {
            // we want point.x to be thumb rect's midX
            // we want point.y to be 1/2 the height of the min track image
            let point = CGPoint(x: thumbR.midX, y: minImg.size.height * 0.5)
    
            // for debugging:
            print("point:", point)
    
            // get the color at point
            if let color = minImg.getPixelColor(point: point) {
                // set tint color of thumb
                sender.thumbTintColor = color
            }
        }
    
        // now do something with the slider's value?
        let value = sender.value
    
    }