Search code examples
iosswiftuisegmentedcontrol

Updating the UISegmentControl programmatically does not update the font color of the unselected segment


I'm using a segmented control as sub tabs for a tab bar controller. I have a button on the main tab that's supposed to transition to the second tab (the one with the sub tabs) and programmatically switch the UISegmentedControl to the first tab (0 index).

Of course, tapping on the segmented control works fine. But, when I try to change the tab programmatically using selectedSegmentIndex, the tab that get's deselected does not update it's font color, which makes the label seem to disappear.

tab bar image

Every post I've read about how to change tabs programmatically indicates that I'm doing it the right way. Can anyone tell me what I'm doing wrong? Thank you for your help!

Here is the code:

class AdvertisingViewController: BaseViewController, TabbedController, ClientSelectorController {

  var clientSelectorView: ClientSelector?
  var tabControl: UISegmentedControl!
  var tabView: UIView!
  var nextTabIndex: Int? = nil
  var activeTab: AdvertisingTabContent? = nil
  var tabs: [AdvertisingTabContent]!
  let margin: CGFloat = 20

  override func viewDidLoad() {
    super.viewDidLoad()

    // Initialize the tab view.
    tabView = UIView()
    view.addSubview(tabView!)

    // Initialize the tab controls.
    tabControl = UISegmentedControl()
    tabControl.insertSegment(withTitle: "Calendar", at: 0, animated: false)
    tabControl.insertSegment(withTitle: "TV", at: 1, animated: false)
    tabControl.insertSegment(withTitle: "Radio", at: 2, animated: false)
    tabControl.insertSegment(withTitle: "Search", at: 3, animated: false)
    tabControl.insertSegment(withTitle: "Display", at: 4, animated: false)
    tabControl.selectedSegmentIndex = 0
    tabControl.addTarget(self, action: #selector(selectedTab(sender:)), for: .valueChanged)
    view.addSubview(tabControl!)

    // Tab control styles.
    let font = UIFont.systemFont(ofSize: 12)
    tabControl.setTitleTextAttributes([
      NSAttributedStringKey.font: font,
      NSAttributedStringKey.foregroundColor: Color.lightGrey
    ], for: .normal)
    tabControl.removeBorders()

    // Intialize the tabs.
    nextTabIndex = nil
    tabs = [
      CampaignsTab(view: tabView),
      TVTab(view: tabView),
      RadioTab(view: tabView),
      PaidSearchTab(view: tabView),
      DisplayTab(view: tabView),
    ]

    // Bind clientSelector button to clientSelectButtonPressed method.
    initializeClientSelector()

    // Set the background color.
    tabView.backgroundColor = Color.backgroundColor
  }

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // Update client selector title.
    layoutClientSelector()

    // Update tab view & segmented control
    let controlHeight: CGFloat = 35
    tabControl.frame = CGRect(x: margin, y: clientSelectorView!.frame.height, width: view.frame.width - (margin * 2), height: controlHeight)

    // Update the tab view.
    tabView.frame = CGRect(x: 0, y: clientSelectorView!.frame.height + controlHeight + margin, width: view.frame.width, height: view.frame.height - clientSelectorView!.frame.height - controlHeight - margin)

    // Build tab.
    buildTab()
  }

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    // Update the tabControl.
    if let next = nextTabIndex {
      if next != tabControl.selectedSegmentIndex {
        tabControl.selectedSegmentIndex = next
      }
      nextTabIndex = nil
    }
  }

  func clientSelectorClicked(selectorModal: UIViewController) {
    present(selectorModal, animated: true, completion: nil)
  }

  // MARK: - Tabs Control

  /**
   The user selected a different tab.
   */
  @objc func selectedTab(sender: UIButton) {
    buildTab()
  }

  /**
   A tab was selected from another part of the app.
   (This is called before viewWillAppear from another tab)
   */
  func switchToTab(atIndex index: Int) {
    nextTabIndex = index
  }

  func buildTab() {

    // Clean up the old tab.
    activeTab?.cleanUp()
    activeTab = nil

    // Check next index.
    var index = tabControl.selectedSegmentIndex
    if let next = nextTabIndex {
      index = next
    }

    // Add/Build the new tab.
    if index >= 0 && tabs.count > index {
      activeTab = tabs[index]
      activeTab?.buildViews()
    }
  }
}

Update:

Spent a couple more hours looking for a solution this morning. I also tried hacking it by finding the label in the subviews of the segmented control, and manually changing the font color. I could change the text of the label, but not the font color. Something is overriding it.

Is there some other way to programmatically change the selected index? Do I need to submit a ticket to apple? Is nobody else having this problem? Thanks in advance for any help you can provide.


Solution

  • After much struggling, I finally found an answer. I'm removing the borders using the method from the following post:

    Swift: How to remove border from segmented control

    extension UISegmentedControl {
      func applyAppStyles() {
        setBackgroundImage(imageWithColor(color: Color.contentBackgroundColor), for: .normal, barMetrics: .default)
        setBackgroundImage(imageWithColor(color: Color.orange), for: .selected, barMetrics: .default)
        setDividerImage(imageWithColor(color: UIColor.clear), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
      }
    
      // 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!
      }
    }
    

    My first call to setBackgroundImage is causing the issue; I'm not sure why. But the following code fixes it:

    tabControl.setTitleTextAttributes([
      NSAttributedStringKey.foregroundColor: Color.darkGreyBlue
    ], for: .selected)