Skip to content

Instantly share code, notes, and snippets.

@benjaminsnorris
Last active December 17, 2015 23:02
Show Gist options
  • Save benjaminsnorris/16d844087934a43534fa to your computer and use it in GitHub Desktop.
Save benjaminsnorris/16d844087934a43534fa to your computer and use it in GitHub Desktop.
Custom Tab Bar
//
// CustomTabBar.swift
// welbe
//
// Created by Ben Norris on 12/8/15.
// Copyright © 2015 O.C. Tanner Corporation. All rights reserved.
//
import UIKit
protocol CustomTabBarDelegate {
func tabBar(tabBar: CustomTabBar, didSelectTab tab: Int)
}
@IBDesignable class CustomTabBar: UIView {
// MARK: - Inspectable properties
@IBInspectable var underlineHeight: CGFloat = 2.0 {
didSet {
underlineHeightConstraint?.constant = underlineHeight
}
}
@IBInspectable var underlineOnBottom: Bool = true {
didSet {
configureUnderlineVerticalPosition()
}
}
@IBInspectable var selectedIndex: Int = 0 {
didSet {
updateButtons()
}
}
@IBInspectable var accentColor: UIColor = UIColor.blueColor() {
didSet {
updateColors()
}
}
@IBInspectable var defaultTitleColor: UIColor = UIColor.blackColor() {
didSet {
updateColors()
}
}
// MARK: - Exposed properties
var titles: [String] = ["one", "two"] {
didSet {
configureButtons()
}
}
var delegate: CustomTabBarDelegate?
// MARK: - Private properties
private let stackView = UIStackView()
private var buttons = [UIButton]()
private let underline = UIView()
private var underlineHeightConstraint: NSLayoutConstraint?
private var underlineWidthConstraint: NSLayoutConstraint?
private var underlinePositionContraint: NSLayoutConstraint?
private var underlineTopConstraint: NSLayoutConstraint?
private var underlineBottomConstraint: NSLayoutConstraint?
// MARK: - Method overrides
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupViews()
}
// MARK: - Exposed API
func tabSelected(button: UIButton) {
guard let index = buttons.indexOf(button) else { fatalError("Invalid button selected") }
selectedIndex = index
delegate?.tabBar(self, didSelectTab: index)
}
}
// MARK: - Private methods
private extension CustomTabBar {
func setupViews() {
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.distribution = .FillEqually
addSubview(stackView)
stackView.leadingAnchor.constraintEqualToAnchor(leadingAnchor).active = true
stackView.topAnchor.constraintEqualToAnchor(topAnchor).active = true
stackView.trailingAnchor.constraintEqualToAnchor(trailingAnchor).active = true
stackView.bottomAnchor.constraintEqualToAnchor(bottomAnchor).active = true
underline.translatesAutoresizingMaskIntoConstraints = false
addSubview(underline)
underlineBottomConstraint = underline.bottomAnchor.constraintEqualToAnchor(bottomAnchor)
underlineBottomConstraint?.active = true
underlineTopConstraint = underline.topAnchor.constraintEqualToAnchor(topAnchor)
underlineHeightConstraint = underline.heightAnchor.constraintEqualToConstant(underlineHeight)
underlineHeightConstraint?.active = true
underlinePositionContraint = underline.leadingAnchor.constraintEqualToAnchor(leadingAnchor)
underlinePositionContraint?.active = true
configureButtons()
configureUnderlineVerticalPosition()
updateColors()
if selectedIndex < buttons.count {
let button = buttons[selectedIndex]
tabSelected(button)
}
}
func configureButtons() {
for button in buttons {
button.removeFromSuperview()
}
buttons.removeAll()
for title in titles {
createButton(title)
}
configureUnderlineWidth()
if !(selectedIndex < buttons.count) {
fatalError("Invalid selection for buttons: \(selectedIndex)")
}
let selectedButton = buttons[selectedIndex]
tabSelected(selectedButton)
}
func createButton(title: String?) {
let button = UIButton(type: .System)
button.setTitle(title, forState: .Normal)
button.setTitleColor(defaultTitleColor, forState: .Normal)
button.titleLabel?.font = UIFont.appFont(17.0)
button.addTarget(self, action: "tabSelected:", forControlEvents: .TouchUpInside)
stackView.addArrangedSubview(button)
buttons.append(button)
}
func configureUnderlineWidth() {
underlineWidthConstraint = underline.widthAnchor.constraintEqualToAnchor(widthAnchor, multiplier: 1 / CGFloat(titles.count))
underlineWidthConstraint?.active = true
}
func configureUnderlineVerticalPosition() {
if underlineOnBottom {
underlineBottomConstraint?.active = true
underlineTopConstraint?.active = false
} else {
underlineBottomConstraint?.active = false
underlineTopConstraint?.active = true
}
}
func updateColors() {
underline.backgroundColor = accentColor
for button in buttons {
button.setTitleColor(defaultTitleColor, forState: .Normal)
button.setTitleColor(accentColor, forState: .Highlighted)
}
}
func updateButtons() {
if !(selectedIndex < buttons.count) {
fatalError("Invalid index for buttons—selectedIndex: \(selectedIndex)")
}
for button in buttons {
button.setTitleColor(defaultTitleColor, forState: .Normal)
}
let currentSelection = buttons[selectedIndex]
currentSelection.setTitleColor(accentColor, forState: .Normal)
// TODO: Add animation
let position = frame.size.width / CGFloat(titles.count) * CGFloat(selectedIndex)
self.underlinePositionContraint?.constant = position
UIView.animateWithDuration(0.3, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1.0, options: [], animations: { () -> Void in
self.layoutIfNeeded()
}, completion: nil)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment