7

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.

Automate This
  • 30,726
  • 11
  • 60
  • 82
  • If mySegmentedControl an IBOutlet? If it is, declare it @IBOutlet var mySegmentedControl = UISegmentedControl! Then you don't need the ? operator. That won't solve the whole problem but that's one thing. If subviews[0] can return nil, then it's an optional, so maybe put a ! after [0]. – clearlight Mar 28 '15 at 02:18

10 Answers10

23

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()
Prajeet Shrestha
  • 7,978
  • 3
  • 34
  • 63
13

The easiest way I have found is:

segmentControl.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.redColor()], forState: UIControlState.Selected)
Adam Gammell
  • 131
  • 1
  • 2
  • 1
    This works for simple cases if you wanted to change the button color based on selection. I needed to change a specific index color regardless of the selected state for a custom UI. – Automate This Jan 05 '16 at 20:46
  • Swift 5 version: segmentControl.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.red], for: UIControl.State.selected) – Mikrasya May 25 '19 at 08:12
5

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

BHUVANESH MOHANKUMAR
  • 2,747
  • 1
  • 33
  • 33
  • 1
    This is, in my eyes, the only legit answer. That's exactly why a segmented control has the `setBackgroundImage(_ backgroundImage: UIImage?, for state: UIControl.State, barMetrics: UIBarMetrics)`. All the other solutions rely on a certain view hierarchy which may change with iOS versions. Starting with iOS 13 things will get a lot easier using `selectedSegmentTintColor: UIColor?` – Joachim Deelen Aug 13 '19 at 16:18
2

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()
gabo
  • 1,133
  • 8
  • 7
2

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.

David_2877
  • 257
  • 4
  • 7
1

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.

Vitalii
  • 4,267
  • 1
  • 40
  • 45
  • I've updated my answer to show customization possibilities. The custom segmented control has simple and clear code - you can easily modify it to be able to set individual colors for each segment. – Vitalii Apr 21 '17 at 11:27
0

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)
}
C.F. Wang
  • 53
  • 5
0

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.

0

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
    }
prad
  • 1,086
  • 1
  • 11
  • 19
  • But can you use this to set different tint colors on each button? That's the point of this question. Sorry, i don't have xCode anymore to test myself. – Automate This Jun 12 '20 at 14:04
-1
class MyUISegmentedControl: UISegmentedControl {

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