iosswiftuikitgradientsegmentedcontrol

Gradient tint color in segmented control


I get gradient image with this method

func gradient(size:CGSize,color:[UIColor]) -> UIImage?{
    //turn color into cgcolor
    let colors = color.map{$0.cgColor}
    //begin graphics context
    UIGraphicsBeginImageContextWithOptions(size, true, 0.0)
    guard let context = UIGraphicsGetCurrentContext() else {
        return nil
    }
    // From now on, the context gets ended if any return happens
    defer {UIGraphicsEndImageContext()}
    //create core graphics context
    let locations:[CGFloat] = [0.0,1.0]
    guard let gredient = CGGradient.init(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors as NSArray as CFArray, locations: locations) else {
        return nil
    }
    //draw the gradient
    context.drawLinearGradient(gredient, start: CGPoint(x:0.0,y:size.height), end: CGPoint(x:size.width,y:size.height), options: [])
    // Generate the image (the defer takes care of closing the context)
    return UIGraphicsGetImageFromCurrentImageContext()
}

Then I set tintColor of segmented control to gradient:

    let gradientImage = gradient(size: listSegmentedControl.frame.size, color: [UIColor.black, UIColor.red])!
    listSegmentedControl.tintColor = UIColor(patternImage: gradientImage)

and that doesn't work. However, same code works for setting backgroundColor:

    let gradientImage = gradient(size: listSegmentedControl.frame.size, color: [UIColor.black, UIColor.red])!
    listSegmentedControl.backgroundColor = UIColor(patternImage: gradientImage)

Does anybody have any ideas why? I really need to set gradient tintColor. Any help is very appreciated.

EDIT:

Ideally I want my segmented control to look like this:

enter image description here


Solution

  • This is a known hack to change the tint color of UISegmentedControl

       let sortedViews = listSegmentedControl.subviews.sorted( by: { $0.frame.origin.x < $1.frame.origin.x } )
    
        for (index, view) in sortedViews.enumerated() {
            if index == listSegmentedControl.selectedSegmentIndex {
                view.tintColor = UIColor(patternImage: gradientImage)
            } else {
                view.tintColor = UIColor.gray //Whatever the color of non selected segment controller tab
            }
        }
    

    Though looks like a ugly hack, I have been using it from a quite a while and seems fairly straight forward. Hope it helps.

    EDIT:

    Is this what you need buddy?

    enter image description here

    If yes lemme know, Ill post the code for the same.

    EDIT 2:

    As OP has mentioned in his comment that the output he is expecting is same as the one I showed in image above, providing code for the same.

    Disclaimer:

    As mentioned by rmaddy in his comments below, this is a hack and makes use of undocumented (Complete public API though) but a very well known hack to change the tint color of UISegemntedControl that exists from as far as iOS 5 (Thats how I remember, lemme know if I am wrong )

    So use answer with the caution in mind that, in future releases of iOS Apple might change the structure of subviews in UISegemntedControl and might affect your O/P. Nothing that I can see, will result in crash but might affect the way O/P is rendered on screen.

    I have declared a variable so that GradientImage can be generated only once, but its up to your implementation to use it the way you want

    var gradientImage : UIImage! = nil
    

    In ViewDidLoad I initialize the gradientImage and UISegmentedControl as

    override func viewDidLoad() {
            super.viewDidLoad()
            gradientImage = gradient(size: segmentControl.frame.size, color: [UIColor.black, UIColor.red])!
    
            //I have specified custom font need not necessarily be used
            //Font color attribute is important though, usually `UISegementedControl` title takes color from tint color, because we might need a different color for text to highlight above gradient color am using custom font colors
    
            let font = UIFont(name: "HelveticaNeue-Medium", size: 20)
            segmentControl.setTitleTextAttributes([NSFontAttributeName : font!, NSForegroundColorAttributeName : UIColor.blue], for: .normal)
            segmentControl.setTitleTextAttributes([NSForegroundColorAttributeName : UIColor.white], for: .selected)
    
            //Set the border color and border to `UISegmentedControl` and also make it round corner
    
            segmentControl.layer.borderColor = UIColor(patternImage: gradientImage).cgColor
            segmentControl.layer.borderWidth = 2
            segmentControl.layer.masksToBounds = true
            segmentControl.layer.cornerRadius = 10
    
            //In order to update the selected Segment tint and background color we need to call multiple statements every time selection changes hence I have moved it to the function and called it in viewDidLoad
    
            updateGradientBackground()
        }
    

    Finally updateGradientBackground function definition is same as the one I posted in my original answer

    fileprivate func updateGradientBackground() {
            let sortedViews = segmentControl.subviews.sorted( by: { $0.frame.origin.x < $1.frame.origin.x } )
            for (index, view) in sortedViews.enumerated() {
                if index == segmentControl.selectedSegmentIndex {
                    //very important thing to notice here is because tint color was not honoring the `UIColor(patternImage` I rather used `backgroundColor` to create the effect and set clear color as clear color
                    view.backgroundColor = UIColor(patternImage: self.gradientImage)
                    view.tintColor = UIColor.clear
                } else {
                    //very important thing to notice here is because tint color was not honoring the `UIColor(patternImage` I rather used `backgroundColor` to create the effect and set clear color as clear color
                    view.backgroundColor = UIColor.white //Whatever the color of non selected segment controller tab
                    view.tintColor = UIColor.clear
                }
            }
        }
    

    Finally, in IBAction of UISegmentedControl, simply call

    @IBAction func segmentControllerTapped(_ sender: UISegmentedControl) {
        self.updateGradientBackground()
    }
    

    Hope this helps