session-ios/SessionUIKit/Components/TabBar.swift

112 lines
4.6 KiB
Swift

import UIKit
public final class TabBar : UIView {
private let tabs: [Tab]
private var accentLineViewHorizontalCenteringConstraint: NSLayoutConstraint!
private var accentLineViewWidthConstraint: NSLayoutConstraint!
// MARK: Components
private lazy var tabLabels: [UILabel] = tabs.map { tab in
let result = UILabel()
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity)
result.textAlignment = .center
result.text = tab.title
result.set(.height, to: TabBar.snHeight - Values.separatorThickness - Values.accentLineThickness)
return result
}
private lazy var accentLineView: UIView = {
let result = UIView()
result.backgroundColor = Colors.accent
return result
}()
// MARK: Types
public struct Tab {
let title: String
let onTap: () -> Void
public init(title: String, onTap: @escaping () -> Void) {
self.title = title
self.onTap = onTap
}
}
// MARK: Settings
public static let snHeight = isIPhone5OrSmaller ? CGFloat(32) : CGFloat(48)
// MARK: Lifecycle
public init(tabs: [Tab]) {
self.tabs = tabs
super.init(frame: CGRect.zero)
setUpViewHierarchy()
}
public override init(frame: CGRect) {
preconditionFailure("Use init(tabs:) instead.")
}
public required init?(coder: NSCoder) {
preconditionFailure("Use init(tabs:) instead.")
}
private func setUpViewHierarchy() {
set(.height, to: TabBar.snHeight)
tabLabels.forEach { tabLabel in
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTabLabelTapped(_:)))
tabLabel.addGestureRecognizer(tapGestureRecognizer)
}
let tabLabelStackView = UIStackView(arrangedSubviews: tabLabels)
tabLabelStackView.axis = .horizontal
tabLabelStackView.distribution = .fillEqually
tabLabelStackView.spacing = Values.mediumSpacing
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTabLabelTapped(_:)))
tabLabelStackView.addGestureRecognizer(tapGestureRecognizer)
tabLabelStackView.set(.height, to: TabBar.snHeight - Values.separatorThickness - Values.accentLineThickness)
addSubview(tabLabelStackView)
let separator = UIView()
separator.backgroundColor = Colors.separator
separator.set(.height, to: Values.separatorThickness)
addSubview(separator)
accentLineView.set(.height, to: Values.accentLineThickness)
addSubview(accentLineView)
tabLabelStackView.pin(.leading, to: .leading, of: self)
tabLabelStackView.pin(.top, to: .top, of: self)
pin(.trailing, to: .trailing, of: tabLabelStackView)
separator.pin(.leading, to: .leading, of: self)
separator.pin(.top, to: .bottom, of: tabLabelStackView)
pin(.trailing, to: .trailing, of: separator)
accentLineView.translatesAutoresizingMaskIntoConstraints = false
selectTab(at: 0, withAnimatedTransition: false)
accentLineView.pin(.top, to: .bottom, of: separator)
pin(.bottom, to: .bottom, of: accentLineView)
}
// MARK: Updating
public func selectTab(at index: Int, withAnimatedTransition isAnimated: Bool = true) {
let tabLabel = tabLabels[index]
accentLineViewHorizontalCenteringConstraint?.isActive = false
accentLineViewHorizontalCenteringConstraint = accentLineView.centerXAnchor.constraint(equalTo: tabLabel.centerXAnchor)
accentLineViewHorizontalCenteringConstraint.isActive = true
accentLineViewWidthConstraint?.isActive = false
accentLineViewWidthConstraint = accentLineView.widthAnchor.constraint(equalTo: tabLabel.widthAnchor)
accentLineViewWidthConstraint.isActive = true
var tabLabelsCopy = tabLabels
tabLabelsCopy.remove(at: index)
UIView.animate(withDuration: isAnimated ? 0.25 : 0) {
tabLabel.textColor = Colors.text
tabLabelsCopy.forEach { $0.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity) }
self.layoutIfNeeded()
}
}
// MARK: Interaction
@objc private func handleTabLabelTapped(_ sender: UITapGestureRecognizer) {
guard let tabLabel = tabLabels.first(where: { $0.bounds.contains(sender.location(in: $0)) }), let index = tabLabels.firstIndex(of: tabLabel) else { return }
selectTab(at: index)
let tab = tabs[index]
tab.onTap()
}
}