Skip to content

Instantly share code, notes, and snippets.

@StanGenchev
Last active March 5, 2021 13:35
Show Gist options
  • Save StanGenchev/de68688ed580b64603fc5e91545c895b to your computer and use it in GitHub Desktop.
Save StanGenchev/de68688ed580b64603fc5e91545c895b to your computer and use it in GitHub Desktop.
CustomSegmentedControl for iOS in Swift 5. Rounded corners, custom colors, animations, shadow, etc.
//
// CustomSegmentedControl.swift
//
// Created by Stan Genchev on 2.03.21.
//
import UIKit
@IBDesignable
class CustomSegmentedControl: UIControl {
var buttons = [UIButton]()
var selector: UIView!
var selectedSegmentIndex = 0
@IBInspectable
var backgroundViewColor: UIColor = .white
@IBInspectable
var borderWidth: CGFloat = 2 {
didSet {
layer.borderWidth = borderWidth
}
}
@IBInspectable
var autoRoundCorners: Bool = true
@IBInspectable
var roundedCornerRadius: CGFloat = 0.0
@IBInspectable
var borderColor: UIColor = .black {
didSet {
layer.borderColor = borderColor.cgColor
}
}
@IBInspectable
var pipeDelimitedTitles: String = "First|Second"
@IBInspectable
var titleColor: UIColor = .black
@IBInspectable
var selectorColor: UIColor = .black
@IBInspectable
var selectorTitleColor: UIColor = .white
@objc func buttonTapped(button: UIButton) {
for (index, btn) in buttons.enumerated() {
if btn == button {
selectedSegmentIndex = index
let startPosition = ((frame.width - (borderWidth * 2)) / CGFloat(buttons.count)) * CGFloat(index) + borderWidth
UIView.animate(withDuration: 0.3, animations: {
self.selector.frame.origin.x = startPosition
})
btn.setTitleColor(selectorTitleColor, for: .normal)
} else {
btn.setTitleColor(titleColor, for: .normal)
}
}
sendActions(for: .valueChanged)
}
func updateView() {
buttons.removeAll()
subviews.forEach { (view) in
view.removeFromSuperview()
}
let titles = pipeDelimitedTitles.components(separatedBy: "|")
for title in titles {
let button = UIButton(type: .system)
button.setTitle(title, for: .normal)
button.setTitleColor(titleColor, for: .normal)
button.addTarget(self, action: #selector(buttonTapped(button:)), for: .touchUpInside)
buttons.append(button)
}
buttons[0].setTitleColor(selectorTitleColor, for: .normal)
let selectorWidth: CGFloat
let selectorHeight: CGFloat
selectorWidth = (frame.width - (borderWidth * 2)) / CGFloat(buttons.count)
selectorHeight = frame.height - (borderWidth * 2)
selector = UIView(frame: CGRect(x: borderWidth, y: borderWidth, width: selectorWidth, height: selectorHeight))
selector.backgroundColor = selectorColor
if (autoRoundCorners) {
layer.cornerRadius = frame.height / 2
selector.layer.cornerRadius = selector.layer.frame.height / 2
} else {
layer.cornerRadius = roundedCornerRadius
selector.layer.cornerRadius = roundedCornerRadius
}
let stack = UIStackView(arrangedSubviews: buttons)
stack.axis = .horizontal
stack.alignment = .fill
stack.distribution = .fillEqually
addSubview(selector)
addSubview(stack)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.topAnchor.constraint(equalTo: self.topAnchor, constant: borderWidth).isActive = true
stack.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -borderWidth).isActive = true
stack.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -borderWidth).isActive = true
stack.leftAnchor.constraint(equalTo: self.leftAnchor, constant: borderWidth).isActive = true
}
private func drawBackground() {
let radius: CGFloat
if (autoRoundCorners) {
radius = self.layer.frame.height / 2
} else {
radius = roundedCornerRadius
}
let shapeLayer = CAShapeLayer()
shapeLayer.path = UIBezierPath(
roundedRect: CGRect(x: 0, y: 0, width: layer.frame.width, height: layer.frame.height),
cornerRadius: radius
).cgPath
shapeLayer.fillColor = backgroundViewColor.cgColor
self.layer.addSublayer(shapeLayer)
}
private func addShadow() {
self.layer.shadowColor = UIColor.lightGray.cgColor
self.layer.shadowOpacity = 1
self.layer.shadowOffset = .zero
self.layer.shadowRadius = 10
}
override func draw(_ rect: CGRect) {
// If we set this to true, we will not be able to draw a shadow around the view
// This is also the reason why we have a separate View, used
// for the background color, instead of just setting backgroundColor directly
layer.masksToBounds = false
// Do not forget to set the background color of the UIView in Interface Deigner to clear
// By default it's white and you will not see the round corners.
backgroundColor = .clear
drawBackground()
updateView()
addShadow()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment