Automate This
Automate This

Reputation: 31364

How to set UISegmentedControl Tint Color for individual segment

Starting to learn Swift and am attempting to convert this ObjectiveC code:

[[mySegmentedControl.subviews objectAtIndex:0] setTintColor:[UIColor blueColor]]

This correctly sets the tint color of the first segment.


This is the closest I've come to getting a Swift version of the same code:

mySegmentedControl?.subviews[0].tintColor = UIColor.blueColor()

The error I get is '@Ivalue $T9' is not identical to 'UIColor!!'


I don't understand what this error means. When I look at the .tintColor method it list UIColor!? and I haven't found what the !? together means in Swift yet.

Upvotes: 8

Views: 31254

Answers (10)

prad
prad

Reputation: 1156

From iOS 13 onwards, the property selectedSegmentTintColor can be used to set tint color of segment control!

So simply do:

segmentControl.selectedSegmentTintColor = .red

If you are supporting below iOS 13,

   if #available(iOS 13.0, *) {
      segmentControl.selectedSegmentTintColor = .red
    } else {
      // Fallback on earlier versions
      // Solution posted by David can be used here
    }

Upvotes: 0

David_2877
David_2877

Reputation: 327

For Swift 5.1 i found to work:

//To set Text Colour when Segment Selected
segmentOutlet.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.red], for: UIControl.State.selected)

//To Set Background Colour when Segment Selected,
//The number in the [] is the segment that gets value change
let subViewOfSegment: UIView = segmentOutlet.subviews[1] as UIView
        subViewOfSegment.backgroundColor = UIColor.blue

I place these within the Switch Statement used to capture the Action of the button when pressed.

Upvotes: 2

BHUVANESH MOHANKUMAR
BHUVANESH MOHANKUMAR

Reputation: 2777

This code works fine with latest version of Swift as on August 2019 (Swift 3.0)

Here this code I have implemented is the extension for the Segment control and can be used for all the segment controls in the application, where the set of code has to defined in application class.

Extension method can be used directly in the application, also you can add all the settings to same method or different methods in extension class, as shown below.

extension UISegmentedControl {
func setSegmentStyle() {
    setBackgroundImage(imageWithColor(color: backgroundColor!), for: .normal, barMetrics: .default)
    setBackgroundImage(imageWithColor(color: tintColor!), for: .selected, barMetrics: .default)
    setDividerImage(imageWithColor(color: UIColor.clear), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)



     let segAttributes: NSDictionary = [
            NSForegroundColorAttributeName: UIColor.gray,
            NSFontAttributeName: UIFont(name: "System-System", size: 14)!
        ]

        setTitleTextAttributes(segAttributes as [NSObject : AnyObject], for: UIControlState.selected)
    }

    // create a 1x1 image with this color
    private func imageWithColor(color: UIColor) -> UIImage {
        let rect = CGRect(x: 0.0, y: 0.0, width:  1.0, height: 1.0)
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()
        context!.setFillColor(color.cgColor);
        context!.fill(rect);
        let image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return image!
    }
}

Everywhere for the segments the below code can be used

 self.mySegment.setSegmentStyle()

enter image description here

Upvotes: 5

Volodymyr Nazarkevych
Volodymyr Nazarkevych

Reputation: 126

The solution is only for two segments but it could be easily extended to use for as much as you need. Firstly I will suggest to create an enum:

    enum SegmentedSections: Int { 
      case first, 
      case second
    }

Than create a funciton, and call this function in viewDidLoad, and also each time call it when .valueChanged happens in segmentedControl:

func setProperSegmentedControlColoring(_ segment: UISegmentedControl, type: SegmentedSections) {
    setSeparatorImages(for: segment, with: type)
    let subviews = segment.subviews
    let sortedViews = subviews.sorted(by: { $0.frame.origin.x < $1.frame.origin.x })

    for (index, view) in sortedViews.enumerated() {
        switch type {
        case .first:
            if index == segment.selectedSegmentIndex {
                view.tintColor = .red
            } else {
                view.tintColor = .blue
            }
        case .second:
            if index == segment.selectedSegmentIndex {
                view.tintColor = .blue
            } else {
                view.tintColor = .red
            }
        }
    }
}

Also you will need to change divider image accordingly:

func setSeparatorImages(for segment: UISegmentedControl, with type: EarnType) {
    switch type {
    case .first:
        let image = UIImage(color: .red)
        segment.setDividerImage(image, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
    case .second:
        let image = UIImage(color: .blue)
        segment.setDividerImage(image, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
    }
}

Also, you will need to have an extension for UIImage. You can find it here.

Upvotes: 0

C.F. Wang
C.F. Wang

Reputation: 63

sender.subviews.sort doesn't work in Swift 4 and remove border is referenced on How to remove border from segmented control

extension UISegmentedControl {

// create a 1x1 image with this color
private func imageWithColor(color: UIColor) -> UIImage {
    let rect = CGRect(x: 0.0, y: 0.0, width:  1.0, height: 1.0)
    UIGraphicsBeginImageContext(rect.size)
    let context = UIGraphicsGetCurrentContext()
    context!.setFillColor(color.cgColor);
    context!.fill(rect);
    let image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image!
}

func removeBackgroundColors() {
    self.setBackgroundImage(imageWithColor(color: .clear), for: .normal, barMetrics: .default)
    self.setBackgroundImage(imageWithColor(color: .clear), for: .selected, barMetrics: .default)
    self.setDividerImage(imageWithColor(color: UIColor.clear), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
}

struct viewPosition {
    let originX: CGFloat
    let originIndex: Int
}

func updateTintColor(selected: UIColor, normal: UIColor) {
    let views = self.subviews
    var positions = [viewPosition]()
    for (i, view) in views.enumerated() {
        let position = viewPosition(originX: view.frame.origin.x, originIndex: i)
        positions.append(position)
    }
    positions.sort(by: { $0.originX < $1.originX })

    for (i, position) in positions.enumerated() {
        let view = self.subviews[position.originIndex]
        if i == self.selectedSegmentIndex {
            view.tintColor = selected
        } else {
            view.tintColor = normal
        }
    }
}
}
override func viewDidLoad() {
    super.viewDidLoad()

    mySegment.removeBackgroundColors()
    mySegment.backgroundColor = .clear
    mySegment.updateTintColor(selected: myNavigationColor, normal: text1Color)
}

Upvotes: 0

Vitalii
Vitalii

Reputation: 4437

After analyzing and trying lots of answers to this and other similar questions I realized that with a 3rd-party custom segmented control it will be much easier and safer to do customizations than trying to hack Apple's UISegmentedControl.

Here's an example of customization with XMSegmentedControl (Swift 3).

Some in code:

    mySegmentControl.delegate = self
    mySegmentControl.font = UIFont.systemFont(ofSize: 12)

And some in the Interface Builder (can be done in code too if you want):

enter image description here

The result is:

enter image description here

In my case it looks very much like system one, but still there are minor differences I had to do exactly as designer wanted it to be.

Note that XMSegmentedControl doesn't allow to have different background colors for different segments, but you can easily add this if needed, since it is a simple .swift file, which is very easy to understand and modify.

Upvotes: 1

nahlamortada
nahlamortada

Reputation: 489

class MyUISegmentedControl: UISegmentedControl {

    required init(coder aDecoder: NSCoder){
        super.init(coder: aDecoder)!
        for subViewOfSegment: UIView in subviews {
            subViewOfSegment.tintColor = UIColor.red
        }
    }
}

Upvotes: -1

Adam Gammell
Adam Gammell

Reputation: 131

The easiest way I have found is:

segmentControl.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.redColor()], forState: UIControlState.Selected)

Upvotes: 13

gabo
gabo

Reputation: 1261

For changing the tintColor of the selected segment

Use the value changed event for the UISegmentControl to sort the segments in order by their origin x value, then loop and compare the selectedSegmentIndex property. Here is an example with assuming a segmented control of 4 segments:

@IBAction func indexChanged(sender: UISegmentedControl) {

    let sortedViews = sender.subviews.sort( { $0.frame.origin.x < $1.frame.origin.x } )

    for (index, view) in sortedViews.enumerate() {
        if index == sender.selectedSegmentIndex {
            view.tintColor = UIColor.blueColor()
        } else {
            view.tintColor = UIColor.lightGrayColor()
        }
    }

}

Then in viewDidLoad set the tintColor for the initially selected segment, in this case it is the first:

let sortedViews = segmentedControlOutletVariable.subviews.sort( { $0.frame.origin.x < $1.frame.origin.x } )
sortedViews[0].tintColor = UIColor.blueColor()

Upvotes: 2

Prajeet Shrestha
Prajeet Shrestha

Reputation: 8108

This will solve your problem:

var subViewOfSegment: UIView = mySegmentedControl.subviews[0] as UIView
subViewOfSegment.tintColor = UIColor.blueColor()

You can also

(mySegmentedControl.subviews[0] as UIView).tintColor = UIColor .blueColor()

Upvotes: 23

Related Questions