Last active
March 5, 2021 13:35
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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