-
Notifications
You must be signed in to change notification settings - Fork 3
/
OkSlidingTabs.swift
172 lines (154 loc) · 6.25 KB
/
OkSlidingTabs.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//
// OkSlidingTabs.swift
// OkDataSources
//
// Created by Roberto Frontado on 3/22/16.
// Copyright © 2016 Roberto Frontado. All rights reserved.
//
import UIKit
@objc public protocol OkSlidingTabsDataSource {
@objc func numberOfTabs() -> Int
@objc func titleAtIndex(_ index: Int) -> String
}
@objc public protocol OkSlidingTabsDelegate {
@objc func onTabSelected(_ index: Int)
}
@IBDesignable
@objc open class OkSlidingTabs: UIView {
fileprivate var scrollView: UIScrollView!
fileprivate var indicatorView: UIView!
fileprivate var tabs: UILabel!
fileprivate var xOffset: CGFloat = 0
fileprivate var currentTabSelected = 0
fileprivate var labels: [UILabel]!
@IBInspectable
open var xPadding: CGFloat = 20
@IBInspectable
open var xMargin: CGFloat = 0
@IBInspectable
open var labelTextColor: UIColor = UIColor.black
@IBInspectable
open var labelBgColor: UIColor = UIColor.white
@IBInspectable
open var indicatorColor: UIColor = UIColor.black
@IBInspectable
open var indicatorHeight: CGFloat = 5
@IBInspectable
open var distributeEvenly: Bool = false {
didSet {
reloadData()
}
}
open var font: UIFont = UIFont.systemFont(ofSize: 14)
open weak var dataSource: OkSlidingTabsDataSource?
open weak var delegate: OkSlidingTabsDelegate?
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initView()
}
override init(frame: CGRect) {
super.init(frame: frame)
initView()
}
// MARK: - Private methods
fileprivate func initView() {
addScrollView()
addIndicatorView()
}
fileprivate func addScrollView() {
scrollView = UIScrollView(frame: self.bounds)
scrollView.backgroundColor = UIColor.clear
scrollView.isScrollEnabled = true
scrollView.showsHorizontalScrollIndicator = false
self.addSubview(scrollView)
}
fileprivate func addIndicatorView() {
if let firstLabelFrame = labels?.first?.frame {
indicatorView?.removeFromSuperview()
indicatorView = UIView(frame: CGRect(x: 0, y: firstLabelFrame.height - indicatorHeight, width: firstLabelFrame.width, height: indicatorHeight))
indicatorView.backgroundColor = indicatorColor
scrollView.addSubview(indicatorView)
}
}
fileprivate func addTabsView() {
if let dataSource = dataSource {
if dataSource.numberOfTabs() > 0 {
labels?.forEach { $0.removeFromSuperview() }
labels = []
for i in 0..<dataSource.numberOfTabs() {
// Label
let label = UILabel()
label.text = dataSource.titleAtIndex(i)
label.backgroundColor = labelBgColor
label.font = font
label.textAlignment = .center
label.textColor = labelTextColor
label.sizeToFit()
if distributeEvenly {
let screenSize = UIScreen.main.bounds
let width = screenSize.width / CGFloat(dataSource.numberOfTabs())
label.frame = CGRect(x: xOffset, y: 0, width: width, height: self.frame.height)
} else {
label.frame = CGRect(x: xOffset, y: 0, width: label.frame.width + xPadding, height: self.frame.height)
}
scrollView.contentSize = CGSize(width: xOffset + label.frame.width, height: self.frame.height)
scrollView.addSubview(label)
labels.append(label)
// Button
let button = UIButton(frame: label.frame)
button.tag = i
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
scrollView.addSubview(button)
xOffset += label.frame.width + xMargin
}
}
}
layoutIfNeeded()
scrollView.frame = self.bounds
}
@objc internal func buttonPressed(_ sender: UIButton) {
currentTabSelected = sender.tag
delegate?.onTabSelected(currentTabSelected)
animateIndicator(currentTabSelected)
}
// MARK: - Indicator view animations
fileprivate func animateIndicator(_ index: Int) {
if !(labels != nil && labels.count > 0 && labels.count > index) {
return
}
let labelFrame = labels[index].frame
UIView.animate(withDuration: 0.3, animations: { () -> Void in
// Indicator animation
let indicatorFrame = self.indicatorView.frame
self.indicatorView.frame = CGRect(x: labelFrame.minX, y: indicatorFrame.minY, width: labelFrame.width, height: indicatorFrame.height)
// Scroll animation if distributeEvenly is false
if !self.distributeEvenly {
if labelFrame.minX < self.frame.width/2 { // The first places
self.scrollView.contentOffset = CGPoint(x: 0, y: 0)
} else { // The rest
// If the remaining space is smaller than a CGRectGetWidth(self.frame)
let lastWidth = self.scrollView.contentSize.width - labelFrame.minX - (self.frame.width - labelFrame.width)/2
if lastWidth < self.frame.width/2 - labelFrame.width/2 {
let xLastOffset = self.scrollView.contentSize.width - self.frame.width
self.scrollView.contentOffset = CGPoint(x: xLastOffset, y: 0)
} else {
// If not
let xOffset = (self.frame.width - labelFrame.width)/2
self.scrollView.contentOffset = CGPoint(x: labelFrame.minX - xOffset, y: 0)
}
}
}
})
}
// MARK: - Public methods
open func reloadData() {
xOffset = 0
scrollView.subviews.forEach { $0.removeFromSuperview() }
addTabsView()
addIndicatorView()
}
open func setCurrentTab(_ index: Int) {
currentTabSelected = index
animateIndicator(index)
}
}