Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save msmollin/e81e3aa87497dd882786fc7f7311b7f8 to your computer and use it in GitHub Desktop.
Save msmollin/e81e3aa87497dd882786fc7f7311b7f8 to your computer and use it in GitHub Desktop.
A segmented control without the borders. Allows for a subtle background on the unselected states, and a tint on the selected state.
//
// ModernSegmentedControl.swift
//
//
// Created by Curtis Herbert on 3/8/18.
//
import Foundation
@objc final class ModernSegmentedControl: UIControl {
var buttonBackgroundColor: UIColor = UIColor.buttonBackground {
didSet {
updateButtonColors()
}
}
var selectedColor: UIColor = UIColor.primary {
didSet {
updateButtonColors()
}
}
var buttonTitles: [String] = [] {
didSet {
let origSubviews = stackView.subviews
for subview in origSubviews {
stackView.removeArrangedSubview(subview)
subview.removeFromSuperview()
}
for title in buttonTitles {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle(title, for: UIControl.State.normal)
button.titleLabel?.font = UIFontMetrics(forTextStyle: UIFont.TextStyle.callout).scaledFont(for: UIFont.systemFont(ofSize: 13.0))
button.titleLabel?.adjustsFontForContentSizeCategory = true
button.titleLabel?.textAlignment = .center
button.titleLabel?.minimumScaleFactor = 0.1
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 5, bottom: 8, right: 5)
button.setContentHuggingPriority(contentHuggingPriority(for: NSLayoutConstraint.Axis.horizontal), for: NSLayoutConstraint.Axis.horizontal)
button.setContentHuggingPriority(contentHuggingPriority(for: NSLayoutConstraint.Axis.vertical), for: NSLayoutConstraint.Axis.vertical)
button.setContentCompressionResistancePriority(contentCompressionResistancePriority(for: NSLayoutConstraint.Axis.horizontal), for: NSLayoutConstraint.Axis.horizontal)
button.setContentCompressionResistancePriority(contentCompressionResistancePriority(for: NSLayoutConstraint.Axis.vertical), for: NSLayoutConstraint.Axis.vertical)
button.titleLabel?.setContentHuggingPriority(contentHuggingPriority(for: NSLayoutConstraint.Axis.horizontal), for: NSLayoutConstraint.Axis.horizontal)
button.titleLabel?.setContentHuggingPriority(contentHuggingPriority(for: NSLayoutConstraint.Axis.vertical), for: NSLayoutConstraint.Axis.vertical)
button.titleLabel?.setContentCompressionResistancePriority(contentCompressionResistancePriority(for: NSLayoutConstraint.Axis.horizontal), for: NSLayoutConstraint.Axis.horizontal)
button.titleLabel?.setContentCompressionResistancePriority(contentCompressionResistancePriority(for: NSLayoutConstraint.Axis.vertical), for: NSLayoutConstraint.Axis.vertical)
stackView.addArrangedSubview(button)
button.addTarget(self, action: #selector(ModernSegmentedControl.buttonTapped(_:)), for: UIControl.Event.touchUpInside)
button.addTarget(self, action: #selector(ModernSegmentedControl.buttonHighlighted(_:)), for: UIControl.Event.touchDown)
button.addTarget(self, action: #selector(ModernSegmentedControl.buttonUnHighlighted(_:)), for: UIControl.Event.touchUpOutside)
}
selectedIndex = 0
}
}
var selectedIndex: Int = 0 {
didSet {
updateButtonColors()
}
}
func setEnabled(_ enabled: Bool, forSegmentAtIndex index: Int) {
if stackView.arrangedSubviews.count >= index, let button = stackView.arrangedSubviews[index] as? UIButton {
button.isEnabled = enabled
updateButtonColors()
}
}
@objc func buttonTapped(_ sender: Any?) {
guard let sender = sender as? UIView, let index = stackView.arrangedSubviews.firstIndex(of: sender) else {
return
}
let oldValue = selectedIndex
for case let button as UIButton in stackView.subviews {
button.isHighlighted = false
}
selectedIndex = index
if oldValue != index {
sendActions(for: UIControl.Event.valueChanged)
}
}
@objc func buttonHighlighted(_ sender: Any?) {
updateButtonColors()
}
@objc func buttonUnHighlighted(_ sender: Any?) {
updateButtonColors()
}
private func updateButtonColors() {
for (index, view) in stackView.subviews.enumerated() {
guard let button = view as? UIButton else {
continue
}
var bgColor = selectedIndex == index ? selectedColor : buttonBackgroundColor
var textColor = selectedIndex == index ? UIColor.white : selectedColor
if !button.isEnabled {
bgColor = bgColor.withAlphaComponent(0.5)
textColor = buttonBackgroundColor
} else if tintAdjustmentMode == .dimmed {
bgColor = bgColor.desaturated()
textColor = textColor.desaturated()
} else if button.isHighlighted && !button.isSelected {
textColor = textColor.withAlphaComponent(0.4)
}
button.backgroundColor = bgColor
button.setTitleColor(textColor, for: UIControl.State.normal)
}
}
override func tintColorDidChange() {
super.tintColorDidChange()
updateButtonColors()
}
private weak var stackView: UIStackView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
isAccessibilityElement = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
isAccessibilityElement = true
}
private func setup() {
let stack = UIStackView()
stack.alignment = .center
stack.axis = .horizontal
stack.distribution = .fillEqually
stack.spacing = 2.0
stack.translatesAutoresizingMaskIntoConstraints = false
addSubview(stack)
addConstraints(stack.constraint(equalTo: self))
addConstraint(heightAnchor.constraint(greaterThanOrEqualToConstant: 30.0))
stackView = stack
layer.cornerRadius = 6.0
layer.masksToBounds = true
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment