Tab Bar: Adds tab bar view/view controller
This commit is contained in:
232
App/Tabs/TabBarView.swift
Normal file
232
App/Tabs/TabBarView.swift
Normal file
@@ -0,0 +1,232 @@
|
||||
//
|
||||
// TabBarView.swift
|
||||
// App
|
||||
//
|
||||
// Created by James Magahern on 10/28/20.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class TabView: UIControl
|
||||
{
|
||||
var active: Bool = false { didSet { setNeedsLayout() } }
|
||||
let label = UILabel(frame: .zero)
|
||||
let closeButton = UIButton(frame: .zero)
|
||||
let imageView = UIImageView(image: nil)
|
||||
|
||||
private let leftSeparator = UIView(frame: .zero)
|
||||
private let rightSeparator = UIView(frame: .zero)
|
||||
|
||||
convenience init() {
|
||||
self.init(frame: .zero)
|
||||
|
||||
addSubview(label)
|
||||
label.text = "Tab View"
|
||||
label.font = .boldSystemFont(ofSize: 11.0)
|
||||
|
||||
addSubview(closeButton)
|
||||
closeButton.setImage(UIImage(systemName: "xmark.square.fill"), for: .normal)
|
||||
closeButton.tintColor = .label
|
||||
|
||||
addSubview(leftSeparator)
|
||||
addSubview(rightSeparator)
|
||||
leftSeparator.backgroundColor = .secondarySystemFill
|
||||
rightSeparator.backgroundColor = .secondarySystemFill
|
||||
|
||||
// Try just one for now
|
||||
leftSeparator.isHidden = true
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let insetBounds = bounds.inset(by: layoutMargins)
|
||||
|
||||
let closeButtonPadding = CGFloat(5.0)
|
||||
let closeButtonSize = CGSize(width: insetBounds.height, height: insetBounds.height)
|
||||
closeButton.frame = CGRect(
|
||||
x: insetBounds.width - closeButtonSize.width, y: insetBounds.minY,
|
||||
width: closeButtonSize.width, height: closeButtonSize.height
|
||||
)
|
||||
|
||||
label.frame = CGRect(
|
||||
x: insetBounds.minX, y: insetBounds.minY,
|
||||
width: closeButton.frame.minX - closeButtonPadding, height: insetBounds.height
|
||||
)
|
||||
|
||||
let separatorWidth = CGFloat(1.0)
|
||||
leftSeparator.frame = CGRect(
|
||||
x: 0.0, y: 0.0,
|
||||
width: separatorWidth, height: bounds.height
|
||||
)
|
||||
|
||||
rightSeparator.frame = CGRect(
|
||||
x: bounds.width - separatorWidth, y: 0.0,
|
||||
width: separatorWidth, height: bounds.height
|
||||
)
|
||||
|
||||
if isTracking {
|
||||
backgroundColor = .systemFill
|
||||
} else if active {
|
||||
backgroundColor = .init(dynamicProvider: { (traitCollection) -> UIColor in
|
||||
if traitCollection.userInterfaceStyle == .light {
|
||||
return .secondarySystemGroupedBackground
|
||||
} else {
|
||||
return .secondarySystemFill
|
||||
}
|
||||
})
|
||||
} else {
|
||||
backgroundColor = .init(dynamicProvider: { (traitCollection) -> UIColor in
|
||||
if traitCollection.userInterfaceStyle == .light {
|
||||
return .secondarySystemFill
|
||||
} else {
|
||||
return .secondarySystemGroupedBackground
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||
super.beginTracking(touch, with: event)
|
||||
setNeedsLayout()
|
||||
return true
|
||||
}
|
||||
|
||||
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||
super.endTracking(touch, with: event)
|
||||
setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
protocol TabBarViewDataSource: class {
|
||||
func numberOfTabs(forTabBarView: TabBarView) -> Int
|
||||
func tabBarView(_ tabBarView: TabBarView, titleForTabAtIndex: Int) -> String
|
||||
func tabBarView(_ tabBarView: TabBarView, imageForTabAtIndex: Int) -> UIImage?
|
||||
}
|
||||
|
||||
protocol TabBarViewDelegate: class {
|
||||
func tabBarView(_ tabBarView: TabBarView, didClickToActivateTabAtIndex: Int)
|
||||
func tabBarView(_ tabBarView: TabBarView, didClickToCloseTabAtIndex: Int)
|
||||
}
|
||||
|
||||
class TabBarView: UIView
|
||||
{
|
||||
static let preferredHeight: CGFloat = 30.0
|
||||
|
||||
public var delegate: TabBarViewDelegate?
|
||||
public var dataSource: TabBarViewDataSource?
|
||||
|
||||
private var tabViews: [TabView] = []
|
||||
private var activeTabIndex: Int = 0
|
||||
private var tabContainerView = UIScrollView(frame: .zero)
|
||||
|
||||
override func sizeThatFits(_ size: CGSize) -> CGSize {
|
||||
CGSize(width: size.width, height: Self.preferredHeight)
|
||||
}
|
||||
|
||||
convenience init() {
|
||||
self.init(frame: .zero)
|
||||
|
||||
addSubview(tabContainerView)
|
||||
backgroundColor = .secondarySystemGroupedBackground
|
||||
tabContainerView.showsHorizontalScrollIndicator = false
|
||||
tabContainerView.showsVerticalScrollIndicator = false
|
||||
}
|
||||
|
||||
public func reloadTabs() {
|
||||
guard let dataSource = self.dataSource else { return }
|
||||
|
||||
let numberOfTabs = dataSource.numberOfTabs(forTabBarView: self)
|
||||
while numberOfTabs < tabViews.count {
|
||||
let tabView = tabViews.removeLast()
|
||||
tabView.removeFromSuperview()
|
||||
}
|
||||
|
||||
while numberOfTabs > tabViews.count {
|
||||
tabViews.append(makeTabView())
|
||||
}
|
||||
|
||||
for (i, _) in tabViews.enumerated() {
|
||||
self.reloadTab(atIndex: i)
|
||||
}
|
||||
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
public func reloadTab(atIndex index: Int) {
|
||||
guard let dataSource = self.dataSource else { return }
|
||||
if index > tabViews.count - 1 { return }
|
||||
|
||||
let title = dataSource.tabBarView(self, titleForTabAtIndex: index)
|
||||
let image = dataSource.tabBarView(self, imageForTabAtIndex: index)
|
||||
|
||||
let tabView = tabViews[index]
|
||||
tabView.label.text = title
|
||||
tabView.imageView.image = image
|
||||
}
|
||||
|
||||
public func activateTab(atIndex index: Int) {
|
||||
self.activeTabIndex = index
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
private func makeTabView() -> TabView {
|
||||
let tabView = TabView()
|
||||
tabView.addAction(UIAction(handler: { [unowned self, tabView] _ in
|
||||
guard let delegate = self.delegate else { return }
|
||||
guard let tabIndex = self.tabViews.firstIndex(of: tabView) else { return }
|
||||
|
||||
delegate.tabBarView(self, didClickToActivateTabAtIndex: tabIndex)
|
||||
}), for: .touchUpInside)
|
||||
|
||||
tabView.closeButton.addAction(UIAction(handler: { [unowned self, tabView] _ in
|
||||
guard let delegate = self.delegate else { return }
|
||||
guard let tabIndex = self.tabViews.firstIndex(of: tabView) else { return }
|
||||
|
||||
delegate.tabBarView(self, didClickToCloseTabAtIndex: tabIndex)
|
||||
}), for: .touchUpInside)
|
||||
|
||||
return tabView
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let tabContainerBounds = bounds
|
||||
tabContainerView.frame = tabContainerBounds
|
||||
|
||||
let minimumTabWidth = CGFloat(140.0)
|
||||
let maximumTabWidth = tabContainerBounds.width
|
||||
|
||||
var xOffset = CGFloat(0.0)
|
||||
var tabWidth: CGFloat = (tabContainerBounds.width / CGFloat(tabViews.count))
|
||||
if tabWidth < minimumTabWidth {
|
||||
tabWidth = minimumTabWidth
|
||||
} else if tabWidth > maximumTabWidth {
|
||||
tabWidth = maximumTabWidth
|
||||
}
|
||||
|
||||
for (i, tabView) in tabViews.enumerated() {
|
||||
tabContainerView.addSubview(tabView)
|
||||
|
||||
tabView.frame = CGRect(
|
||||
x: xOffset,
|
||||
y: tabContainerBounds.minY,
|
||||
width: tabWidth,
|
||||
height: tabContainerBounds.height
|
||||
)
|
||||
|
||||
xOffset += tabView.frame.width
|
||||
|
||||
if i == activeTabIndex {
|
||||
tabView.active = true
|
||||
} else {
|
||||
tabView.active = false
|
||||
}
|
||||
}
|
||||
|
||||
tabContainerView.contentSize = CGSize(
|
||||
width: xOffset, height: tabContainerBounds.height
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user