Skip to content

Instantly share code, notes, and snippets.

@AmirDaliri
Created September 9, 2018 04:47
Show Gist options
  • Save AmirDaliri/d95c2259f1c854701a48f0fd29bbff58 to your computer and use it in GitHub Desktop.
Save AmirDaliri/d95c2259f1c854701a48f0fd29bbff58 to your computer and use it in GitHub Desktop.
MaterialTabBar.swift
//
// MaterialTabBar.swift
//
// Created by Amir Daliri on 9/6/17.
// Copyright © 2017 AmirDaliri. All rights reserved.
//
import UIKit
protocol MaterialTabBarDelegate {
func materialTabBar(_ materialTabBar: MaterialTabBar, willChangeToIndex index: Int)
func materialTabBar(_ materialTabBar: MaterialTabBar, didChangeToIndex index: Int)
}
class MaterialTabBar: UIView {
var items = [String]() {
didSet {
for button in buttons {
button.removeFromSuperview()
}
buttons.removeAll()
for (index, item) in items.enumerated() {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitleColor(.black, for: .normal)
button.setTitleColor(tintColor, for: .selected)
button.contentEdgeInsets = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8)
button.setTitle(item, for: .normal)
button.setTitle(item, for: .selected)
button.tag = index
button.addTarget(self, action: #selector(itemSelected(_:)), for: .touchUpInside)
button.titleLabel?.font = IranSans.regular.of(14)
buttons.append(button)
}
addButtons()
if items.count > 0, indicatorLine != nil {
indicatorLine.isHidden = false
}
}
}
var selectedIndex = 0 {
didSet {
changeSelectedIndex(toIndex: selectedIndex)
}
}
var isAnimationEnabled = true
var useEqualWidths = false
var delegate: MaterialTabBarDelegate?
fileprivate var buttons = [UIButton]()
fileprivate var scrollView: UIScrollView!
fileprivate var contentView = UIView()
fileprivate var indicatorLine: UIView!
fileprivate var bottomBorder: UIView!
fileprivate let indicatorLineHeight: CGFloat = 3
var indicatorLineLeadingConstraint: NSLayoutConstraint!
fileprivate var indicatorLineWidthConstraint: NSLayoutConstraint!
init(tintColor: UIColor) {
super.init(frame: CGRect.zero)
self.tintColor = tintColor
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func layoutSubviews() {
super.layoutSubviews()
contentView.frame.size.height = frame.height
indicatorLine.frame.origin.y = frame.height - indicatorLineHeight
if contentView.frame.width < frame.width {
scrollView.isScrollEnabled = true
scrollView.contentOffset = CGPoint(x: -(frame.width - contentView.frame.width) / 2, y: 0)
scrollView.isScrollEnabled = false
}
else {
scrollView.isScrollEnabled = true
}
if useEqualWidths {
for button in buttons {
button.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width / CGFloat(buttons.count)).isActive = true
}
}
}
private func setup() {
backgroundColor = .white
scrollView = UIScrollView()
scrollView.delegate = self
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
addSubview(scrollView)
scrollView.topAnchor.constraint(equalTo: topAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
contentView = UIView()
scrollView.addSubview(contentView)
indicatorLine = UIView()
indicatorLine.translatesAutoresizingMaskIntoConstraints = false
indicatorLine.backgroundColor = tintColor
indicatorLine.isHidden = true
contentView.addSubview(indicatorLine)
indicatorLineLeadingConstraint = indicatorLine.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0)
indicatorLineLeadingConstraint.isActive = true
indicatorLineWidthConstraint = indicatorLine.widthAnchor.constraint(equalToConstant: 64)
indicatorLineWidthConstraint.isActive = true
indicatorLine.heightAnchor.constraint(equalToConstant: indicatorLineHeight).isActive = true
indicatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
addButtons()
bottomBorder = UIView()
bottomBorder.translatesAutoresizingMaskIntoConstraints = false
bottomBorder.backgroundColor = #colorLiteral(red: 0.8666666667, green: 0.8666666667, blue: 0.8666666667, alpha: 1)
addSubview(bottomBorder)
bottomBorder.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
bottomBorder.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
bottomBorder.heightAnchor.constraint(equalToConstant: 1).isActive = true
bottomBorder.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
private func addButtons() {
var totalWidth: CGFloat = 0
for i in 0 ..< buttons.count {
let button = buttons[i]
contentView.addSubview(button)
button.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
button.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
if i > 0 {
button.leadingAnchor.constraint(equalTo: buttons[i - 1].trailingAnchor).isActive = true
}
else {
button.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
}
button.sizeToFit()
if i == 0 {
indicatorLineWidthConstraint.constant = button.frame.width
}
totalWidth += button.frame.width
}
if useEqualWidths {
totalWidth = UIScreen.main.bounds.width - 8
}
if totalWidth < frame.width {
addSubview(contentView)
contentView.frame = CGRect(x: (frame.width - totalWidth) / 2, y: 0, width: totalWidth, height: 44)
scrollView.isHidden = true
}
else {
contentView.frame = CGRect(x: 0, y: 0, width: totalWidth, height: 44)
scrollView.contentSize = contentView.frame.size
scrollView.isHidden = false
}
if useEqualWidths {
indicatorLineLeadingConstraint.constant = 0
indicatorLineWidthConstraint.constant = totalWidth / 2
}
else if buttons.count > 0 {
indicatorLineLeadingConstraint.constant = totalWidth - buttons[0].frame.origin.x - buttons[0].frame.width
}
setNeedsLayout()
}
@objc fileprivate func itemSelected(_ sender: UIButton) {
delegate?.materialTabBar(self, willChangeToIndex: sender.tag)
selectedIndex = sender.tag
delegate?.materialTabBar(self, didChangeToIndex: selectedIndex)
}
public func changeSelectedIndex(toIndex index: Int) {
guard buttons.count > index, index >= 0 else {
return
}
let targetButton = buttons[index]
self.indicatorLine.layer.removeAllAnimations()
if !isRTL {
indicatorLineLeadingConstraint.constant = targetButton.frame.origin.x
}
else {
indicatorLineLeadingConstraint.constant = contentView.frame.width - targetButton.frame.origin.x - targetButton.frame.width
}
indicatorLineWidthConstraint.constant = targetButton.frame.width
UIView.animate(withDuration: isAnimationEnabled ? 0.25 : 0) {
self.contentView.layoutIfNeeded()
}
let rectToMakeVisible = CGRect(x: max(targetButton.frame.origin.x - 50, 0), y: targetButton.frame.origin.y, width: targetButton.frame.width + 100, height: targetButton.frame.height)
scrollView.scrollRectToVisible(rectToMakeVisible, animated: isAnimationEnabled)
for (i, button) in buttons.enumerated() {
button.isSelected = i == index
}
}
}
extension MaterialTabBar: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y != 0 {
scrollView.contentOffset.y = 0
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment