Commit 9b69558f by Daniel Dahan

Added image states for TabItems used in TabBar and

parent 6cb79199
## 2.15.0
* [issue-1057](https://github.com/CosmicMind/Material/issues/1057): Added image states for TabItems used in TabBar and TabsController
## 2.14.0
* [issue-995](https://github.com/CosmicMind/Material/issues/995): Updated iOS 11 layout margins for NavigationBar.
......
Pod::Spec.new do |s|
s.name = 'Material'
s.version = '2.14.0'
s.version = '2.15.0'
s.swift_version = '4.0'
s.license = 'BSD-3-Clause'
s.summary = 'A UI/UX framework for creating beautiful applications.'
s.homepage = 'http://cosmicmind.com'
......
......@@ -267,7 +267,7 @@
96BCB7971CB40DC500C806FE /* NavigationDrawerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationDrawerController.swift; sourceTree = "<group>"; };
96BCB7981CB40DC500C806FE /* Bar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bar.swift; sourceTree = "<group>"; };
96BCB7991CB40DC500C806FE /* TransitionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionController.swift; sourceTree = "<group>"; };
96BCB79A1CB40DC500C806FE /* TabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = "<group>"; tabWidth = 4; };
96BCB79A1CB40DC500C806FE /* TabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = "<group>"; tabWidth = 2; };
96BCB79C1CB40DC500C806FE /* TextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
96BCB79D1CB40DC500C806FE /* TextStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextStorage.swift; sourceTree = "<group>"; };
96BCB79E1CB40DC500C806FE /* TextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = "<group>"; };
......@@ -852,7 +852,7 @@
attributes = {
LastSwiftMigration = 0710;
LastSwiftUpdateCheck = 0730;
LastUpgradeCheck = 0900;
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = "CosmicMind, Inc.";
TargetAttributes = {
963832351B88DFD80015F710 = {
......@@ -1024,12 +1024,14 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
......@@ -1085,12 +1087,14 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
......
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0920"
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
......@@ -26,7 +26,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
......@@ -56,7 +55,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
......
Subproject commit 654928d706213db2cd9f71281361960177ad0c1d
Subproject commit b3595d023fdd3b64dede8c30bcaa08ee366cf249
......@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.13.7</string>
<string>2.15.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
......
......@@ -31,536 +31,603 @@
import UIKit
open class TabItem: FlatButton {
open override func prepare() {
super.prepare()
pulseAnimation = .none
}
/// A dictionary of TabItemStates to UIColors for states.
fileprivate var colorForState = [TabItemState: UIColor]()
/// A dictionary of TabItemStates to UIImages for states.
fileprivate var imageForState = [TabItemState: UIImage]()
/// Sets the normal and highlighted image for the button.
open override var image: UIImage? {
didSet {
setTabItemImage(image, for: .normal)
setTabItemImage(image, for: .selected)
setTabItemImage(image, for: .highlighted)
super.image = image
}
}
open override func prepare() {
super.prepare()
pulseAnimation = .none
prepareImages()
prepareColors()
updateColors()
}
}
fileprivate extension TabItem {
/// Prepares the tabsItems images.
func prepareImages() {
imageForState[.normal] = image
imageForState[.selected] = image
imageForState[.highlighted] = image
}
/// Prepares the tabsItems colors.
func prepareColors() {
colorForState[.normal] = Color.grey.base
colorForState[.selected] = Color.blue.base
colorForState[.highlighted] = Color.blue.base
}
}
fileprivate extension TabItem {
/// Updates the tabItems colors.
func updateColors() {
let normalColor = colorForState[.normal]!
let selectedColor = colorForState[.selected]!
let highlightedColor = colorForState[.highlighted]!
setTitleColor(normalColor, for: .normal)
setImage(imageForState[.normal]?.tint(with: normalColor), for: .normal)
setTitleColor(selectedColor, for: .selected)
setImage(imageForState[.selected]?.tint(with: selectedColor), for: .selected)
setTitleColor(highlightedColor, for: .highlighted)
setImage(imageForState[.highlighted]?.tint(with: highlightedColor), for: .highlighted)
}
}
extension TabItem {
/**
Retrieves the tabItem color for a given state.
- Parameter for state: A TabItemState.
- Returns: A UIColor.
*/
open func getTabItemColor(for state: TabItemState) -> UIColor {
return colorForState[state]!
}
/**
Sets the color for the tabItem given a TabItemState.
- Parameter _ color: A UIColor.
- Parameter for state: A TabItemState.
*/
open func setTabItemColor(_ color: UIColor, for state: TabItemState) {
colorForState[state] = color
updateColors()
}
/**
Retrieves the tabItem image for a given state.
- Parameter for state: A TabItemState.
- Returns: An optional UIImage.
*/
open func getTabItemImage(for state: TabItemState) -> UIImage? {
return imageForState[state]
}
/**
Sets the image for the tabItem given a TabItemState.
- Parameter _ image: An optional UIImage.
- Parameter for state: A TabItemState.
*/
open func setTabItemImage(_ image: UIImage?, for state: TabItemState) {
imageForState[state] = image
updateColors()
}
}
@objc(TabItemState)
public enum TabItemState: Int {
case normal
case highlighted
case selected
case normal
case highlighted
case selected
}
@objc(TabItemLineState)
public enum TabItemLineState: Int {
case selected
case selected
}
@objc(TabBarLineAlignment)
public enum TabBarLineAlignment: Int {
case top
case bottom
case top
case bottom
}
@objc(TabBarDelegate)
public protocol TabBarDelegate {
/**
A delegation method that is executed to determine if the TabBar should
transition to the next tab.
- Parameter tabBar: A TabBar.
- Parameter tabItem: A TabItem.
- Returns: A Boolean.
*/
@objc
optional func tabBar(tabBar: TabBar, shouldSelect tabItem: TabItem) -> Bool
/**
A delegation method that is executed when the tabItem will trigger the
animation to the next tab.
- Parameter tabBar: A TabBar.
- Parameter tabItem: A TabItem.
*/
@objc
optional func tabBar(tabBar: TabBar, willSelect tabItem: TabItem)
/**
A delegation method that is executed when the tabItem did complete the
animation to the next tab.
- Parameter tabBar: A TabBar.
- Parameter tabItem: A TabItem.
*/
@objc
optional func tabBar(tabBar: TabBar, didSelect tabItem: TabItem)
/**
A delegation method that is executed to determine if the TabBar should
transition to the next tab.
- Parameter tabBar: A TabBar.
- Parameter tabItem: A TabItem.
- Returns: A Boolean.
*/
@objc
optional func tabBar(tabBar: TabBar, shouldSelect tabItem: TabItem) -> Bool
/**
A delegation method that is executed when the tabItem will trigger the
animation to the next tab.
- Parameter tabBar: A TabBar.
- Parameter tabItem: A TabItem.
*/
@objc
optional func tabBar(tabBar: TabBar, willSelect tabItem: TabItem)
/**
A delegation method that is executed when the tabItem did complete the
animation to the next tab.
- Parameter tabBar: A TabBar.
- Parameter tabItem: A TabItem.
*/
@objc
optional func tabBar(tabBar: TabBar, didSelect tabItem: TabItem)
}
@objc(_TabBarDelegate)
internal protocol _TabBarDelegate {
/**
A delegation method that is executed when the tabItem will trigger the
animation to the next tab.
- Parameter tabBar: A TabBar.
- Parameter tabItem: A TabItem.
*/
@objc
optional func _tabBar(tabBar: TabBar, willSelect tabItem: TabItem)
/**
A delegation method that is executed when the tabItem will trigger the
animation to the next tab.
- Parameter tabBar: A TabBar.
- Parameter tabItem: A TabItem.
*/
@objc
optional func _tabBar(tabBar: TabBar, willSelect tabItem: TabItem)
}
@objc(TabBarStyle)
public enum TabBarStyle: Int {
case auto
case nonScrollable
case scrollable
case auto
case nonScrollable
case scrollable
}
open class TabBar: Bar {
/// Only for inital load to get the line animation correct.
fileprivate var shouldNotAnimateLineView = false
/// The total width of the tabItems.
fileprivate var tabItemsTotalWidth: CGFloat {
var w: CGFloat = 0
let q = 2 * tabItemsInterimSpace
let p = q + tabItemsInterimSpace
for v in tabItems {
let x = v.sizeThatFits(CGSize(width: .greatestFiniteMagnitude, height: scrollView.bounds.height)).width
w += x
w += p
}
w -= tabItemsInterimSpace
return w
}
/// A dictionary of TabItemStates to UIColors for tabItems.
fileprivate var tabItemsColorForState = [TabItemState: UIColor]()
/// A dictionary of TabItemLineStates to UIColors for the line.
fileprivate var lineColorForState = [TabItemLineState: UIColor]()
/// An enum that determines the tab bar style.
open var tabBarStyle = TabBarStyle.auto {
didSet {
layoutSubviews()
}
}
/// A reference to the scroll view when the tab bar style is scrollable.
open let scrollView = UIScrollView()
/// Enables and disables bouncing when swiping.
open var isScrollBounceEnabled: Bool {
get {
return scrollView.bounces
}
set(value) {
scrollView.bounces = value
}
}
/// A delegation reference.
open weak var delegate: TabBarDelegate?
internal weak var _delegate: _TabBarDelegate?
/// The currently selected tabItem.
open internal(set) var selectedTabItem: TabItem? {
willSet {
selectedTabItem?.isSelected = false
}
didSet {
selectedTabItem?.isSelected = true
}
}
/// A preset wrapper around tabItems contentEdgeInsets.
open var tabItemsContentEdgeInsetsPreset: EdgeInsetsPreset {
get {
return contentView.grid.contentEdgeInsetsPreset
}
set(value) {
contentView.grid.contentEdgeInsetsPreset = value
}
}
/// A reference to EdgeInsets.
@IBInspectable
open var tabItemsContentEdgeInsets: EdgeInsets {
get {
return contentView.grid.contentEdgeInsets
}
set(value) {
contentView.grid.contentEdgeInsets = value
}
}
/// A preset wrapper around tabItems interimSpace.
open var tabItemsInterimSpacePreset: InterimSpacePreset {
get {
return contentView.grid.interimSpacePreset
}
set(value) {
contentView.grid.interimSpacePreset = value
}
}
/// A wrapper around tabItems interimSpace.
@IBInspectable
open var tabItemsInterimSpace: InterimSpace {
get {
return contentView.grid.interimSpace
}
set(value) {
contentView.grid.interimSpace = value
}
}
/// TabItems.
@objc
open var tabItems = [TabItem]() {
didSet {
oldValue.forEach {
$0.removeFromSuperview()
}
prepareTabItems()
layoutSubviews()
}
}
/// A reference to the line UIView.
open let line = UIView()
/// A value for the line alignment.
@objc
open var lineAlignment = TabBarLineAlignment.bottom {
didSet {
layoutSubviews()
}
}
/// The line height.
@objc
open var lineHeight: CGFloat {
get {
return line.bounds.height
}
set(value) {
line.frame.size.height = value
}
}
/// The line color.
@objc
open var lineColor: UIColor {
get {
return lineColorForState[.selected]!
}
set(value) {
setLineColor(value, for: .selected)
}
}
open override func layoutSubviews() {
super.layoutSubviews()
guard willLayout else {
return
}
layoutScrollView()
layoutLine()
updateScrollView()
}
open override func prepare() {
super.prepare()
contentEdgeInsetsPreset = .none
interimSpacePreset = .interimSpace6
tabItemsInterimSpacePreset = .interimSpace4
prepareContentView()
prepareScrollView()
prepareDivider()
prepareLine()
prepareTabItemsColor()
prepareLineColor()
updateTabItemColors()
updateLineColors()
}
/// A dictionary of TabItemLineStates to UIColors for the line.
fileprivate var lineColorForState = [TabItemLineState: UIColor]()
/// Only for inital load to get the line animation correct.
fileprivate var shouldNotAnimateLineView = false
/// The total width of the tabItems.
fileprivate var tabItemsTotalWidth: CGFloat {
var w: CGFloat = 0
let q = 2 * tabItemsInterimSpace
let p = q + tabItemsInterimSpace
for v in tabItems {
let x = v.sizeThatFits(CGSize(width: .greatestFiniteMagnitude, height: scrollView.bounds.height)).width
w += x
w += p
}
w -= tabItemsInterimSpace
return w
}
/// An enum that determines the tab bar style.
open var tabBarStyle = TabBarStyle.auto {
didSet {
layoutSubviews()
}
}
/// A reference to the scroll view when the tab bar style is scrollable.
open let scrollView = UIScrollView()
/// Enables and disables bouncing when swiping.
open var isScrollBounceEnabled: Bool {
get {
return scrollView.bounces
}
set(value) {
scrollView.bounces = value
}
}
/// A delegation reference.
open weak var delegate: TabBarDelegate?
internal weak var _delegate: _TabBarDelegate?
/// The currently selected tabItem.
open internal(set) var selectedTabItem: TabItem? {
willSet {
selectedTabItem?.isSelected = false
}
didSet {
selectedTabItem?.isSelected = true
}
}
/// A preset wrapper around tabItems contentEdgeInsets.
open var tabItemsContentEdgeInsetsPreset: EdgeInsetsPreset {
get {
return contentView.grid.contentEdgeInsetsPreset
}
set(value) {
contentView.grid.contentEdgeInsetsPreset = value
}
}
/// A reference to EdgeInsets.
@IBInspectable
open var tabItemsContentEdgeInsets: EdgeInsets {
get {
return contentView.grid.contentEdgeInsets
}
set(value) {
contentView.grid.contentEdgeInsets = value
}
}
/// A preset wrapper around tabItems interimSpace.
open var tabItemsInterimSpacePreset: InterimSpacePreset {
get {
return contentView.grid.interimSpacePreset
}
set(value) {
contentView.grid.interimSpacePreset = value
}
}
/// A wrapper around tabItems interimSpace.
@IBInspectable
open var tabItemsInterimSpace: InterimSpace {
get {
return contentView.grid.interimSpace
}
set(value) {
contentView.grid.interimSpace = value
}
}
/// TabItems.
@objc
open var tabItems = [TabItem]() {
didSet {
oldValue.forEach {
$0.removeFromSuperview()
}
prepareTabItems()
layoutSubviews()
}
}
/// A reference to the line UIView.
open let line = UIView()
/// A value for the line alignment.
@objc
open var lineAlignment = TabBarLineAlignment.bottom {
didSet {
layoutSubviews()
}
}
/// The line height.
@objc
open var lineHeight: CGFloat {
get {
return line.bounds.height
}
set(value) {
line.frame.size.height = value
}
}
/// The line color.
@objc
open var lineColor: UIColor {
get {
return lineColorForState[.selected]!
}
set(value) {
setLineColor(value, for: .selected)
}
}
open override func layoutSubviews() {
super.layoutSubviews()
guard willLayout else {
return
}
layoutScrollView()
layoutLine()
updateScrollView()
}
open override func prepare() {
super.prepare()
contentEdgeInsetsPreset = .none
interimSpacePreset = .interimSpace6
tabItemsInterimSpacePreset = .interimSpace4
prepareContentView()
prepareScrollView()
prepareDivider()
prepareLine()
prepareLineColor()
updateLineColors()
}
}
fileprivate extension TabBar {
// Prepares the line.
func prepareLine() {
line.layer.zPosition = 10000
lineHeight = 3
scrollView.addSubview(line)
}
/// Prepares the divider.
func prepareDivider() {
dividerColor = Color.grey.lighten2
dividerAlignment = .top
}
/// Prepares the tabItems.
func prepareTabItems() {
shouldNotAnimateLineView = true
for v in tabItems {
v.grid.columns = 0
v.contentEdgeInsets = .zero
prepareTabItemHandler(tabItem: v)
}
selectedTabItem = tabItems.first
}
/// Prepares the tabsItems colors.
func prepareTabItemsColor() {
tabItemsColorForState[.normal] = Color.grey.base
tabItemsColorForState[.selected] = Color.blue.base
tabItemsColorForState[.highlighted] = Color.blue.base
}
/// Prepares the line colors.
func prepareLineColor() {
lineColorForState[.selected] = Color.blue.base
}
/**
Prepares the tabItem animation handler.
- Parameter tabItem: A TabItem.
*/
func prepareTabItemHandler(tabItem: TabItem) {
removeTabItemHandler(tabItem: tabItem)
tabItem.addTarget(self, action: #selector(handleTabItemsChange(tabItem:)), for: .touchUpInside)
}
/// Prepares the contentView.
func prepareContentView() {
contentView.layer.zPosition = 6000
}
/// Prepares the scroll view.
func prepareScrollView() {
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
centerViews = [scrollView]
}
// Prepares the line.
func prepareLine() {
line.layer.zPosition = 10000
lineHeight = 3
scrollView.addSubview(line)
}
/// Prepares the divider.
func prepareDivider() {
dividerColor = Color.grey.lighten2
dividerAlignment = .top
}
/// Prepares the tabItems.
func prepareTabItems() {
shouldNotAnimateLineView = true
for v in tabItems {
v.grid.columns = 0
v.contentEdgeInsets = .zero
prepareTabItemHandler(tabItem: v)
}
selectedTabItem = tabItems.first
}
/// Prepares the line colors.
func prepareLineColor() {
lineColorForState[.selected] = Color.blue.base
}
/**
Prepares the tabItem animation handler.
- Parameter tabItem: A TabItem.
*/
func prepareTabItemHandler(tabItem: TabItem) {
removeTabItemHandler(tabItem: tabItem)
tabItem.addTarget(self, action: #selector(handleTabItemsChange(tabItem:)), for: .touchUpInside)
}
/// Prepares the contentView.
func prepareContentView() {
contentView.layer.zPosition = 6000
}
/// Prepares the scroll view.
func prepareScrollView() {
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
centerViews = [scrollView]
}
}
fileprivate extension TabBar {
/// Layout the scrollView.
func layoutScrollView() {
contentView.grid.reload()
if .scrollable == tabBarStyle || (.auto == tabBarStyle && tabItemsTotalWidth > scrollView.bounds.width) {
var w: CGFloat = 0
let q = 2 * tabItemsInterimSpace
let p = q + tabItemsInterimSpace
for v in tabItems {
v.sizeToFit()
let x = v.sizeThatFits(CGSize(width: .greatestFiniteMagnitude, height: scrollView.bounds.height)).width
v.frame.size.height = scrollView.bounds.height
v.frame.size.width = x + q
v.frame.origin.x = w
w += x
w += p
if scrollView != v.superview {
scrollView.addSubview(v)
}
}
w -= tabItemsInterimSpace
scrollView.contentSize = CGSize(width: w, height: scrollView.bounds.height)
} else {
scrollView.grid.begin()
scrollView.grid.views = tabItems
scrollView.grid.axis.columns = tabItems.count
scrollView.grid.contentEdgeInsets = tabItemsContentEdgeInsets
scrollView.grid.interimSpace = tabItemsInterimSpace
scrollView.grid.commit()
scrollView.contentSize = scrollView.frame.size
}
}
/// Layout the line view.
func layoutLine() {
guard let v = selectedTabItem else {
return
}
/// Layout the scrollView.
func layoutScrollView() {
contentView.grid.reload()
if .scrollable == tabBarStyle || (.auto == tabBarStyle && tabItemsTotalWidth > scrollView.bounds.width) {
var w: CGFloat = 0
let q = 2 * tabItemsInterimSpace
let p = q + tabItemsInterimSpace
for v in tabItems {
v.sizeToFit()
let x = v.sizeThatFits(CGSize(width: .greatestFiniteMagnitude, height: scrollView.bounds.height)).width
v.frame.size.height = scrollView.bounds.height
v.frame.size.width = x + q
v.frame.origin.x = w
w += x
w += p
guard shouldNotAnimateLineView else {
line.animate(.duration(0),
.size(width: v.bounds.width, height: lineHeight),
.position(x: v.center.x, y: .bottom == lineAlignment ? scrollView.bounds.height - lineHeight / 2 : lineHeight / 2))
return
if scrollView != v.superview {
scrollView.addSubview(v)
}
line.frame = CGRect(x: v.frame.origin.x, y: .bottom == lineAlignment ? scrollView.bounds.height - lineHeight : 0, width: v.bounds.width, height: lineHeight)
shouldNotAnimateLineView = false
}
}
w -= tabItemsInterimSpace
scrollView.contentSize = CGSize(width: w, height: scrollView.bounds.height)
} else {
scrollView.grid.begin()
scrollView.grid.views = tabItems
scrollView.grid.axis.columns = tabItems.count
scrollView.grid.contentEdgeInsets = tabItemsContentEdgeInsets
scrollView.grid.interimSpace = tabItemsInterimSpace
scrollView.grid.commit()
scrollView.contentSize = scrollView.frame.size
}
}
/// Layout the line view.
func layoutLine() {
guard let v = selectedTabItem else {
return
}
guard shouldNotAnimateLineView else {
line.animate(.duration(0),
.size(width: v.bounds.width, height: lineHeight),
.position(x: v.center.x, y: .bottom == lineAlignment ? scrollView.bounds.height - lineHeight / 2 : lineHeight / 2))
return
}
line.frame = CGRect(x: v.frame.origin.x, y: .bottom == lineAlignment ? scrollView.bounds.height - lineHeight : 0, width: v.bounds.width, height: lineHeight)
shouldNotAnimateLineView = false
}
}
fileprivate extension TabBar {
/**
Removes the tabItem animation handler.
- Parameter tabItem: A TabItem.
*/
func removeTabItemHandler(tabItem: TabItem) {
tabItem.removeTarget(self, action: #selector(handleTabItemsChange(tabItem:)), for: .touchUpInside)
}
extension TabBar {
/**
Retrieves the tabItem color for a given state.
- Parameter for state: A TabItemState.
- Returns: A optional UIColor.
*/
open func getTabItemColor(for state: TabItemState) -> UIColor? {
return tabItems.first?.getTabItemColor(for: state)
}
/**
Sets the color for the tabItem given a TabItemState.
- Parameter _ color: A UIColor.
- Parameter for state: A TabItemState.
*/
open func setTabItemsColor(_ color: UIColor, for state: TabItemState) {
for v in tabItems {
v.setTabItemColor(color, for: state)
}
}
/**
Retrieves the line color for a given state.
- Parameter for state: A TabItemLineState.
- Returns: A UIColor.
*/
open func getLineColor(for state: TabItemLineState) -> UIColor {
return lineColorForState[state]!
}
/**
Sets the color for the line given a TabItemLineState.
- Parameter _ color: A UIColor.
- Parameter for state: A TabItemLineState.
*/
open func setLineColor(_ color: UIColor, for state: TabItemLineState) {
lineColorForState[state] = color
updateLineColors()
}
}
fileprivate extension TabBar {
/// Handles the tabItem touch event.
@objc
func handleTabItemsChange(tabItem: TabItem) {
guard !(false == delegate?.tabBar?(tabBar: self, shouldSelect: tabItem)) else {
return
}
animate(to: tabItem, isTriggeredByUserInteraction: true)
}
/**
Removes the tabItem animation handler.
- Parameter tabItem: A TabItem.
*/
func removeTabItemHandler(tabItem: TabItem) {
tabItem.removeTarget(self, action: #selector(handleTabItemsChange(tabItem:)), for: .touchUpInside)
}
}
extension TabBar {
/**
Selects a given index from the tabItems array.
- Parameter at index: An Int.
- Paramater completion: An optional completion block.
*/
@objc
open func select(at index: Int, completion: ((TabItem) -> Void)? = nil) {
guard -1 < index, index < tabItems.count else {
return
}
animate(to: tabItems[index], isTriggeredByUserInteraction: false, completion: completion)
fileprivate extension TabBar {
/// Handles the tabItem touch event.
@objc
func handleTabItemsChange(tabItem: TabItem) {
guard !(false == delegate?.tabBar?(tabBar: self, shouldSelect: tabItem)) else {
return
}
/**
Animates to a given tabItem.
- Parameter to tabItem: A TabItem.
- Parameter completion: An optional completion block.
*/
open func animate(to tabItem: TabItem, completion: ((TabItem) -> Void)? = nil) {
animate(to: tabItem, isTriggeredByUserInteraction: false, completion: completion)
}
animate(to: tabItem, isTriggeredByUserInteraction: true)
}
}
extension TabBar {
/**
Retrieves the tabItem color for a given state.
- Parameter for state: A TabItemState.
- Returns: A UIColor.
*/
open func getTabItemColor(for state: TabItemState) -> UIColor {
return tabItemsColorForState[state]!
}
/**
Sets the color for the tabItems given a TabItemState.
- Parameter _ color: A UIColor.
- Parameter for state: A TabItemState.
*/
open func setTabItemsColor(_ color: UIColor, for state: TabItemState) {
tabItemsColorForState[state] = color
updateTabItemColors()
}
/**
Retrieves the line color for a given state.
- Parameter for state: A TabItemLineState.
- Returns: A UIColor.
*/
open func getLineColor(for state: TabItemLineState) -> UIColor {
return lineColorForState[state]!
}
/**
Sets the color for the line given a TabItemLineState.
- Parameter _ color: A UIColor.
- Parameter for state: A TabItemLineState.
*/
open func setLineColor(_ color: UIColor, for state: TabItemLineState) {
lineColorForState[state] = color
updateLineColors()
}
/**
Selects a given index from the tabItems array.
- Parameter at index: An Int.
- Paramater completion: An optional completion block.
*/
@objc
open func select(at index: Int, completion: ((TabItem) -> Void)? = nil) {
guard -1 < index, index < tabItems.count else {
return
}
animate(to: tabItems[index], isTriggeredByUserInteraction: false, completion: completion)
}
/**
Animates to a given tabItem.
- Parameter to tabItem: A TabItem.
- Parameter completion: An optional completion block.
*/
open func animate(to tabItem: TabItem, completion: ((TabItem) -> Void)? = nil) {
animate(to: tabItem, isTriggeredByUserInteraction: false, completion: completion)
}
}
fileprivate extension TabBar {
/// Updates the tabItems colors.
func updateTabItemColors() {
let normalColor = tabItemsColorForState[.normal]!
let selectedColor = tabItemsColorForState[.selected]!
let highlightedColor = tabItemsColorForState[.highlighted]!
for v in tabItems {
v.setTitleColor(normalColor, for: .normal)
v.setImage(v.image?.tint(with: normalColor), for: .normal)
v.setTitleColor(selectedColor, for: .selected)
v.setImage(v.image?.tint(with: selectedColor), for: .selected)
v.setTitleColor(highlightedColor, for: .highlighted)
v.setImage(v.image?.tint(with: highlightedColor), for: .highlighted)
}
}
/// Updates the line colors.
func updateLineColors() {
line.backgroundColor = lineColorForState[.selected]
}
/// Updates the line colors.
func updateLineColors() {
line.backgroundColor = lineColorForState[.selected]
}
}
fileprivate extension TabBar {
/**
Animates to a given tabItem.
- Parameter to tabItem: A TabItem.
- Parameter isTriggeredByUserInteraction: A boolean indicating whether the
state was changed by a user interaction, true if yes, false otherwise.
- Parameter completion: An optional completion block.
*/
func animate(to tabItem: TabItem, isTriggeredByUserInteraction: Bool, completion: ((TabItem) -> Void)? = nil) {
if isTriggeredByUserInteraction {
_delegate?._tabBar?(tabBar: self, willSelect: tabItem)
delegate?.tabBar?(tabBar: self, willSelect: tabItem)
}
selectedTabItem = tabItem
line.animate(.duration(0.25),
.size(width: tabItem.bounds.width, height: lineHeight),
.position(x: tabItem.center.x, y: .bottom == lineAlignment ? scrollView.bounds.height - lineHeight / 2 : lineHeight / 2),
.completion({ [weak self, isTriggeredByUserInteraction = isTriggeredByUserInteraction, tabItem = tabItem, completion = completion] in
guard let s = self else {
return
}
if isTriggeredByUserInteraction {
s.delegate?.tabBar?(tabBar: s, didSelect: tabItem)
}
completion?(tabItem)
}))
updateScrollView()
}
/**
Animates to a given tabItem.
- Parameter to tabItem: A TabItem.
- Parameter isTriggeredByUserInteraction: A boolean indicating whether the
state was changed by a user interaction, true if yes, false otherwise.
- Parameter completion: An optional completion block.
*/
func animate(to tabItem: TabItem, isTriggeredByUserInteraction: Bool, completion: ((TabItem) -> Void)? = nil) {
if isTriggeredByUserInteraction {
_delegate?._tabBar?(tabBar: self, willSelect: tabItem)
delegate?.tabBar?(tabBar: self, willSelect: tabItem)
}
selectedTabItem = tabItem
line.animate(.duration(0.25),
.size(width: tabItem.bounds.width, height: lineHeight),
.position(x: tabItem.center.x, y: .bottom == lineAlignment ? scrollView.bounds.height - lineHeight / 2 : lineHeight / 2),
.completion({ [weak self, isTriggeredByUserInteraction = isTriggeredByUserInteraction, tabItem = tabItem, completion = completion] in
guard let s = self else {
return
}
if isTriggeredByUserInteraction {
s.delegate?.tabBar?(tabBar: s, didSelect: tabItem)
}
completion?(tabItem)
}))
updateScrollView()
}
}
fileprivate extension TabBar {
/// Updates the scrollView.
func updateScrollView() {
guard let v = selectedTabItem else {
return
}
if !scrollView.bounds.contains(v.frame) {
let contentOffsetX = (v.frame.origin.x < scrollView.bounds.minX) ? v.frame.origin.x : v.frame.maxX - scrollView.bounds.width
let normalizedOffsetX = min(max(contentOffsetX, 0), scrollView.contentSize.width - scrollView.bounds.width)
scrollView.setContentOffset(CGPoint(x: normalizedOffsetX, y: 0), animated: true)
}
/// Updates the scrollView.
func updateScrollView() {
guard let v = selectedTabItem else {
return
}
if !scrollView.bounds.contains(v.frame) {
let contentOffsetX = (v.frame.origin.x < scrollView.bounds.minX) ? v.frame.origin.x : v.frame.maxX - scrollView.bounds.width
let normalizedOffsetX = min(max(contentOffsetX, 0), scrollView.contentSize.width - scrollView.bounds.width)
scrollView.setContentOffset(CGPoint(x: normalizedOffsetX, y: 0), animated: true)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment