Search code examples
swiftuiviewuisegmentedcontrol

How do I prevent one UIView from being hidden by another UIView?


I'm creating a custom, reusable segmented controller using UIViews and I'm having a problem with overlapping views. It currently looks like this:

enter image description here

You can see that the blue selector is under the buttons but I want it to sit at the bottom and be four pixels high. To do this, I have:

let numberOfButtons = CGFloat(buttonTitles.count)
let selectorWidth = frame.width / numberOfButtons
let selectorYPosition = frame.height - 3 <--- This cause it to be hidden behind the button
selector = UIView(frame: CGRect(x: 0, y: selectorYPosition, width: selectorWidth, height: 4))
selector.layer.cornerRadius = 0
selector.backgroundColor = selectorColor
addSubview(selector)
bringSubviewToFront(selector) <--- I thought this would work but it does nothing

which results in the selector UIView being hidden behind the segment UIView (I have the Y position set to - 3 so you can see how it's being covered up. I actually want it to be - 4, but that makes it disappear entirely):

enter image description here

I thought using bringSubviewToFront() would bring it in front of the segment UIView but it doesn't seem to do anything. I've looked through Apple View Programming Guide and lots of SO threads but can't find an answer.

Can anybody help me see what I'm missing?

Full code:

class CustomSegmentedControl: UIControl {
  var buttons = [UIButton]()
  var selector: UIView!
  var selectedButtonIndex = 0

  var borderWidth: CGFloat = 0 {
    didSet {
      layer.borderWidth = borderWidth
    }
  }

  var borderColor: UIColor = UIColor.black {
    didSet {
      layer.borderColor = borderColor.cgColor
    }
  }

  var separatorBorderColor: UIColor = UIColor.lightGray {
    didSet {

    }
  }

  var commaSeparatedTitles: String = "" {
    didSet {
      updateView()
    }
  }

  var textColor: UIColor = .lightGray {
    didSet {
      updateView()
    }
  }

  var selectorColor: UIColor = .blue {
    didSet {
      updateView()
    }
  }

  var selectorTextColor: UIColor = .black {
    didSet {
      updateView()
    }
  }

  func updateView() {
    buttons.removeAll()
    subviews.forEach { $0.removeFromSuperview() }

    // create buttons
    let buttonTitles = commaSeparatedTitles.components(separatedBy: ",")
    for buttonTitle in buttonTitles {
      let button = UIButton(type: .system)
      button.setTitle(buttonTitle, for: .normal)
      button.setTitleColor(textColor, for: .normal)
      button.backgroundColor = UIColor.white
      button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
      buttons.append(button)
    }

    // make first button selected
    buttons[0].setTitleColor(selectorTextColor, for: .normal)

    let numberOfButtons = CGFloat(buttonTitles.count)
    let selectorWidth = frame.width / numberOfButtons
    let selectorYPosition = frame.height - 3
    selector = UIView(frame: CGRect(x: 0, y: selectorYPosition, width: selectorWidth, height: 4))
    selector.layer.cornerRadius = 0
    selector.backgroundColor = selectorColor
    addSubview(selector)
    bringSubviewToFront(selector)

    let stackView = UIStackView(arrangedSubviews: buttons)
    stackView.axis = .horizontal
    stackView.alignment = .fill
    stackView.distribution = .fillEqually
    addSubview(stackView)

    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
    stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
    stackView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
    stackView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
  }

  @objc func buttonTapped(button: UIButton) {
    for (buttonIndex, btn) in buttons.enumerated() {
      btn.setTitleColor(textColor, for: .normal)

      if btn == button {
        let numberOfButtons = CGFloat(buttons.count)
        let selectorStartPosition = frame.width / numberOfButtons * CGFloat(buttonIndex)
        UIView.animate(withDuration: 0.3, animations: { self.selector.frame.origin.x = selectorStartPosition })
        btn.setTitleColor(selectorTextColor, for: .normal)
      }
    }

    sendActions(for: .valueChanged)

  }

}

Solution

  • You are covering up your selector with the stackView.

    You need to do:

    bringSubviewToFront(selector)
    

    after you have added all of the views. Move that line to the bottom of updateView().