14 |
15 | Made with ❤️ by [XMARTLABS](http://xmartlabs.com).
16 |
17 | Android [PagerTabStrip](http://developer.android.com/reference/android/support/v4/view/PagerTabStrip.html) for iOS!
18 |
19 | **XLPagerTabStrip** is a *Container View Controller* that allows us to switch easily among a collection of view controllers. Pan gesture can be used to move on to next or previous view controller. It shows a interactive indicator of the current, previous, next child view controllers.
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | ## Getting involved
31 |
32 | * If you **want to contribute** please feel free to **submit pull requests**.
33 | * If you **have a feature request** please **open an issue**.
34 | * If you **found a bug** or **need help** please **check older issues, [FAQ](#faq) and threads on [StackOverflow](http://stackoverflow.com/questions/tagged/XLPagerTabStrip) (Tag 'XLPagerTabStrip') before submitting an issue**.
35 |
36 | **Before contribute check the [CONTRIBUTING](CONTRIBUTING.md) file for more info.**
37 |
38 | If you use **XLPagerTabStrip** in your app We would love to hear about it! Drop us a line on [twitter](https://twitter.com/xmartlabs).
39 |
40 | ## Pager Types
41 |
42 | The library provides 4 different ways to show the view controllers.
43 |
44 | ### Button Bar
45 |
46 | This is likely to be the most common pager type. It's used by many well known apps such as instagram, youtube, skype and many others.
47 |
48 |
49 |
50 | ### Bar
51 |
52 | This mode doesn't show a title neither an image. It only shows a bar that indicates the current view controller.
53 |
54 |
55 |
56 | ### Twitter
57 |
58 | Long time ago twitter app made use of this type of pager in the app main screen.
59 |
60 |
61 |
62 | ### Segmented
63 |
64 | This mode uses a `UISegmentedControl` to indicates which is the view controller being displayed.
65 |
66 |
67 |
68 | ## Usage
69 |
70 | Basically we just need to provide the list of child view controllers to show and these view controllers should provide the information (title or image) that will be shown in the associated indicator.
71 |
72 | Let's see the steps to do this:
73 |
74 | ##### Choose which type of pager we want to create
75 |
76 | First we should choose the type of pager we want to create, depending on our choice we will have to create a view controller that extends from one of the following controllers: `TwitterPagerTabStripViewController`, `ButtonBarPagerTabStripViewController`, `SegmentedPagerTabStripViewController`, `BarPagerTabStripViewController`.
77 |
78 | > All these build-in pager controllers extend from the base class `PagerTabStripViewController`.
79 | > You can also make your custom pager controller by extending directly from `PagerTabStripViewController` in case no pager menu type fits your needs.
80 |
81 | ```swift
82 | import XLPagerTabStrip
83 |
84 | class MyPagerTabStripName: ButtonBarPagerTabStripViewController {
85 | ..
86 | }
87 | ```
88 |
89 | ##### Connect outlets and add layout constraints
90 |
91 | We strongly recommend to use IB to set up our page controller views.
92 |
93 | Drag into the storyboard a `UIViewController` and set up its class with your pager controller (`MyPagerTabStripName`).
94 | Drag a `UIScrollView` into your view controller view and connect `PagerTabStripViewController` `containerView` outlet with the scroll view.
95 |
96 | Depending on which type of paging view controller you are working with you may have to connect more outlets.
97 |
98 | For `BarPagerTabStripViewController` we should connect `barView` outlet. barView type is UIView. `ButtonBarPagerTabStripViewController` requires us to connect `buttonBarView` outlet. `buttonBarView` type is `ButtonBarView` which extends from `UICollectionView`. `SegmentedPagerTabStripViewController` has a `segmentedControl` outlet, if the outlet is not connected the library try to set up the navigationItem `titleView` property using a `UISegmentedControl`. `TwitterPagerTabStripViewController` doesn't require us to connect any additional outlet.
99 |
100 | > The example project contains a example for each pager controller type and we can look into it to see how views were added and how outlets were connected.
101 |
102 | ##### Provide the view controllers that will appear embedded into the PagerTabStrip view controller
103 |
104 | You can provide the view controllers by overriding `func viewControllers(for: pagerTabStripController: PagerTabStripViewController) -> [UIViewController]` method.
105 |
106 | ```swift
107 | override public func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
108 | return [MyEmbeddedViewController(), MySecondEmbeddedViewController()]
109 | }
110 | ```
111 |
112 | > The method above is the only method declared in `PagerTabStripDataSource` protocol. We don't need to explicitly conform to it since base pager class already does it.
113 |
114 |
115 | ##### Provide information to show in each indicator
116 |
117 | Every UIViewController that will appear within the PagerTabStrip needs to provide either a title or an image.
118 | In order to do so they should conform to `IndicatorInfoProvider` by implementing `func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo`
119 | which provides the information required to show the PagerTabStrip menu (indicator) associated with the view controller.
120 |
121 | ```swift
122 | class MyEmbeddedViewController: UITableViewController, IndicatorInfoProvider {
123 |
124 | func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
125 | return IndicatorInfo(title: "My Child title")
126 | }
127 | }
128 | ```
129 |
130 | **For a detailed step by step guide about how to use the library, please check out this community [blog post](https://medium.com/michaeladeyeri/how-to-implement-android-like-tab-layouts-in-ios-using-swift-3-578516c3aa9).**
131 |
132 | That's it! We're done! 🍻🍻
133 |
134 |
135 | ## Customization
136 |
137 | ##### Pager Behaviour
138 |
139 | The pager indicator can be updated progressive as we swipe or at once in the middle of the transition between the view controllers.
140 | By setting up `pagerBehaviour` property we can choose how the indicator should be updated.
141 |
142 | ```swift
143 | public var pagerBehaviour: PagerTabStripBehaviour
144 | ```
145 |
146 | ```swift
147 | public enum PagerTabStripBehaviour {
148 | case Common(skipIntermediteViewControllers: Bool)
149 | case Progressive(skipIntermediteViewControllers: Bool, elasticIndicatorLimit: Bool)
150 | }
151 | ```
152 |
153 | Default Values:
154 | ```swift
155 | // Twitter Type
156 | PagerTabStripBehaviour.Common(skipIntermediteViewControllers: true)
157 | // Segmented Type
158 | PagerTabStripBehaviour.Common(skipIntermediteViewControllers: true)
159 | // Bar Type
160 | PagerTabStripBehaviour.Progressive(skipIntermediteViewControllers: true, elasticIndicatorLimit: true)
161 | // ButtonBar Type
162 | PagerTabStripBehaviour.Progressive(skipIntermediteViewControllers: true, elasticIndicatorLimit: true)`
163 | ```
164 |
165 | As you might have noticed `Common` and `Progressive` enumeration cases has `skipIntermediteViewControllers` and `elasticIndicatorLimit` associated values.
166 |
167 | `skipIntermediteViewControllers` allows us to skip intermediate view controllers when a tab indicator is tapped.
168 |
169 | `elasticIndicatorLimit` allows us to tension the indicator when we reach a limit, I mean when we try to move forward from last indicator or move back from first indicator.
170 |
171 | ##### PagerTabStripDelegate & PagerTabStripIsProgressiveDelegate
172 |
173 | Normally we don't need to implement these protocols because each pager type already conforms to it in order to properly update its indicator. Anyway there may be some scenarios when overriding a method come come in handy.
174 |
175 | ```swift
176 | public protocol PagerTabStripDelegate: class {
177 |
178 | func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int)
179 | }
180 |
181 | public protocol PagerTabStripIsProgressiveDelegate : PagerTabStripDelegate {
182 |
183 | func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: CGFloat, indexWasChanged: Bool)
184 | }
185 | ```
186 |
187 | > Again, The method invoked by the library depends on the `pagerBehaviour` value.
188 |
189 |
190 |
191 |
192 | ### ButtonBar Customization
193 |
194 | ```swift
195 |
196 | settings.style.buttonBarBackgroundColor: UIColor?
197 | // buttonBar minimumInteritemSpacing value, note that button bar extends from UICollectionView
198 | settings.style.buttonBarMinimumInteritemSpacing: CGFloat?
199 | // buttonBar minimumLineSpacing value
200 | settings.style.buttonBarMinimumLineSpacing: CGFloat?
201 | // buttonBar flow layout left content inset value
202 | settings.style.buttonBarLeftContentInset: CGFloat?
203 | // buttonBar flow layout right content inset value
204 | settings.style.buttonBarRightContentInset: CGFloat?
205 |
206 | // selected bar view is created programmatically so it's important to set up the following 2 properties properly
207 | settings.style.selectedBarBackgroundColor = UIColor.blackColor()
208 | settings.style.selectedBarHeight: CGFloat = 5
209 |
210 | // each buttonBar item is a UICollectionView cell of type ButtonBarViewCell
211 | settings.style.buttonBarItemBackgroundColor: UIColor?
212 | settings.style.buttonBarItemFont = UIFont.systemFontOfSize(18)
213 | // helps to determine the cell width, it represent the space before and after the title label
214 | settings.style.buttonBarItemLeftRightMargin: CGFloat = 8
215 | settings.style.buttonBarItemTitleColor: UIColor?
216 | // in case the barView items do not fill the screen width this property stretch the cells to fill the screen
217 | settings.style.buttonBarItemsShouldFillAvailiableWidth = true
218 | // only used if button bar is created programmatically and not using storyboards or nib files as recommended.
219 | public var buttonBarHeight: CGFloat?
220 | ```
221 |
222 | **Important:** Settings should be called before `viewDidLoad` is called.
223 | ```swift
224 | override func viewDidLoad() {
225 | self.settings.style.selectedBarHeight = 2
226 | self.settings.style.selectedBarBackgroundColor = UIColor.whiteColor()
227 |
228 | super.viewDidLoad()
229 | }
230 | ```
231 |
232 | ##### Update cells when selected indicator changes
233 |
234 | We may need to update the indicator cell when the displayed view controller changes. The following function properties help to accomplish that. Depending on our pager `pagerBehaviour` value we will have to set up `changeCurrentIndex` or `changeCurrentIndexProgressive`.
235 |
236 | ```swift
237 | public var changeCurrentIndex: ((oldCell: ButtonBarViewCell?, newCell: ButtonBarViewCell?, animated: Bool) -> Void)?
238 | public var changeCurrentIndexProgressive: ((oldCell: ButtonBarViewCell?, newCell: ButtonBarViewCell?, progressPercentage: CGFloat, changeCurrentIndex: Bool, animated: Bool) -> Void)?
239 | ```
240 |
241 | Let's see an example:
242 |
243 | ```swift
244 | changeCurrentIndexProgressive = { (oldCell: ButtonBarViewCell?, newCell: ButtonBarViewCell?, progressPercentage: CGFloat, changeCurrentIndex: Bool, animated: Bool) -> Void in
245 | guard changeCurrentIndex == true else { return }
246 |
247 | oldCell?.label.textColor = UIColor(white: 1, alpha: 0.6)
248 | newCell?.label.textColor = .whiteColor()
249 |
250 | if animated {
251 | UIView.animateWithDuration(0.1, animations: { () -> Void in
252 | newCell?.transform = CGAffineTransformMakeScale(1.0, 1.0)
253 | oldCell?.transform = CGAffineTransformMakeScale(0.8, 0.8)
254 | })
255 | }
256 | else {
257 | newCell?.transform = CGAffineTransformMakeScale(1.0, 1.0)
258 | oldCell?.transform = CGAffineTransformMakeScale(0.8, 0.8)
259 | }
260 | }
261 | ```
262 |
263 | ### Bar Type Customization
264 |
265 | ```swift
266 | settings.style.barBackgroundColor: UIColor?
267 | settings.style.selectedBarBackgroundColor: UIColor?
268 | // barHeight is only set up when the bar is created programmatically and not using storyboards or xib files as recommended.
269 | settings.style.barHeight: CGFloat = 5
270 | ```
271 |
272 | ### Twitter Type Customization
273 |
274 | ```swift
275 | settings.style.dotColor = UIColor(white: 1, alpha: 0.4)
276 | settings.style.selectedDotColor = UIColor.whiteColor()
277 | settings.style.portraitTitleFont = UIFont.systemFontOfSize(18)
278 | settings.style.landscapeTitleFont = UIFont.systemFontOfSize(15)
279 | settings.style.titleColor = UIColor.whiteColor()
280 | ```
281 |
282 | ### Segmented Type Customization
283 |
284 | ```swift
285 | settings.style.segmentedControlColor: UIColor?
286 | ```
287 |
288 |
289 |
290 | ## Requirements
291 |
292 | * iOS 8.0+
293 | * Xcode 8.0+
294 |
295 | ## Examples
296 |
297 | Follow these 3 steps to run Example project: Clone XLPagerTabStrip repository, open XLPagerTabStrip workspace and run the *Example* project.
298 |
299 | ## Installation
300 |
301 | ### CocoaPods
302 |
303 | [CocoaPods](https://cocoapods.org/) is a dependency manager for Cocoa projects.
304 |
305 | To install XLPagerTabStrip, simply add the following line to your Podfile:
306 |
307 | ```ruby
308 | pod 'XLPagerTabStrip', '~> 8.0'
309 | ```
310 |
311 | ### Carthage
312 |
313 | [Carthage](https://github.com/Carthage/Carthage) is a simple, decentralized dependency manager for Cocoa.
314 |
315 | To install XLPagerTabStrip, simply add the following line to your Cartfile:
316 |
317 | ```ogdl
318 | github "xmartlabs/XLPagerTabStrip" ~> 8.0
319 | ```
320 |
321 | ## FAQ
322 |
323 | #### How to change the visible child view controller programmatically
324 |
325 | `PagerTabStripViewController` provides the following methods to programmatically change the visible child view controller:
326 |
327 | ```swift
328 | func moveToViewController(at index: Int)
329 | func moveToViewController(at index: Int, animated: Bool)
330 | func moveTo(viewController: UIViewController)
331 | func moveTo(viewController: UIViewController, animated: Bool)
332 | ```
333 |
334 |
335 | #### How to migrate from Swift 2 to Swift 3
336 |
337 | Check out [our migration guide](https://github.com/xmartlabs/XLPagerTabStrip/blob/master/Migration.md)
338 |
339 | ## Author
340 |
341 | * [Martin Barreto](https://github.com/mtnBarreto) ([@mtnBarreto](https://twitter.com/mtnBarreto))
342 |
343 | ## Change Log
344 |
345 | This can be found in the [CHANGELOG.md](CHANGELOG.md) file.
346 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/BarPagerTabStripViewController.swift:
--------------------------------------------------------------------------------
1 | // BarPagerTabStripViewController.swift
2 | // XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip )
3 | //
4 | // Copyright (c) 2017 Xmartlabs ( http://xmartlabs.com )
5 | //
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 |
25 | import Foundation
26 | import UIKit
27 |
28 | public struct BarPagerTabStripSettings {
29 |
30 | public struct Style {
31 | public var barBackgroundColor: UIColor?
32 | public var selectedBarBackgroundColor: UIColor?
33 | public var barHeight: CGFloat = 5 // barHeight is ony set up when the bar is created programatically and not using storyboards or xib files.
34 | }
35 |
36 | public var style = Style()
37 | }
38 |
39 | open class BarPagerTabStripViewController: PagerTabStripViewController, PagerTabStripDataSource, PagerTabStripIsProgressiveDelegate {
40 |
41 | public var settings = BarPagerTabStripSettings()
42 |
43 | @IBOutlet weak public var barView: BarView!
44 |
45 | required public init?(coder aDecoder: NSCoder) {
46 | super.init(coder: aDecoder)
47 | delegate = self
48 | datasource = self
49 | }
50 |
51 | public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
52 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
53 | delegate = self
54 | datasource = self
55 | }
56 |
57 | open override func viewDidLoad() {
58 | super.viewDidLoad()
59 | barView = barView ?? {
60 | let barView = BarView(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: settings.style.barHeight))
61 | barView.autoresizingMask = .flexibleWidth
62 | barView.backgroundColor = .black
63 | barView.selectedBar.backgroundColor = .white
64 | return barView
65 | }()
66 |
67 | barView.backgroundColor = settings.style.barBackgroundColor ?? barView.backgroundColor
68 | barView.selectedBar.backgroundColor = settings.style.selectedBarBackgroundColor ?? barView.selectedBar.backgroundColor
69 | }
70 |
71 | open override func viewWillAppear(_ animated: Bool) {
72 | super.viewWillAppear(animated)
73 | if barView.superview == nil {
74 | view.addSubview(barView)
75 | }
76 | barView.optionsCount = viewControllers.count
77 | barView.moveTo(index: currentIndex, animated: false)
78 | }
79 |
80 | open override func reloadPagerTabStripView() {
81 | super.reloadPagerTabStripView()
82 | barView.optionsCount = viewControllers.count
83 | if isViewLoaded {
84 | barView.moveTo(index: currentIndex, animated: false)
85 | }
86 | }
87 |
88 | // MARK: - PagerTabStripDelegate
89 |
90 | open func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: CGFloat, indexWasChanged: Bool) {
91 |
92 | barView.move(fromIndex: fromIndex, toIndex: toIndex, progressPercentage: progressPercentage)
93 | }
94 |
95 | open func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int) {
96 | barView.moveTo(index: toIndex, animated: true)
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/BarView.swift:
--------------------------------------------------------------------------------
1 | // BarView.swift
2 | // XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip )
3 | //
4 | // Copyright (c) 2017 Xmartlabs ( http://xmartlabs.com )
5 | //
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 |
25 | import Foundation
26 |
27 | open class BarView: UIView {
28 |
29 | open lazy var selectedBar: UIView = { [unowned self] in
30 | let selectedBar = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height))
31 | return selectedBar
32 | }()
33 |
34 | var optionsCount = 1 {
35 | willSet(newOptionsCount) {
36 | if newOptionsCount <= selectedIndex {
37 | selectedIndex = optionsCount - 1
38 | }
39 | }
40 | }
41 | var selectedIndex = 0
42 |
43 | required public init?(coder aDecoder: NSCoder) {
44 | super.init(coder: aDecoder)
45 | addSubview(selectedBar)
46 | }
47 |
48 | override init(frame: CGRect) {
49 | super.init(frame: frame)
50 | addSubview(selectedBar)
51 | }
52 |
53 | // MARK: - Helpers
54 |
55 | private func updateSelectedBarPosition(with animation: Bool) {
56 | var frame = selectedBar.frame
57 | frame.size.width = self.frame.size.width / CGFloat(optionsCount)
58 | frame.origin.x = frame.size.width * CGFloat(selectedIndex)
59 | if animation {
60 | UIView.animate(withDuration: 0.3, animations: { [weak self] in
61 | self?.selectedBar.frame = frame
62 | })
63 | } else {
64 | selectedBar.frame = frame
65 | }
66 | }
67 |
68 | open func moveTo(index: Int, animated: Bool) {
69 | selectedIndex = index
70 | updateSelectedBarPosition(with: animated)
71 | }
72 |
73 | open func move(fromIndex: Int, toIndex: Int, progressPercentage: CGFloat) {
74 | selectedIndex = (progressPercentage > 0.5) ? toIndex : fromIndex
75 |
76 | var newFrame = selectedBar.frame
77 | newFrame.size.width = frame.size.width / CGFloat(optionsCount)
78 | var fromFrame = newFrame
79 | fromFrame.origin.x = newFrame.size.width * CGFloat(fromIndex)
80 | var toFrame = newFrame
81 | toFrame.origin.x = toFrame.size.width * CGFloat(toIndex)
82 | var targetFrame = fromFrame
83 | targetFrame.origin.x += (toFrame.origin.x - targetFrame.origin.x) * CGFloat(progressPercentage)
84 | selectedBar.frame = targetFrame
85 | }
86 |
87 | open override func layoutSubviews() {
88 | super.layoutSubviews()
89 | updateSelectedBarPosition(with: false)
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/BaseButtonBarPagerTabStripViewController.swift:
--------------------------------------------------------------------------------
1 | // BaseButtonBarPagerTabStripViewController.swift
2 | // XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip )
3 | //
4 | // Copyright (c) 2017 Xmartlabs ( http://xmartlabs.com )
5 | //
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 |
25 | import Foundation
26 |
27 | open class BaseButtonBarPagerTabStripViewController: PagerTabStripViewController, PagerTabStripDataSource, PagerTabStripIsProgressiveDelegate, UICollectionViewDelegate, UICollectionViewDataSource {
28 |
29 | public var settings = ButtonBarPagerTabStripSettings()
30 | public var buttonBarItemSpec: ButtonBarItemSpec!
31 | public var changeCurrentIndex: ((_ oldCell: ButtonBarCellType?, _ newCell: ButtonBarCellType?, _ animated: Bool) -> Void)?
32 | public var changeCurrentIndexProgressive: ((_ oldCell: ButtonBarCellType?, _ newCell: ButtonBarCellType?, _ progressPercentage: CGFloat, _ changeCurrentIndex: Bool, _ animated: Bool) -> Void)?
33 |
34 | @IBOutlet public weak var buttonBarView: ButtonBarView!
35 |
36 | lazy private var cachedCellWidths: [CGFloat]? = { [unowned self] in
37 | return self.calculateWidths()
38 | }()
39 |
40 | public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
41 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
42 | delegate = self
43 | datasource = self
44 | }
45 |
46 | required public init?(coder aDecoder: NSCoder) {
47 | super.init(coder: aDecoder)
48 | delegate = self
49 | datasource = self
50 | }
51 |
52 | open override func viewDidLoad() {
53 | super.viewDidLoad()
54 | let buttonBarViewAux = buttonBarView ?? {
55 | let flowLayout = UICollectionViewFlowLayout()
56 | flowLayout.scrollDirection = .horizontal
57 | flowLayout.sectionInset = UIEdgeInsets(top: 0, left: settings.style.buttonBarLeftContentInset ?? 35, bottom: 0, right: settings.style.buttonBarRightContentInset ?? 35)
58 | let buttonBarHeight = settings.style.buttonBarHeight ?? 44
59 | let buttonBar = ButtonBarView(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: buttonBarHeight), collectionViewLayout: flowLayout)
60 | buttonBar.backgroundColor = .orange
61 | buttonBar.selectedBar.backgroundColor = .black
62 | buttonBar.autoresizingMask = .flexibleWidth
63 | var newContainerViewFrame = containerView.frame
64 | newContainerViewFrame.origin.y = buttonBarHeight
65 | newContainerViewFrame.size.height = containerView.frame.size.height - (buttonBarHeight - containerView.frame.origin.y)
66 | containerView.frame = newContainerViewFrame
67 | return buttonBar
68 | }()
69 | buttonBarView = buttonBarViewAux
70 |
71 | if buttonBarView.superview == nil {
72 | view.addSubview(buttonBarView)
73 | }
74 | if buttonBarView.delegate == nil {
75 | buttonBarView.delegate = self
76 | }
77 | if buttonBarView.dataSource == nil {
78 | buttonBarView.dataSource = self
79 | }
80 | buttonBarView.scrollsToTop = false
81 | let flowLayout = buttonBarView.collectionViewLayout as! UICollectionViewFlowLayout // swiftlint:disable:this force_cast
82 | flowLayout.scrollDirection = .horizontal
83 | flowLayout.minimumInteritemSpacing = settings.style.buttonBarMinimumInteritemSpacing ?? flowLayout.minimumInteritemSpacing
84 | flowLayout.minimumLineSpacing = settings.style.buttonBarMinimumLineSpacing ?? flowLayout.minimumLineSpacing
85 | let sectionInset = flowLayout.sectionInset
86 | flowLayout.sectionInset = UIEdgeInsets(top: sectionInset.top, left: settings.style.buttonBarLeftContentInset ?? sectionInset.left, bottom: sectionInset.bottom, right: settings.style.buttonBarRightContentInset ?? sectionInset.right)
87 | buttonBarView.showsHorizontalScrollIndicator = false
88 | buttonBarView.backgroundColor = settings.style.buttonBarBackgroundColor ?? buttonBarView.backgroundColor
89 | buttonBarView.selectedBar.backgroundColor = settings.style.selectedBarBackgroundColor
90 |
91 | buttonBarView.selectedBarHeight = settings.style.selectedBarHeight
92 | // register button bar item cell
93 | switch buttonBarItemSpec! {
94 | case .nibFile(let nibName, let bundle, _):
95 | buttonBarView.register(UINib(nibName: nibName, bundle: bundle), forCellWithReuseIdentifier:"Cell")
96 | case .cellClass:
97 | buttonBarView.register(ButtonBarCellType.self, forCellWithReuseIdentifier:"Cell")
98 | }
99 | //-
100 | }
101 |
102 | open override func viewWillAppear(_ animated: Bool) {
103 | super.viewWillAppear(animated)
104 | buttonBarView.layoutIfNeeded()
105 | isViewAppearing = true
106 | }
107 |
108 | open override func viewDidAppear(_ animated: Bool) {
109 | super.viewDidAppear(animated)
110 | isViewAppearing = false
111 | }
112 |
113 | open override func viewDidLayoutSubviews() {
114 | super.viewDidLayoutSubviews()
115 |
116 | guard isViewAppearing || isViewRotating else { return }
117 |
118 | // Force the UICollectionViewFlowLayout to get laid out again with the new size if
119 | // a) The view is appearing. This ensures that
120 | // collectionView:layout:sizeForItemAtIndexPath: is called for a second time
121 | // when the view is shown and when the view *frame(s)* are actually set
122 | // (we need the view frame's to have been set to work out the size's and on the
123 | // first call to collectionView:layout:sizeForItemAtIndexPath: the view frame(s)
124 | // aren't set correctly)
125 | // b) The view is rotating. This ensures that
126 | // collectionView:layout:sizeForItemAtIndexPath: is called again and can use the views
127 | // *new* frame so that the buttonBarView cell's actually get resized correctly
128 | cachedCellWidths = calculateWidths()
129 | buttonBarView.collectionViewLayout.invalidateLayout()
130 | // When the view first appears or is rotated we also need to ensure that the barButtonView's
131 | // selectedBar is resized and its contentOffset/scroll is set correctly (the selected
132 | // tab/cell may end up either skewed or off screen after a rotation otherwise)
133 | buttonBarView.moveTo(index: currentIndex, animated: false, swipeDirection: .none, pagerScroll: .scrollOnlyIfOutOfScreen)
134 | }
135 |
136 | // MARK: - View Rotation
137 |
138 | open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
139 | super.viewWillTransition(to: size, with: coordinator)
140 | }
141 |
142 | // MARK: - Public Methods
143 |
144 | open override func reloadPagerTabStripView() {
145 | super.reloadPagerTabStripView()
146 | guard isViewLoaded else { return }
147 | buttonBarView.reloadData()
148 | cachedCellWidths = calculateWidths()
149 | buttonBarView.moveTo(index: currentIndex, animated: false, swipeDirection: .none, pagerScroll: .yes)
150 | }
151 |
152 | open func calculateStretchedCellWidths(_ minimumCellWidths: [CGFloat], suggestedStretchedCellWidth: CGFloat, previousNumberOfLargeCells: Int) -> CGFloat {
153 | var numberOfLargeCells = 0
154 | var totalWidthOfLargeCells: CGFloat = 0
155 |
156 | for minimumCellWidthValue in minimumCellWidths where minimumCellWidthValue > suggestedStretchedCellWidth {
157 | totalWidthOfLargeCells += minimumCellWidthValue
158 | numberOfLargeCells += 1
159 | }
160 |
161 | guard numberOfLargeCells > previousNumberOfLargeCells else { return suggestedStretchedCellWidth }
162 |
163 | let flowLayout = buttonBarView.collectionViewLayout as! UICollectionViewFlowLayout // swiftlint:disable:this force_cast
164 | let collectionViewAvailiableWidth = buttonBarView.frame.size.width - flowLayout.sectionInset.left - flowLayout.sectionInset.right
165 | let numberOfCells = minimumCellWidths.count
166 | let cellSpacingTotal = CGFloat(numberOfCells - 1) * flowLayout.minimumLineSpacing
167 |
168 | let numberOfSmallCells = numberOfCells - numberOfLargeCells
169 | let newSuggestedStretchedCellWidth = (collectionViewAvailiableWidth - totalWidthOfLargeCells - cellSpacingTotal) / CGFloat(numberOfSmallCells)
170 |
171 | return calculateStretchedCellWidths(minimumCellWidths, suggestedStretchedCellWidth: newSuggestedStretchedCellWidth, previousNumberOfLargeCells: numberOfLargeCells)
172 | }
173 |
174 | open func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int) {
175 | guard shouldUpdateButtonBarView else { return }
176 | buttonBarView.moveTo(index: toIndex, animated: true, swipeDirection: toIndex < fromIndex ? .right : .left, pagerScroll: .yes)
177 |
178 | if let changeCurrentIndex = changeCurrentIndex {
179 | let oldCell = buttonBarView.cellForItem(at: IndexPath(item: currentIndex != fromIndex ? fromIndex : toIndex, section: 0)) as? ButtonBarCellType
180 | let newCell = buttonBarView.cellForItem(at: IndexPath(item: currentIndex, section: 0)) as? ButtonBarCellType
181 | changeCurrentIndex(oldCell, newCell, true)
182 | }
183 | }
184 |
185 | open func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: CGFloat, indexWasChanged: Bool) {
186 | guard shouldUpdateButtonBarView else { return }
187 | buttonBarView.move(fromIndex: fromIndex, toIndex: toIndex, progressPercentage: progressPercentage, pagerScroll: .yes)
188 | if let changeCurrentIndexProgressive = changeCurrentIndexProgressive {
189 | let oldCell = buttonBarView.cellForItem(at: IndexPath(item: currentIndex != fromIndex ? fromIndex : toIndex, section: 0)) as? ButtonBarCellType
190 | let newCell = buttonBarView.cellForItem(at: IndexPath(item: currentIndex, section: 0)) as? ButtonBarCellType
191 | changeCurrentIndexProgressive(oldCell, newCell, progressPercentage, indexWasChanged, true)
192 | }
193 | }
194 |
195 | // MARK: - UICollectionViewDelegateFlowLayut
196 |
197 | @objc open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
198 | guard let cellWidthValue = cachedCellWidths?[indexPath.row] else {
199 | fatalError("cachedCellWidths for \(indexPath.row) must not be nil")
200 | }
201 | return CGSize(width: cellWidthValue, height: collectionView.frame.size.height)
202 | }
203 |
204 | open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
205 | guard indexPath.item != currentIndex else { return }
206 |
207 | buttonBarView.moveTo(index: indexPath.item, animated: true, swipeDirection: .none, pagerScroll: .yes)
208 | shouldUpdateButtonBarView = false
209 |
210 | let oldCell = buttonBarView.cellForItem(at: IndexPath(item: currentIndex, section: 0)) as? ButtonBarCellType
211 | let newCell = buttonBarView.cellForItem(at: IndexPath(item: indexPath.item, section: 0)) as? ButtonBarCellType
212 | if pagerBehaviour.isProgressiveIndicator {
213 | if let changeCurrentIndexProgressive = changeCurrentIndexProgressive {
214 | changeCurrentIndexProgressive(oldCell, newCell, 1, true, true)
215 | }
216 | } else {
217 | if let changeCurrentIndex = changeCurrentIndex {
218 | changeCurrentIndex(oldCell, newCell, true)
219 | }
220 | }
221 | moveToViewController(at: indexPath.item)
222 | }
223 |
224 | // MARK: - UICollectionViewDataSource
225 |
226 | open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
227 | return viewControllers.count
228 | }
229 |
230 | open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
231 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as? ButtonBarCellType else {
232 | fatalError("UICollectionViewCell should be or extend from ButtonBarViewCell")
233 | }
234 | let childController = viewControllers[indexPath.item] as! IndicatorInfoProvider // swiftlint:disable:this force_cast
235 | let indicatorInfo = childController.indicatorInfo(for: self)
236 |
237 | configure(cell: cell, for: indicatorInfo)
238 |
239 | if pagerBehaviour.isProgressiveIndicator {
240 | if let changeCurrentIndexProgressive = changeCurrentIndexProgressive {
241 | changeCurrentIndexProgressive(currentIndex == indexPath.item ? nil : cell, currentIndex == indexPath.item ? cell : nil, 1, true, false)
242 | }
243 | } else {
244 | if let changeCurrentIndex = changeCurrentIndex {
245 | changeCurrentIndex(currentIndex == indexPath.item ? nil : cell, currentIndex == indexPath.item ? cell : nil, false)
246 | }
247 | }
248 |
249 | return cell
250 | }
251 |
252 | // MARK: - UIScrollViewDelegate
253 |
254 | open override func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
255 | super.scrollViewDidEndScrollingAnimation(scrollView)
256 |
257 | guard scrollView == containerView else { return }
258 | shouldUpdateButtonBarView = true
259 | }
260 |
261 | open func configure(cell: ButtonBarCellType, for indicatorInfo: IndicatorInfo) {
262 | fatalError("You must override this method to set up ButtonBarView cell accordingly")
263 | }
264 |
265 | private func calculateWidths() -> [CGFloat] {
266 | let flowLayout = buttonBarView.collectionViewLayout as! UICollectionViewFlowLayout // swiftlint:disable:this force_cast
267 | let numberOfCells = viewControllers.count
268 |
269 | var minimumCellWidths = [CGFloat]()
270 | var collectionViewContentWidth: CGFloat = 0
271 |
272 | for viewController in viewControllers {
273 | let childController = viewController as! IndicatorInfoProvider // swiftlint:disable:this force_cast
274 | let indicatorInfo = childController.indicatorInfo(for: self)
275 | switch buttonBarItemSpec! {
276 | case .cellClass(let widthCallback):
277 | let width = widthCallback(indicatorInfo)
278 | minimumCellWidths.append(width)
279 | collectionViewContentWidth += width
280 | case .nibFile(_, _, let widthCallback):
281 | let width = widthCallback(indicatorInfo)
282 | minimumCellWidths.append(width)
283 | collectionViewContentWidth += width
284 | }
285 | }
286 |
287 | let cellSpacingTotal = CGFloat(numberOfCells - 1) * flowLayout.minimumLineSpacing
288 | collectionViewContentWidth += cellSpacingTotal
289 |
290 | let collectionViewAvailableVisibleWidth = buttonBarView.frame.size.width - flowLayout.sectionInset.left - flowLayout.sectionInset.right
291 |
292 | if !settings.style.buttonBarItemsShouldFillAvailableWidth || collectionViewAvailableVisibleWidth < collectionViewContentWidth {
293 | return minimumCellWidths
294 | } else {
295 | let stretchedCellWidthIfAllEqual = (collectionViewAvailableVisibleWidth - cellSpacingTotal) / CGFloat(numberOfCells)
296 | let generalMinimumCellWidth = calculateStretchedCellWidths(minimumCellWidths, suggestedStretchedCellWidth: stretchedCellWidthIfAllEqual, previousNumberOfLargeCells: 0)
297 | var stretchedCellWidths = [CGFloat]()
298 |
299 | for minimumCellWidthValue in minimumCellWidths {
300 | let cellWidth = (minimumCellWidthValue > generalMinimumCellWidth) ? minimumCellWidthValue : generalMinimumCellWidth
301 | stretchedCellWidths.append(cellWidth)
302 | }
303 |
304 | return stretchedCellWidths
305 | }
306 | }
307 |
308 | private var shouldUpdateButtonBarView = true
309 | }
310 |
311 | open class ExampleBaseButtonBarPagerTabStripViewController: BaseButtonBarPagerTabStripViewController {
312 |
313 | public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
314 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
315 | initialize()
316 | }
317 |
318 | public required init?(coder aDecoder: NSCoder) {
319 | super.init(coder: aDecoder)
320 | initialize()
321 | }
322 |
323 | open func initialize() {
324 | buttonBarItemSpec = .nibFile(nibName: "ButtonCell", bundle: Bundle(for: ButtonBarViewCell.self), width: { [weak self] (childItemInfo) -> CGFloat in
325 | let label = UILabel()
326 | label.translatesAutoresizingMaskIntoConstraints = false
327 | label.font = self?.settings.style.buttonBarItemFont ?? label.font
328 | label.text = childItemInfo.title
329 | let labelSize = label.intrinsicContentSize
330 | return labelSize.width + CGFloat(self?.settings.style.buttonBarItemLeftRightMargin ?? 8 * 2)
331 | })
332 | }
333 |
334 | open override func configure(cell: ButtonBarViewCell, for indicatorInfo: IndicatorInfo) {
335 | cell.label.text = indicatorInfo.title
336 | if let image = indicatorInfo.image {
337 | cell.imageView.image = image
338 | }
339 | if let highlightedImage = indicatorInfo.highlightedImage {
340 | cell.imageView.highlightedImage = highlightedImage
341 | }
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/ButtonBarView.swift:
--------------------------------------------------------------------------------
1 | // ButtonBarView.swift
2 | // XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip )
3 | //
4 | // Copyright (c) 2017 Xmartlabs ( http://xmartlabs.com )
5 | //
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 |
25 | import UIKit
26 |
27 | public enum PagerScroll {
28 | case no
29 | case yes
30 | case scrollOnlyIfOutOfScreen
31 | }
32 |
33 | public enum SelectedBarAlignment {
34 | case left
35 | case center
36 | case right
37 | case progressive
38 | }
39 |
40 | public enum SelectedBarVerticalAlignment {
41 | case top
42 | case middle
43 | case bottom
44 | }
45 |
46 | open class ButtonBarView: UICollectionView {
47 |
48 | open lazy var selectedBar: UIView = { [unowned self] in
49 | let bar = UIView(frame: CGRect(x: 0, y: self.frame.size.height - CGFloat(self.selectedBarHeight), width: 0, height: CGFloat(self.selectedBarHeight)))
50 | bar.layer.zPosition = 9999
51 | return bar
52 | }()
53 |
54 | internal var selectedBarHeight: CGFloat = 4 {
55 | didSet {
56 | updateSelectedBarYPosition()
57 | }
58 | }
59 | var selectedBarVerticalAlignment: SelectedBarVerticalAlignment = .bottom
60 | var selectedBarAlignment: SelectedBarAlignment = .center
61 | var selectedIndex = 0
62 |
63 | required public init?(coder aDecoder: NSCoder) {
64 | super.init(coder: aDecoder)
65 | addSubview(selectedBar)
66 | }
67 |
68 | public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
69 | super.init(frame: frame, collectionViewLayout: layout)
70 | addSubview(selectedBar)
71 | }
72 |
73 | open func moveTo(index: Int, animated: Bool, swipeDirection: SwipeDirection, pagerScroll: PagerScroll) {
74 | selectedIndex = index
75 | updateSelectedBarPosition(animated, swipeDirection: swipeDirection, pagerScroll: pagerScroll)
76 | }
77 |
78 | open func move(fromIndex: Int, toIndex: Int, progressPercentage: CGFloat, pagerScroll: PagerScroll) {
79 | selectedIndex = progressPercentage > 0.5 ? toIndex : fromIndex
80 |
81 | let fromFrame = layoutAttributesForItem(at: IndexPath(item: fromIndex, section: 0))!.frame
82 | let numberOfItems = dataSource!.collectionView(self, numberOfItemsInSection: 0)
83 |
84 | var toFrame: CGRect
85 |
86 | if toIndex < 0 || toIndex > numberOfItems - 1 {
87 | if toIndex < 0 {
88 | let cellAtts = layoutAttributesForItem(at: IndexPath(item: 0, section: 0))
89 | toFrame = cellAtts!.frame.offsetBy(dx: -cellAtts!.frame.size.width, dy: 0)
90 | } else {
91 | let cellAtts = layoutAttributesForItem(at: IndexPath(item: (numberOfItems - 1), section: 0))
92 | toFrame = cellAtts!.frame.offsetBy(dx: cellAtts!.frame.size.width, dy: 0)
93 | }
94 | } else {
95 | toFrame = layoutAttributesForItem(at: IndexPath(item: toIndex, section: 0))!.frame
96 | }
97 |
98 | var targetFrame = fromFrame
99 | targetFrame.size.height = selectedBar.frame.size.height
100 | targetFrame.size.width += (toFrame.size.width - fromFrame.size.width) * progressPercentage
101 | targetFrame.origin.x += (toFrame.origin.x - fromFrame.origin.x) * progressPercentage
102 |
103 | selectedBar.frame = CGRect(x: targetFrame.origin.x, y: selectedBar.frame.origin.y, width: targetFrame.size.width, height: selectedBar.frame.size.height)
104 |
105 | var targetContentOffset: CGFloat = 0.0
106 | if contentSize.width > frame.size.width {
107 | let toContentOffset = contentOffsetForCell(withFrame: toFrame, andIndex: toIndex)
108 | let fromContentOffset = contentOffsetForCell(withFrame: fromFrame, andIndex: fromIndex)
109 |
110 | targetContentOffset = fromContentOffset + ((toContentOffset - fromContentOffset) * progressPercentage)
111 | }
112 |
113 | setContentOffset(CGPoint(x: targetContentOffset, y: 0), animated: false)
114 | }
115 |
116 | open func updateSelectedBarPosition(_ animated: Bool, swipeDirection: SwipeDirection, pagerScroll: PagerScroll) {
117 | var selectedBarFrame = selectedBar.frame
118 |
119 | let selectedCellIndexPath = IndexPath(item: selectedIndex, section: 0)
120 | let attributes = layoutAttributesForItem(at: selectedCellIndexPath)
121 | let selectedCellFrame = attributes!.frame
122 |
123 | updateContentOffset(animated: animated, pagerScroll: pagerScroll, toFrame: selectedCellFrame, toIndex: (selectedCellIndexPath as NSIndexPath).row)
124 |
125 | selectedBarFrame.size.width = selectedCellFrame.size.width
126 | selectedBarFrame.origin.x = selectedCellFrame.origin.x
127 |
128 | if animated {
129 | UIView.animate(withDuration: 0.3, animations: { [weak self] in
130 | self?.selectedBar.frame = selectedBarFrame
131 | })
132 | } else {
133 | selectedBar.frame = selectedBarFrame
134 | }
135 | }
136 |
137 | // MARK: - Helpers
138 |
139 | private func updateContentOffset(animated: Bool, pagerScroll: PagerScroll, toFrame: CGRect, toIndex: Int) {
140 | guard pagerScroll != .no || (pagerScroll != .scrollOnlyIfOutOfScreen && (toFrame.origin.x < contentOffset.x || toFrame.origin.x >= (contentOffset.x + frame.size.width - contentInset.left))) else { return }
141 | let targetContentOffset = contentSize.width > frame.size.width ? contentOffsetForCell(withFrame: toFrame, andIndex: toIndex) : 0
142 | setContentOffset(CGPoint(x: targetContentOffset, y: 0), animated: animated)
143 | }
144 |
145 | private func contentOffsetForCell(withFrame cellFrame: CGRect, andIndex index: Int) -> CGFloat {
146 | let sectionInset = (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset // swiftlint:disable:this force_cast
147 | var alignmentOffset: CGFloat = 0.0
148 |
149 | switch selectedBarAlignment {
150 | case .left:
151 | alignmentOffset = sectionInset.left
152 | case .right:
153 | alignmentOffset = frame.size.width - sectionInset.right - cellFrame.size.width
154 | case .center:
155 | alignmentOffset = (frame.size.width - cellFrame.size.width) * 0.5
156 | case .progressive:
157 | let cellHalfWidth = cellFrame.size.width * 0.5
158 | let leftAlignmentOffset = sectionInset.left + cellHalfWidth
159 | let rightAlignmentOffset = frame.size.width - sectionInset.right - cellHalfWidth
160 | let numberOfItems = dataSource!.collectionView(self, numberOfItemsInSection: 0)
161 | let progress = index / (numberOfItems - 1)
162 | alignmentOffset = leftAlignmentOffset + (rightAlignmentOffset - leftAlignmentOffset) * CGFloat(progress) - cellHalfWidth
163 | }
164 |
165 | var contentOffset = cellFrame.origin.x - alignmentOffset
166 | contentOffset = max(0, contentOffset)
167 | contentOffset = min(contentSize.width - frame.size.width, contentOffset)
168 | return contentOffset
169 | }
170 |
171 | private func updateSelectedBarYPosition() {
172 | var selectedBarFrame = selectedBar.frame
173 |
174 | switch selectedBarVerticalAlignment {
175 | case .top:
176 | selectedBarFrame.origin.y = 0
177 | case .middle:
178 | selectedBarFrame.origin.y = (frame.size.height - selectedBarHeight) / 2
179 | case .bottom:
180 | selectedBarFrame.origin.y = frame.size.height - selectedBarHeight
181 | }
182 |
183 | selectedBarFrame.size.height = selectedBarHeight
184 | selectedBar.frame = selectedBarFrame
185 | }
186 |
187 | override open func layoutSubviews() {
188 | super.layoutSubviews()
189 | updateSelectedBarYPosition()
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/ButtonBarViewCell.swift:
--------------------------------------------------------------------------------
1 | // ButtonBarViewCell.swift
2 | // XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip )
3 | //
4 | // Copyright (c) 2017 Xmartlabs ( http://xmartlabs.com )
5 | //
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 |
25 | import Foundation
26 |
27 | open class ButtonBarViewCell: UICollectionViewCell {
28 |
29 | @IBOutlet open var imageView: UIImageView!
30 | @IBOutlet open var label: UILabel!
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/ButtonCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/FXPageControl.h:
--------------------------------------------------------------------------------
1 | //
2 | // FXPageControl.h
3 | //
4 | // Version 1.4
5 | //
6 | // Created by Nick Lockwood on 07/01/2010.
7 | // Copyright 2010 Charcoal Design
8 | //
9 | // Distributed under the permissive zlib License
10 | // Get the latest version of FXPageControl from here:
11 | //
12 | // https://github.com/nicklockwood/FXPageControl
13 | //
14 | // This software is provided 'as-is', without any express or implied
15 | // warranty. In no event will the authors be held liable for any damages
16 | // arising from the use of this software.
17 | //
18 | // Permission is granted to anyone to use this software for any purpose,
19 | // including commercial applications, and to alter it and redistribute it
20 | // freely, subject to the following restrictions:
21 | //
22 | // 1. The origin of this software must not be misrepresented; you must not
23 | // claim that you wrote the original software. If you use this software
24 | // in a product, an acknowledgment in the product documentation would be
25 | // appreciated but is not required.
26 | //
27 | // 2. Altered source versions must be plainly marked as such, and must not be
28 | // misrepresented as being the original software.
29 | //
30 | // 3. This notice may not be removed or altered from any source distribution.
31 | //
32 |
33 |
34 | #pragma GCC diagnostic push
35 | #pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis"
36 | #import
37 |
38 |
39 | #import
40 | #undef weak_delegate
41 | #if __has_feature(objc_arc_weak)
42 | #define weak_delegate weak
43 | #else
44 | #define weak_delegate unsafe_unretained
45 | #endif
46 |
47 |
48 | extern const CGPathRef FXPageControlDotShapeCircle;
49 | extern const CGPathRef FXPageControlDotShapeSquare;
50 | extern const CGPathRef FXPageControlDotShapeTriangle;
51 |
52 |
53 | @protocol FXPageControlDelegate;
54 |
55 |
56 | IB_DESIGNABLE @interface FXPageControl : UIControl
57 |
58 | - (void)setUp;
59 | - (CGSize)sizeForNumberOfPages:(NSInteger)pageCount;
60 | - (void)updateCurrentPageDisplay;
61 |
62 | @property (nonatomic, weak_delegate) IBOutlet id delegate;
63 |
64 | @property (nonatomic, assign) IBInspectable NSInteger currentPage;
65 | @property (nonatomic, assign) IBInspectable NSInteger numberOfPages;
66 | @property (nonatomic, assign) IBInspectable BOOL defersCurrentPageDisplay;
67 | @property (nonatomic, assign) IBInspectable BOOL hidesForSinglePage;
68 | @property (nonatomic, assign, getter = isWrapEnabled) IBInspectable BOOL wrapEnabled;
69 | @property (nonatomic, assign, getter = isVertical) IBInspectable BOOL vertical;
70 |
71 | @property (nonatomic, strong) IBInspectable UIImage *dotImage;
72 | @property (nonatomic, assign) IBInspectable CGPathRef dotShape;
73 | @property (nonatomic, assign) IBInspectable CGFloat dotSize;
74 | @property (nonatomic, strong) IBInspectable UIColor *dotColor;
75 | @property (nonatomic, strong) IBInspectable UIColor *dotShadowColor;
76 | @property (nonatomic, assign) IBInspectable CGFloat dotShadowBlur;
77 | @property (nonatomic, assign) IBInspectable CGSize dotShadowOffset;
78 |
79 | @property (nonatomic, strong) IBInspectable UIImage *selectedDotImage;
80 | @property (nonatomic, assign) IBInspectable CGPathRef selectedDotShape;
81 | @property (nonatomic, assign) IBInspectable CGFloat selectedDotSize;
82 | @property (nonatomic, strong) IBInspectable UIColor *selectedDotColor;
83 | @property (nonatomic, strong) IBInspectable UIColor *selectedDotShadowColor;
84 | @property (nonatomic, assign) IBInspectable CGFloat selectedDotShadowBlur;
85 | @property (nonatomic, assign) IBInspectable CGSize selectedDotShadowOffset;
86 |
87 | @property (nonatomic, assign) IBInspectable CGFloat dotSpacing;
88 |
89 | @end
90 |
91 |
92 | @protocol FXPageControlDelegate
93 | @optional
94 |
95 | - (UIImage *)pageControl:(FXPageControl *)pageControl imageForDotAtIndex:(NSInteger)index;
96 | - (CGPathRef)pageControl:(FXPageControl *)pageControl shapeForDotAtIndex:(NSInteger)index;
97 | - (UIColor *)pageControl:(FXPageControl *)pageControl colorForDotAtIndex:(NSInteger)index;
98 |
99 | - (UIImage *)pageControl:(FXPageControl *)pageControl selectedImageForDotAtIndex:(NSInteger)index;
100 | - (CGPathRef)pageControl:(FXPageControl *)pageControl selectedShapeForDotAtIndex:(NSInteger)index;
101 | - (UIColor *)pageControl:(FXPageControl *)pageControl selectedColorForDotAtIndex:(NSInteger)index;
102 |
103 | @end
104 |
105 |
106 | #pragma GCC diagnostic pop
107 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/FXPageControl.m:
--------------------------------------------------------------------------------
1 | //
2 | // FXPageControl.m
3 | //
4 | // Version 1.4
5 | //
6 | // Created by Nick Lockwood on 07/01/2010.
7 | // Copyright 2010 Charcoal Design
8 | //
9 | // Distributed under the permissive zlib License
10 | // Get the latest version of FXPageControl from here:
11 | //
12 | // https://github.com/nicklockwood/FXPageControl
13 | //
14 | // This software is provided 'as-is', without any express or implied
15 | // warranty. In no event will the authors be held liable for any damages
16 | // arising from the use of this software.
17 | //
18 | // Permission is granted to anyone to use this software for any purpose,
19 | // including commercial applications, and to alter it and redistribute it
20 | // freely, subject to the following restrictions:
21 | //
22 | // 1. The origin of this software must not be misrepresented; you must not
23 | // claim that you wrote the original software. If you use this software
24 | // in a product, an acknowledgment in the product documentation would be
25 | // appreciated but is not required.
26 | //
27 | // 2. Altered source versions must be plainly marked as such, and must not be
28 | // misrepresented as being the original software.
29 | //
30 | // 3. This notice may not be removed or altered from any source distribution.
31 | //
32 |
33 | #import "FXPageControl.h"
34 |
35 |
36 | #pragma GCC diagnostic ignored "-Wgnu"
37 | #pragma GCC diagnostic ignored "-Warc-repeated-use-of-weak"
38 | #pragma GCC diagnostic ignored "-Wdirect-ivar-access"
39 |
40 |
41 | #import
42 | #if !__has_feature(objc_arc)
43 | #error This class requires automatic reference counting
44 | #endif
45 |
46 |
47 | const CGPathRef FXPageControlDotShapeCircle = (const CGPathRef)1;
48 | const CGPathRef FXPageControlDotShapeSquare = (const CGPathRef)2;
49 | const CGPathRef FXPageControlDotShapeTriangle = (const CGPathRef)3;
50 | #define LAST_SHAPE FXPageControlDotShapeTriangle
51 |
52 |
53 | @implementation NSObject (FXPageControl)
54 |
55 | - (UIImage *)pageControl:(__unused FXPageControl *)pageControl imageForDotAtIndex:(__unused NSInteger)index { return nil; }
56 | - (CGPathRef)pageControl:(__unused FXPageControl *)pageControl shapeForDotAtIndex:(__unused NSInteger)index { return NULL; }
57 | - (UIColor *)pageControl:(__unused FXPageControl *)pageControl colorForDotAtIndex:(__unused NSInteger)index { return nil; }
58 |
59 | - (UIImage *)pageControl:(__unused FXPageControl *)pageControl selectedImageForDotAtIndex:(__unused NSInteger)index { return nil; }
60 | - (CGPathRef)pageControl:(__unused FXPageControl *)pageControl selectedShapeForDotAtIndex:(__unused NSInteger)index { return NULL; }
61 | - (UIColor *)pageControl:(__unused FXPageControl *)pageControl selectedColorForDotAtIndex:(__unused NSInteger)index { return nil; }
62 |
63 | @end
64 |
65 |
66 | @implementation FXPageControl
67 |
68 | - (void)setUp
69 | {
70 | //needs redrawing if bounds change
71 | self.contentMode = UIViewContentModeRedraw;
72 |
73 | //set defaults
74 | _dotSpacing = 10.0f;
75 | _dotSize = 6.0f;
76 | _dotShadowOffset = CGSizeMake(0, 1);
77 | _selectedDotShadowOffset = CGSizeMake(0, 1);
78 | }
79 |
80 | - (id)initWithFrame:(CGRect)frame
81 | {
82 | if ((self = [super initWithFrame:frame]))
83 | {
84 | [self setUp];
85 | }
86 | return self;
87 | }
88 |
89 | - (id)initWithCoder:(NSCoder *)aDecoder
90 | {
91 | if ((self = [super initWithCoder:aDecoder]))
92 | {
93 | [self setUp];
94 | }
95 | return self;
96 | }
97 |
98 | - (void)dealloc
99 | {
100 | if (_dotShape > LAST_SHAPE) CGPathRelease(_dotShape);
101 | if (_selectedDotShape > LAST_SHAPE) CGPathRelease(_selectedDotShape);
102 | }
103 |
104 | - (CGSize)sizeForNumberOfPages:(__unused NSInteger)pageCount
105 | {
106 | CGFloat width = _dotSize + (_dotSize + _dotSpacing) * (_numberOfPages - 1);
107 | return _vertical? CGSizeMake(_dotSize, width): CGSizeMake(width, _dotSize);
108 | }
109 |
110 | - (void)updateCurrentPageDisplay
111 | {
112 | [self setNeedsDisplay];
113 | }
114 |
115 | - (void)drawRect:(__unused CGRect)rect
116 | {
117 | if (_numberOfPages > 1 || !_hidesForSinglePage)
118 | {
119 | CGContextRef context = UIGraphicsGetCurrentContext();
120 | CGSize size = [self sizeForNumberOfPages:_numberOfPages];
121 | if (_vertical)
122 | {
123 | CGContextTranslateCTM(context, self.frame.size.width / 2, (self.frame.size.height - size.height) / 2);
124 | }
125 | else
126 | {
127 | CGContextTranslateCTM(context, (self.frame.size.width - size.width) / 2, self.frame.size.height / 2);
128 | }
129 |
130 | for (int i = 0; i < _numberOfPages; i++)
131 | {
132 | UIImage *dotImage = nil;
133 | UIColor *dotColor = nil;
134 | CGPathRef dotShape = NULL;
135 | CGFloat dotSize = 0;
136 | UIColor *dotShadowColor = nil;
137 | CGSize dotShadowOffset = CGSizeZero;
138 | CGFloat dotShadowBlur = 0;
139 |
140 | if (i == _currentPage)
141 | {
142 | [_selectedDotColor setFill];
143 | dotImage = [_delegate pageControl:self selectedImageForDotAtIndex:i] ?: _selectedDotImage;
144 | dotShape = [_delegate pageControl:self selectedShapeForDotAtIndex:i] ?: _selectedDotShape ?: _dotShape;
145 | dotColor = [_delegate pageControl:self selectedColorForDotAtIndex:i] ?: _selectedDotColor ?: [UIColor blackColor];
146 | dotShadowBlur = _selectedDotShadowBlur;
147 | dotShadowColor = _selectedDotShadowColor;
148 | dotShadowOffset = _selectedDotShadowOffset;
149 | dotSize = _selectedDotSize ?: _dotSize;
150 | }
151 | else
152 | {
153 | [_dotColor setFill];
154 | dotImage = [_delegate pageControl:self imageForDotAtIndex:i] ?: _dotImage;
155 | dotShape = [_delegate pageControl:self shapeForDotAtIndex:i] ?: _dotShape;
156 | dotColor = [_delegate pageControl:self colorForDotAtIndex:i] ?: _dotColor;
157 | if (!dotColor)
158 | {
159 | //fall back to selected dot color with reduced alpha
160 | dotColor = [_delegate pageControl:self selectedColorForDotAtIndex:i] ?: _selectedDotColor ?: [UIColor blackColor];
161 | dotColor = [dotColor colorWithAlphaComponent:0.25f];
162 | }
163 | dotShadowBlur = _dotShadowBlur;
164 | dotShadowColor = _dotShadowColor;
165 | dotShadowOffset = _dotShadowOffset;
166 | dotSize = _dotSize;
167 | }
168 |
169 | CGContextSaveGState(context);
170 | CGFloat offset = (_dotSize + _dotSpacing) * i + _dotSize / 2;
171 | CGContextTranslateCTM(context, _vertical? 0: offset, _vertical? offset: 0);
172 |
173 | if (dotShadowColor && ![dotShadowColor isEqual:[UIColor clearColor]])
174 | {
175 | CGContextSetShadowWithColor(context, dotShadowOffset, dotShadowBlur, dotShadowColor.CGColor);
176 | }
177 | if (dotImage)
178 | {
179 | [dotImage drawInRect:CGRectMake(-dotImage.size.width / 2, -dotImage.size.height / 2, dotImage.size.width, dotImage.size.height)];
180 | }
181 | else
182 | {
183 | [dotColor setFill];
184 | if (!dotShape || dotShape == FXPageControlDotShapeCircle)
185 | {
186 | CGContextFillEllipseInRect(context, CGRectMake(-dotSize / 2, -dotSize / 2, dotSize, dotSize));
187 | }
188 | else if (dotShape == FXPageControlDotShapeSquare)
189 | {
190 | CGContextFillRect(context, CGRectMake(-dotSize / 2, -dotSize / 2, dotSize, dotSize));
191 | }
192 | else if (dotShape == FXPageControlDotShapeTriangle)
193 | {
194 | CGContextBeginPath(context);
195 | CGContextMoveToPoint(context, 0, -dotSize / 2);
196 | CGContextAddLineToPoint(context, dotSize / 2, dotSize / 2);
197 | CGContextAddLineToPoint(context, -dotSize / 2, dotSize / 2);
198 | CGContextAddLineToPoint(context, 0, -dotSize / 2);
199 | CGContextFillPath(context);
200 | }
201 | else
202 | {
203 | CGContextBeginPath(context);
204 | CGContextAddPath(context, dotShape);
205 | CGContextFillPath(context);
206 | }
207 | }
208 | CGContextRestoreGState(context);
209 | }
210 | }
211 | }
212 |
213 | - (NSInteger)clampedPageValue:(NSInteger)page
214 | {
215 | if (_wrapEnabled)
216 | {
217 | return _numberOfPages? (page + _numberOfPages) % _numberOfPages: 0;
218 | }
219 | else
220 | {
221 | return MIN(MAX(0, page), _numberOfPages - 1);
222 | }
223 | }
224 |
225 | - (void)setDotImage:(UIImage *)dotImage
226 | {
227 | if (_dotImage != dotImage)
228 | {
229 | _dotImage = dotImage;
230 | [self setNeedsDisplay];
231 | }
232 | }
233 |
234 | - (void)setDotShape:(CGPathRef)dotShape
235 | {
236 | if (_dotShape != dotShape)
237 | {
238 | if (_dotShape > LAST_SHAPE) CGPathRelease(_dotShape);
239 | _dotShape = dotShape;
240 | if (_dotShape > LAST_SHAPE) CGPathRetain(_dotShape);
241 | [self setNeedsDisplay];
242 | }
243 | }
244 |
245 | - (void)setDotSize:(CGFloat)dotSize
246 | {
247 | if (ABS(_dotSize - dotSize) > 0.001)
248 | {
249 | _dotSize = dotSize;
250 | [self setNeedsDisplay];
251 | }
252 | }
253 |
254 | - (void)setDotColor:(UIColor *)dotColor
255 | {
256 | if (_dotColor != dotColor)
257 | {
258 | _dotColor = dotColor;
259 | [self setNeedsDisplay];
260 | }
261 | }
262 |
263 | - (void)setDotShadowColor:(UIColor *)dotColor
264 | {
265 | if (_dotShadowColor != dotColor)
266 | {
267 | _dotShadowColor = dotColor;
268 | [self setNeedsDisplay];
269 | }
270 | }
271 |
272 | - (void)setDotShadowBlur:(CGFloat)dotShadowBlur
273 | {
274 | if (ABS(_dotShadowBlur - dotShadowBlur) > 0.001)
275 | {
276 | _dotShadowBlur = dotShadowBlur;
277 | [self setNeedsDisplay];
278 | }
279 | }
280 |
281 | - (void)setDotShadowOffset:(CGSize)dotShadowOffset
282 | {
283 | if (!CGSizeEqualToSize(_dotShadowOffset, dotShadowOffset))
284 | {
285 | _dotShadowOffset = dotShadowOffset;
286 | [self setNeedsDisplay];
287 | }
288 | }
289 |
290 | - (void)setSelectedDotImage:(UIImage *)dotImage
291 | {
292 | if (_selectedDotImage != dotImage)
293 | {
294 | _selectedDotImage = dotImage;
295 | [self setNeedsDisplay];
296 | }
297 | }
298 |
299 | - (void)setSelectedDotColor:(UIColor *)dotColor
300 | {
301 | if (_selectedDotColor != dotColor)
302 | {
303 | _selectedDotColor = dotColor;
304 | [self setNeedsDisplay];
305 | }
306 | }
307 |
308 | - (void)setSelectedDotShape:(CGPathRef)dotShape
309 | {
310 | if (_selectedDotShape != dotShape)
311 | {
312 | if (_selectedDotShape > LAST_SHAPE) CGPathRelease(_selectedDotShape);
313 | _selectedDotShape = dotShape;
314 | if (_selectedDotShape > LAST_SHAPE) CGPathRetain(_selectedDotShape);
315 | [self setNeedsDisplay];
316 | }
317 | }
318 |
319 | - (void)setSelectedDotSize:(CGFloat)dotSize
320 | {
321 | if (ABS(_selectedDotSize - dotSize) > 0.001)
322 | {
323 | _selectedDotSize = dotSize;
324 | [self setNeedsDisplay];
325 | }
326 | }
327 |
328 | - (void)setSelectedDotShadowColor:(UIColor *)dotColor
329 | {
330 | if (_selectedDotShadowColor != dotColor)
331 | {
332 | _selectedDotShadowColor = dotColor;
333 | [self setNeedsDisplay];
334 | }
335 | }
336 |
337 | - (void)setSelectedDotShadowBlur:(CGFloat)dotShadowBlur
338 | {
339 | if (ABS(_selectedDotShadowBlur - dotShadowBlur) > 0.001)
340 | {
341 | _selectedDotShadowBlur = dotShadowBlur;
342 | [self setNeedsDisplay];
343 | }
344 | }
345 |
346 | - (void)setSelectedDotShadowOffset:(CGSize)dotShadowOffset
347 | {
348 | if (!CGSizeEqualToSize(_selectedDotShadowOffset, dotShadowOffset))
349 | {
350 | _selectedDotShadowOffset = dotShadowOffset;
351 | [self setNeedsDisplay];
352 | }
353 | }
354 |
355 | - (void)setDotSpacing:(CGFloat)dotSpacing
356 | {
357 | if (ABS(_dotSpacing - dotSpacing) > 0.001)
358 | {
359 | _dotSpacing = dotSpacing;
360 | [self setNeedsDisplay];
361 | }
362 | }
363 |
364 | - (void)setDelegate:(id)delegate
365 | {
366 | if (_delegate != delegate)
367 | {
368 | _delegate = delegate;
369 | [self setNeedsDisplay];
370 | }
371 | }
372 |
373 | - (void)setCurrentPage:(NSInteger)page
374 | {
375 | _currentPage = [self clampedPageValue:page];
376 | [self setNeedsDisplay];
377 | }
378 |
379 | - (void)setNumberOfPages:(NSInteger)pages
380 | {
381 | if (_numberOfPages != pages)
382 | {
383 | _numberOfPages = pages;
384 | if (_currentPage >= pages)
385 | {
386 | _currentPage = pages - 1;
387 | }
388 | [self setNeedsDisplay];
389 | }
390 | }
391 |
392 | - (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
393 | {
394 | CGPoint point = [touch locationInView:self];
395 | BOOL forward = _vertical? (point.y > self.frame.size.height / 2): (point.x > self.frame.size.width / 2);
396 | _currentPage = [self clampedPageValue:_currentPage + (forward? 1: -1)];
397 | if (!_defersCurrentPageDisplay)
398 | {
399 | [self setNeedsDisplay];
400 | }
401 | [self sendActionsForControlEvents:UIControlEventValueChanged];
402 | [super endTrackingWithTouch:touch withEvent:event];
403 | }
404 |
405 | - (CGSize)sizeThatFits:(__unused CGSize)size
406 | {
407 | CGSize dotSize = [self sizeForNumberOfPages:_numberOfPages];
408 | if (_selectedDotSize)
409 | {
410 | CGFloat width = (_selectedDotSize - _dotSize);
411 | CGFloat height = MAX(36, MAX(_dotSize, _selectedDotSize));
412 | dotSize.width = _vertical? height: dotSize.width + width;
413 | dotSize.height = _vertical? dotSize.height + width: height;
414 |
415 | }
416 | if ((_dotShadowColor && ![_dotShadowColor isEqual:[UIColor clearColor]]) ||
417 | (_selectedDotShadowColor && ![_selectedDotShadowColor isEqual:[UIColor clearColor]]))
418 | {
419 | dotSize.width += MAX(_dotShadowOffset.width, _selectedDotShadowOffset.width) * 2;
420 | dotSize.height += MAX(_dotShadowOffset.height, _selectedDotShadowOffset.height) * 2;
421 | dotSize.width += MAX(_dotShadowBlur, _selectedDotShadowBlur) * 2;
422 | dotSize.height += MAX(_dotShadowBlur, _selectedDotShadowBlur) * 2;
423 | }
424 | return dotSize;
425 | }
426 |
427 | - (CGSize)intrinsicContentSize
428 | {
429 | return [self sizeThatFits:self.bounds.size];
430 | }
431 |
432 | @end
433 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/IndicatorInfo.swift:
--------------------------------------------------------------------------------
1 | // IndicatorInfo.swift
2 | // XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip )
3 | //
4 | // Copyright (c) 2017 Xmartlabs ( http://xmartlabs.com )
5 | //
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 |
25 | import Foundation
26 |
27 | public struct IndicatorInfo {
28 |
29 | public var title: String?
30 | public var image: UIImage?
31 | public var highlightedImage: UIImage?
32 |
33 | public init(title: String?) {
34 | self.title = title
35 | }
36 |
37 | public init(image: UIImage?, highlightedImage: UIImage? = nil) {
38 | self.image = image
39 | self.highlightedImage = highlightedImage
40 | }
41 |
42 | public init(title: String?, image: UIImage?, highlightedImage: UIImage? = nil) {
43 | self.title = title
44 | self.image = image
45 | self.highlightedImage = highlightedImage
46 | }
47 |
48 | }
49 |
50 | extension IndicatorInfo : ExpressibleByStringLiteral {
51 |
52 | public init(stringLiteral value: String) {
53 | title = value
54 | }
55 |
56 | public init(extendedGraphemeClusterLiteral value: String) {
57 | title = value
58 | }
59 |
60 | public init(unicodeScalarLiteral value: String) {
61 | title = value
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/PagerTabStripBehaviour.swift:
--------------------------------------------------------------------------------
1 | // PagerTabStripOptions.swift
2 | // XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip )
3 | //
4 | // Copyright (c) 2017 Xmartlabs ( http://xmartlabs.com )
5 | //
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 |
25 | import Foundation
26 |
27 | public enum PagerTabStripBehaviour {
28 |
29 | case common(skipIntermediateViewControllers: Bool)
30 | case progressive(skipIntermediateViewControllers: Bool, elasticIndicatorLimit: Bool)
31 |
32 | public var skipIntermediateViewControllers: Bool {
33 | switch self {
34 | case .common(let skipIntermediateViewControllers):
35 | return skipIntermediateViewControllers
36 | case .progressive(let skipIntermediateViewControllers, _):
37 | return skipIntermediateViewControllers
38 | }
39 | }
40 |
41 | public var isProgressiveIndicator: Bool {
42 | switch self {
43 | case .common:
44 | return false
45 | case .progressive:
46 | return true
47 | }
48 | }
49 |
50 | public var isElasticIndicatorLimit: Bool {
51 | switch self {
52 | case .common:
53 | return false
54 | case .progressive(_, let elasticIndicatorLimit):
55 | return elasticIndicatorLimit
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/PagerTabStripError.swift:
--------------------------------------------------------------------------------
1 | // PagerTabStripError.swift
2 | // XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip )
3 | //
4 | // Copyright (c) 2017 Xmartlabs ( http://xmartlabs.com )
5 | //
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 |
25 | import Foundation
26 |
27 | public enum PagerTabStripError: Error {
28 |
29 | case viewControllerOutOfBounds
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/PagerTabStripViewController.swift:
--------------------------------------------------------------------------------
1 | // PagerTabStripViewController.swift
2 | // XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip )
3 | //
4 | // Copyright (c) 2017 Xmartlabs ( http://xmartlabs.com )
5 | //
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 |
25 | import Foundation
26 |
27 | // MARK: Protocols
28 |
29 | public protocol IndicatorInfoProvider {
30 |
31 | func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo
32 |
33 | }
34 |
35 | public protocol PagerTabStripDelegate: class {
36 |
37 | func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int)
38 | }
39 |
40 | public protocol PagerTabStripIsProgressiveDelegate: PagerTabStripDelegate {
41 |
42 | func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: CGFloat, indexWasChanged: Bool)
43 | }
44 |
45 | public protocol PagerTabStripDataSource: class {
46 |
47 | func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController]
48 | }
49 |
50 | // MARK: PagerTabStripViewController
51 |
52 | open class PagerTabStripViewController: UIViewController, UIScrollViewDelegate {
53 |
54 | @IBOutlet weak public var containerView: UIScrollView!
55 |
56 | open weak var delegate: PagerTabStripDelegate?
57 | open weak var datasource: PagerTabStripDataSource?
58 |
59 | open var pagerBehaviour = PagerTabStripBehaviour.progressive(skipIntermediateViewControllers: true, elasticIndicatorLimit: true)
60 |
61 | open private(set) var viewControllers = [UIViewController]()
62 | open private(set) var currentIndex = 0
63 | open private(set) var preCurrentIndex = 0 // used *only* to store the index to which move when the pager becomes visible
64 |
65 | open var pageWidth: CGFloat {
66 | return containerView.bounds.width
67 | }
68 |
69 | open var scrollPercentage: CGFloat {
70 | if swipeDirection != .right {
71 | let module = fmod(containerView.contentOffset.x, pageWidth)
72 | return module == 0.0 ? 1.0 : module / pageWidth
73 | }
74 | return 1 - fmod(containerView.contentOffset.x >= 0 ? containerView.contentOffset.x : pageWidth + containerView.contentOffset.x, pageWidth) / pageWidth
75 | }
76 |
77 | open var swipeDirection: SwipeDirection {
78 | if containerView.contentOffset.x > lastContentOffset {
79 | return .left
80 | } else if containerView.contentOffset.x < lastContentOffset {
81 | return .right
82 | }
83 | return .none
84 | }
85 |
86 | override open func viewDidLoad() {
87 | super.viewDidLoad()
88 | let conteinerViewAux = containerView ?? {
89 | let containerView = UIScrollView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height))
90 | containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
91 | return containerView
92 | }()
93 | containerView = conteinerViewAux
94 | if containerView.superview == nil {
95 | view.addSubview(containerView)
96 | }
97 | containerView.bounces = true
98 | containerView.alwaysBounceHorizontal = true
99 | containerView.alwaysBounceVertical = false
100 | containerView.scrollsToTop = false
101 | containerView.delegate = self
102 | containerView.showsVerticalScrollIndicator = false
103 | containerView.showsHorizontalScrollIndicator = false
104 | containerView.isPagingEnabled = true
105 | reloadViewControllers()
106 |
107 | let childController = viewControllers[currentIndex]
108 | addChildViewController(childController)
109 | childController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
110 | containerView.addSubview(childController.view)
111 | childController.didMove(toParentViewController: self)
112 | }
113 |
114 | open override func viewWillAppear(_ animated: Bool) {
115 | super.viewWillAppear(animated)
116 | isViewAppearing = true
117 | childViewControllers.forEach { $0.beginAppearanceTransition(true, animated: animated) }
118 | }
119 |
120 | override open func viewDidAppear(_ animated: Bool) {
121 | super.viewDidAppear(animated)
122 | lastSize = containerView.bounds.size
123 | updateIfNeeded()
124 | let needToUpdateCurrentChild = preCurrentIndex != currentIndex
125 | if needToUpdateCurrentChild {
126 | moveToViewController(at: preCurrentIndex)
127 | }
128 | isViewAppearing = false
129 | childViewControllers.forEach { $0.endAppearanceTransition() }
130 | }
131 |
132 | open override func viewWillDisappear(_ animated: Bool) {
133 | super.viewWillDisappear(animated)
134 | childViewControllers.forEach { $0.beginAppearanceTransition(false, animated: animated) }
135 | }
136 |
137 | open override func viewDidDisappear(_ animated: Bool) {
138 | super.viewDidDisappear(animated)
139 | childViewControllers.forEach { $0.endAppearanceTransition() }
140 | }
141 |
142 | override open func viewDidLayoutSubviews() {
143 | super.viewDidLayoutSubviews()
144 | updateIfNeeded()
145 | }
146 |
147 | open override var shouldAutomaticallyForwardAppearanceMethods: Bool {
148 | return false
149 | }
150 |
151 | open func moveToViewController(at index: Int, animated: Bool = true) {
152 | guard isViewLoaded && view.window != nil && currentIndex != index else {
153 | preCurrentIndex = index
154 | return
155 | }
156 |
157 | if animated && pagerBehaviour.skipIntermediateViewControllers && abs(currentIndex - index) > 1 {
158 | var tmpViewControllers = viewControllers
159 | let currentChildVC = viewControllers[currentIndex]
160 | let fromIndex = currentIndex < index ? index - 1 : index + 1
161 | let fromChildVC = viewControllers[fromIndex]
162 | tmpViewControllers[currentIndex] = fromChildVC
163 | tmpViewControllers[fromIndex] = currentChildVC
164 | pagerTabStripChildViewControllersForScrolling = tmpViewControllers
165 | containerView.setContentOffset(CGPoint(x: pageOffsetForChild(at: fromIndex), y: 0), animated: false)
166 | (navigationController?.view ?? view).isUserInteractionEnabled = !animated
167 | containerView.setContentOffset(CGPoint(x: pageOffsetForChild(at: index), y: 0), animated: true)
168 | } else {
169 | (navigationController?.view ?? view).isUserInteractionEnabled = !animated
170 | containerView.setContentOffset(CGPoint(x: pageOffsetForChild(at: index), y: 0), animated: animated)
171 | }
172 | }
173 |
174 | open func moveTo(viewController: UIViewController, animated: Bool = true) {
175 | moveToViewController(at: viewControllers.index(of: viewController)!, animated: animated)
176 | }
177 |
178 | // MARK: - PagerTabStripDataSource
179 |
180 | open func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
181 | assertionFailure("Sub-class must implement the PagerTabStripDataSource viewControllers(for:) method")
182 | return []
183 | }
184 |
185 | // MARK: - Helpers
186 |
187 | open func updateIfNeeded() {
188 | if isViewLoaded && !lastSize.equalTo(containerView.bounds.size) {
189 | updateContent()
190 | }
191 | }
192 |
193 | open func canMoveTo(index: Int) -> Bool {
194 | return currentIndex != index && viewControllers.count > index
195 | }
196 |
197 | open func pageOffsetForChild(at index: Int) -> CGFloat {
198 | return CGFloat(index) * containerView.bounds.width
199 | }
200 |
201 | open func offsetForChild(at index: Int) -> CGFloat {
202 | return (CGFloat(index) * containerView.bounds.width) + ((containerView.bounds.width - view.bounds.width) * 0.5)
203 | }
204 |
205 | open func offsetForChild(viewController: UIViewController) throws -> CGFloat {
206 | guard let index = viewControllers.index(of: viewController) else {
207 | throw PagerTabStripError.viewControllerOutOfBounds
208 | }
209 | return offsetForChild(at: index)
210 | }
211 |
212 | open func pageFor(contentOffset: CGFloat) -> Int {
213 | let result = virtualPageFor(contentOffset: contentOffset)
214 | return pageFor(virtualPage: result)
215 | }
216 |
217 | open func virtualPageFor(contentOffset: CGFloat) -> Int {
218 | return Int((contentOffset + 1.5 * pageWidth) / pageWidth) - 1
219 | }
220 |
221 | open func pageFor(virtualPage: Int) -> Int {
222 | if virtualPage < 0 {
223 | return 0
224 | }
225 | if virtualPage > viewControllers.count - 1 {
226 | return viewControllers.count - 1
227 | }
228 | return virtualPage
229 | }
230 |
231 | open func updateContent() {
232 | if lastSize.width != containerView.bounds.size.width {
233 | lastSize = containerView.bounds.size
234 | containerView.contentOffset = CGPoint(x: pageOffsetForChild(at: currentIndex), y: 0)
235 | }
236 | lastSize = containerView.bounds.size
237 |
238 | let pagerViewControllers = pagerTabStripChildViewControllersForScrolling ?? viewControllers
239 | containerView.contentSize = CGSize(width: containerView.bounds.width * CGFloat(pagerViewControllers.count), height: containerView.contentSize.height)
240 |
241 | for (index, childController) in pagerViewControllers.enumerated() {
242 | let pageOffsetForChild = self.pageOffsetForChild(at: index)
243 | if fabs(containerView.contentOffset.x - pageOffsetForChild) < containerView.bounds.width {
244 | if childController.parent != nil {
245 | childController.view.frame = CGRect(x: offsetForChild(at: index), y: 0, width: view.bounds.width, height: containerView.bounds.height)
246 | childController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
247 | } else {
248 | childController.beginAppearanceTransition(true, animated: false)
249 | addChildViewController(childController)
250 | childController.view.frame = CGRect(x: offsetForChild(at: index), y: 0, width: view.bounds.width, height: containerView.bounds.height)
251 | childController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
252 | containerView.addSubview(childController.view)
253 | childController.didMove(toParentViewController: self)
254 | childController.endAppearanceTransition()
255 | }
256 | } else {
257 | if childController.parent != nil {
258 | childController.beginAppearanceTransition(false, animated: false)
259 | childController.willMove(toParentViewController: nil)
260 | childController.view.removeFromSuperview()
261 | childController.removeFromParentViewController()
262 | childController.endAppearanceTransition()
263 | }
264 | }
265 | }
266 |
267 | let oldCurrentIndex = currentIndex
268 | let virtualPage = virtualPageFor(contentOffset: containerView.contentOffset.x)
269 | let newCurrentIndex = pageFor(virtualPage: virtualPage)
270 | currentIndex = newCurrentIndex
271 | preCurrentIndex = currentIndex
272 | let changeCurrentIndex = newCurrentIndex != oldCurrentIndex
273 |
274 | if let progressiveDeledate = self as? PagerTabStripIsProgressiveDelegate, pagerBehaviour.isProgressiveIndicator {
275 |
276 | let (fromIndex, toIndex, scrollPercentage) = progressiveIndicatorData(virtualPage)
277 | progressiveDeledate.updateIndicator(for: self, fromIndex: fromIndex, toIndex: toIndex, withProgressPercentage: scrollPercentage, indexWasChanged: changeCurrentIndex)
278 | } else {
279 | delegate?.updateIndicator(for: self, fromIndex: min(oldCurrentIndex, pagerViewControllers.count - 1), toIndex: newCurrentIndex)
280 | }
281 | }
282 |
283 | open func reloadPagerTabStripView() {
284 | guard isViewLoaded else { return }
285 | for childController in viewControllers where childController.parent != nil {
286 | childController.beginAppearanceTransition(false, animated: false)
287 | childController.willMove(toParentViewController: nil)
288 | childController.view.removeFromSuperview()
289 | childController.removeFromParentViewController()
290 | childController.endAppearanceTransition()
291 | }
292 | reloadViewControllers()
293 | containerView.contentSize = CGSize(width: containerView.bounds.width * CGFloat(viewControllers.count), height: containerView.contentSize.height)
294 | if currentIndex >= viewControllers.count {
295 | currentIndex = viewControllers.count - 1
296 | }
297 | preCurrentIndex = currentIndex
298 | containerView.contentOffset = CGPoint(x: pageOffsetForChild(at: currentIndex), y: 0)
299 | updateContent()
300 | }
301 |
302 | // MARK: - UIScrollDelegate
303 |
304 | open func scrollViewDidScroll(_ scrollView: UIScrollView) {
305 | if containerView == scrollView {
306 | updateContent()
307 | lastContentOffset = scrollView.contentOffset.x
308 | }
309 | }
310 |
311 | open func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
312 | if containerView == scrollView {
313 | lastPageNumber = pageFor(contentOffset: scrollView.contentOffset.x)
314 | }
315 | }
316 |
317 | open func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
318 | if containerView == scrollView {
319 | pagerTabStripChildViewControllersForScrolling = nil
320 | (navigationController?.view ?? view).isUserInteractionEnabled = true
321 | updateContent()
322 | }
323 | }
324 |
325 | // MARK: - Orientation
326 |
327 | open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
328 | super.viewWillTransition(to: size, with: coordinator)
329 | isViewRotating = true
330 | pageBeforeRotate = currentIndex
331 | coordinator.animate(alongsideTransition: nil) { [weak self] _ in
332 | guard let me = self else { return }
333 | me.isViewRotating = false
334 | me.currentIndex = me.pageBeforeRotate
335 | me.preCurrentIndex = me.currentIndex
336 | me.updateIfNeeded()
337 | }
338 | }
339 |
340 | // MARK: Private
341 |
342 | private func progressiveIndicatorData(_ virtualPage: Int) -> (Int, Int, CGFloat) {
343 | let count = viewControllers.count
344 | var fromIndex = currentIndex
345 | var toIndex = currentIndex
346 | let direction = swipeDirection
347 |
348 | if direction == .left {
349 | if virtualPage > count - 1 {
350 | fromIndex = count - 1
351 | toIndex = count
352 | } else {
353 | if self.scrollPercentage >= 0.5 {
354 | fromIndex = max(toIndex - 1, 0)
355 | } else {
356 | toIndex = fromIndex + 1
357 | }
358 | }
359 | } else if direction == .right {
360 | if virtualPage < 0 {
361 | fromIndex = 0
362 | toIndex = -1
363 | } else {
364 | if self.scrollPercentage > 0.5 {
365 | fromIndex = min(toIndex + 1, count - 1)
366 | } else {
367 | toIndex = fromIndex - 1
368 | }
369 | }
370 | }
371 | let scrollPercentage = pagerBehaviour.isElasticIndicatorLimit ? self.scrollPercentage : ((toIndex < 0 || toIndex >= count) ? 0.0 : self.scrollPercentage)
372 | return (fromIndex, toIndex, scrollPercentage)
373 | }
374 |
375 | private func reloadViewControllers() {
376 | guard let dataSource = datasource else {
377 | fatalError("dataSource must not be nil")
378 | }
379 | viewControllers = dataSource.viewControllers(for: self)
380 | // viewControllers
381 | guard !viewControllers.isEmpty else {
382 | fatalError("viewControllers(for:) should provide at least one child view controller")
383 | }
384 | viewControllers.forEach { if !($0 is IndicatorInfoProvider) { fatalError("Every view controller provided by PagerTabStripDataSource's viewControllers(for:) method must conform to InfoProvider") }}
385 |
386 | }
387 |
388 | private var pagerTabStripChildViewControllersForScrolling: [UIViewController]?
389 | private var lastPageNumber = 0
390 | private var lastContentOffset: CGFloat = 0.0
391 | private var pageBeforeRotate = 0
392 | private var lastSize = CGSize(width: 0, height: 0)
393 | internal var isViewRotating = false
394 | internal var isViewAppearing = false
395 |
396 | }
397 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/SegmentedPagerTabStripViewController.swift:
--------------------------------------------------------------------------------
1 | // SegmentedPagerTabStripViewController.swift
2 | // XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip )
3 | //
4 | // Copyright (c) 2017 Xmartlabs ( http://xmartlabs.com )
5 | //
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 |
25 | import Foundation
26 |
27 | public struct SegmentedPagerTabStripSettings {
28 |
29 | public struct Style {
30 | public var segmentedControlColor: UIColor?
31 | }
32 |
33 | public var style = Style()
34 | }
35 |
36 | open class SegmentedPagerTabStripViewController: PagerTabStripViewController, PagerTabStripDataSource, PagerTabStripDelegate {
37 |
38 | @IBOutlet weak public var segmentedControl: UISegmentedControl!
39 |
40 | open var settings = SegmentedPagerTabStripSettings()
41 |
42 | public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
43 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
44 | pagerBehaviour = PagerTabStripBehaviour.common(skipIntermediateViewControllers: true)
45 | delegate = self
46 | datasource = self
47 | }
48 |
49 | required public init?(coder aDecoder: NSCoder) {
50 | super.init(coder: aDecoder)
51 | pagerBehaviour = PagerTabStripBehaviour.common(skipIntermediateViewControllers: true)
52 | delegate = self
53 | datasource = self
54 | }
55 |
56 | private(set) var shouldUpdateSegmentedControl = true
57 |
58 | open override func viewDidLoad() {
59 | super.viewDidLoad()
60 | segmentedControl = segmentedControl ?? UISegmentedControl()
61 | if segmentedControl.superview == nil {
62 | navigationItem.titleView = segmentedControl
63 | }
64 | segmentedControl.tintColor = settings.style.segmentedControlColor ?? segmentedControl.tintColor
65 | segmentedControl.addTarget(self, action: #selector(SegmentedPagerTabStripViewController.segmentedControlChanged(_:)), for: .valueChanged)
66 | reloadSegmentedControl()
67 | }
68 |
69 | open override func reloadPagerTabStripView() {
70 | super.reloadPagerTabStripView()
71 | if isViewLoaded {
72 | reloadSegmentedControl()
73 | }
74 | }
75 |
76 | func reloadSegmentedControl() {
77 | segmentedControl.removeAllSegments()
78 | for (index, item) in viewControllers.enumerated() {
79 | let child = item as! IndicatorInfoProvider // swiftlint:disable:this force_cast
80 | if let image = child.indicatorInfo(for: self).image {
81 | segmentedControl.insertSegment(with: image, at: index, animated: false)
82 | } else {
83 | segmentedControl.insertSegment(withTitle: child.indicatorInfo(for: self).title, at: index, animated: false)
84 | }
85 | }
86 | segmentedControl.selectedSegmentIndex = currentIndex
87 | }
88 |
89 | @objc func segmentedControlChanged(_ sender: UISegmentedControl) {
90 | let index = sender.selectedSegmentIndex
91 | updateIndicator(for: self, fromIndex: currentIndex, toIndex: index)
92 | shouldUpdateSegmentedControl = false
93 | moveToViewController(at: index)
94 | }
95 |
96 | // MARK: - PagerTabStripDelegate
97 |
98 | open func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int) {
99 | if shouldUpdateSegmentedControl {
100 | segmentedControl.selectedSegmentIndex = toIndex
101 | }
102 | }
103 |
104 | // MARK: - UIScrollViewDelegate
105 |
106 | open override func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
107 | super.scrollViewDidEndScrollingAnimation(scrollView)
108 | shouldUpdateSegmentedControl = true
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/SwipeDirection.swift:
--------------------------------------------------------------------------------
1 | // SwipeDirection.swift
2 | // XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip )
3 | //
4 | // Copyright (c) 2017 Xmartlabs ( http://xmartlabs.com )
5 | //
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 |
25 | import Foundation
26 |
27 | public enum SwipeDirection {
28 | case left
29 | case right
30 | case none
31 | }
32 |
--------------------------------------------------------------------------------
/Pods/XLPagerTabStrip/Sources/TwitterPagerTabStripViewController.swift:
--------------------------------------------------------------------------------
1 | // TwitterPagerTabStripViewController.swift
2 | // XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip )
3 | //
4 | // Copyright (c) 2017 Xmartlabs ( http://xmartlabs.com )
5 | //
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in
15 | // all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | // THE SOFTWARE.
24 |
25 | import Foundation
26 |
27 | public struct TwitterPagerTabStripSettings {
28 |
29 | public struct Style {
30 | public var dotColor = UIColor(white: 1, alpha: 0.4)
31 | public var selectedDotColor = UIColor.white
32 | public var portraitTitleFont = UIFont.systemFont(ofSize: 18)
33 | public var landscapeTitleFont = UIFont.systemFont(ofSize: 15)
34 | public var titleColor = UIColor.white
35 | }
36 |
37 | public var style = Style()
38 | }
39 |
40 | open class TwitterPagerTabStripViewController: PagerTabStripViewController, PagerTabStripDataSource, PagerTabStripIsProgressiveDelegate {
41 |
42 | open var settings = TwitterPagerTabStripSettings()
43 |
44 | public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
45 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
46 | pagerBehaviour = .progressive(skipIntermediateViewControllers: true, elasticIndicatorLimit: true)
47 | delegate = self
48 | datasource = self
49 | }
50 |
51 | required public init?(coder aDecoder: NSCoder) {
52 | super.init(coder: aDecoder)
53 | pagerBehaviour = .progressive(skipIntermediateViewControllers: true, elasticIndicatorLimit: true)
54 | delegate = self
55 | datasource = self
56 | }
57 |
58 | open override func viewDidLoad() {
59 | super.viewDidLoad()
60 |
61 | if titleView.superview == nil {
62 | navigationItem.titleView = titleView
63 | }
64 |
65 | // keep watching the frame of titleView
66 | titleView.addObserver(self, forKeyPath: "frame", options: [.new, .old], context: nil)
67 |
68 | guard let navigationController = navigationController else {
69 | fatalError("TwitterPagerTabStripViewController should be embedded in a UINavigationController")
70 | }
71 | titleView.frame = CGRect(x: 0, y: 0, width: navigationController.navigationBar.frame.width, height: navigationController.navigationBar.frame.height)
72 | titleView.addSubview(titleScrollView)
73 | titleView.addSubview(pageControl)
74 | reloadNavigationViewItems()
75 | }
76 |
77 | open override func reloadPagerTabStripView() {
78 | super.reloadPagerTabStripView()
79 | guard isViewLoaded else { return }
80 |
81 | reloadNavigationViewItems()
82 | setNavigationViewItemsPosition(updateAlpha: true)
83 | }
84 |
85 | // MARK: - PagerTabStripDelegate
86 |
87 | open func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: CGFloat, indexWasChanged: Bool) {
88 |
89 | // move indicator scroll view
90 | guard let distance = distanceValue else { return }
91 | var xOffset: CGFloat = 0
92 | if fromIndex < toIndex {
93 | xOffset = distance * CGFloat(fromIndex) + distance * progressPercentage
94 | } else if fromIndex > toIndex {
95 | xOffset = distance * CGFloat(fromIndex) - distance * progressPercentage
96 | } else {
97 | xOffset = distance * CGFloat(fromIndex)
98 | }
99 |
100 | titleScrollView.contentOffset = CGPoint(x: xOffset, y: 0)
101 |
102 | // update alpha of titles
103 | setAlphaWith(offset: xOffset, andDistance: distance)
104 |
105 | // update page control page
106 | pageControl.currentPage = currentIndex
107 | }
108 |
109 | open func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int) {
110 | fatalError()
111 | }
112 |
113 | open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
114 | guard object as AnyObject === titleView && keyPath == "frame" && change?[NSKeyValueChangeKey.kindKey] as? UInt == NSKeyValueChange.setting.rawValue else { return }
115 |
116 | let oldRect = (change![NSKeyValueChangeKey.oldKey]! as AnyObject).cgRectValue
117 | let newRect = (change![NSKeyValueChangeKey.oldKey]! as AnyObject).cgRectValue
118 | if (oldRect?.equalTo(newRect!))! {
119 | titleScrollView.frame = CGRect(x: 0, y: 0, width: titleView.frame.width, height: titleScrollView.frame.height)
120 | setNavigationViewItemsPosition(updateAlpha: true)
121 | }
122 | }
123 |
124 | deinit {
125 | if isViewLoaded {
126 | titleView.removeObserver(self, forKeyPath: "frame")
127 | }
128 | }
129 |
130 | open override func viewDidLayoutSubviews() {
131 | super.viewDidLayoutSubviews()
132 | setNavigationViewItemsPosition(updateAlpha: false)
133 | }
134 |
135 | // MARK: - Helpers
136 |
137 | private lazy var titleView: UIView = {
138 | let navigationView = UIView()
139 | navigationView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
140 | return navigationView
141 | }()
142 |
143 | private lazy var titleScrollView: UIScrollView = { [unowned self] in
144 | let titleScrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 44))
145 | titleScrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
146 | titleScrollView.bounces = true
147 | titleScrollView.scrollsToTop = false
148 | titleScrollView.delegate = self
149 | titleScrollView.showsVerticalScrollIndicator = false
150 | titleScrollView.showsHorizontalScrollIndicator = false
151 | titleScrollView.isPagingEnabled = true
152 | titleScrollView.isUserInteractionEnabled = false
153 | titleScrollView.alwaysBounceHorizontal = true
154 | titleScrollView.alwaysBounceVertical = false
155 | return titleScrollView
156 | }()
157 |
158 | private lazy var pageControl: FXPageControl = { [unowned self] in
159 | let pageControl = FXPageControl()
160 | pageControl.backgroundColor = .clear
161 | pageControl.dotSize = 3.8
162 | pageControl.dotSpacing = 4.0
163 | pageControl.dotColor = self.settings.style.dotColor
164 | pageControl.selectedDotColor = self.settings.style.selectedDotColor
165 | pageControl.isUserInteractionEnabled = false
166 | return pageControl
167 | }()
168 |
169 | private var childTitleLabels = [UILabel]()
170 |
171 | private func reloadNavigationViewItems() {
172 | // remove all child view controller header labels
173 | childTitleLabels.forEach { $0.removeFromSuperview() }
174 | childTitleLabels.removeAll()
175 | for (index, item) in viewControllers.enumerated() {
176 | let child = item as! IndicatorInfoProvider // swiftlint:disable:this force_cast
177 | let indicatorInfo = child.indicatorInfo(for: self)
178 | let navTitleLabel: UILabel = {
179 | let label = UILabel()
180 | label.text = indicatorInfo.title
181 | label.font = UIApplication.shared.statusBarOrientation.isPortrait ? settings.style.portraitTitleFont : settings.style.landscapeTitleFont
182 | label.textColor = settings.style.titleColor
183 | label.alpha = 0
184 | return label
185 | }()
186 | navTitleLabel.alpha = currentIndex == index ? 1 : 0
187 | navTitleLabel.textColor = settings.style.titleColor
188 | titleScrollView.addSubview(navTitleLabel)
189 | childTitleLabels.append(navTitleLabel)
190 | }
191 | }
192 |
193 | private func setNavigationViewItemsPosition(updateAlpha: Bool) {
194 | guard let distance = distanceValue else { return }
195 | let isPortrait = UIApplication.shared.statusBarOrientation.isPortrait
196 | let navBarHeight: CGFloat = navigationController!.navigationBar.frame.size.height
197 | for (index, label) in childTitleLabels.enumerated() {
198 | if updateAlpha {
199 | label.alpha = currentIndex == index ? 1 : 0
200 | }
201 | label.font = isPortrait ? settings.style.portraitTitleFont : settings.style.landscapeTitleFont
202 | let viewSize = label.intrinsicContentSize
203 | let originX = distance - viewSize.width/2 + CGFloat(index) * distance
204 | let originY = (CGFloat(navBarHeight) - viewSize.height) / 2
205 | label.frame = CGRect(x: originX, y: originY - 2, width: viewSize.width, height: viewSize.height)
206 | label.tag = index
207 | }
208 |
209 | let xOffset = distance * CGFloat(currentIndex)
210 | titleScrollView.contentOffset = CGPoint(x: xOffset, y: 0)
211 |
212 | pageControl.numberOfPages = childTitleLabels.count
213 | pageControl.currentPage = currentIndex
214 | let viewSize = pageControl.sizeForNumber(ofPages: childTitleLabels.count)
215 | let originX = distance - viewSize.width / 2
216 | pageControl.frame = CGRect(x: originX, y: navBarHeight - 10, width: viewSize.width, height: viewSize.height)
217 | }
218 |
219 | private func setAlphaWith(offset: CGFloat, andDistance distance: CGFloat) {
220 | for (index, label) in childTitleLabels.enumerated() {
221 | label.alpha = {
222 | if offset < distance * CGFloat(index) {
223 | return (offset - distance * CGFloat(index - 1)) / distance
224 | } else {
225 | return 1 - ((offset - distance * CGFloat(index)) / distance)
226 | }
227 | }()
228 | }
229 | }
230 |
231 | private var distanceValue: CGFloat? {
232 | return navigationController.map { $0.navigationBar.convert($0.navigationBar.center, to: titleView) }?.x
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ParallaxHeader
2 | A sample iOS implementation [XLPagerTabStrip](https://github.com/xmartlabs/XLPagerTabStrip) with header view
3 |
4 | ## Notes
5 | References to [this article](https://sexyswift.wordpress.com/2016/08/25/swift-parallax-scrolling-with-header-sticking-at-top-nested-uiscrollview/)
6 | or you can checkout [here](https://github.com/bibekdari/DParallexScrolling/blob/master/Parallex%20Scrolling/ViewController.swift)
7 |
8 | ## Video Walkthrough
9 |
10 | Here's a walkthrough of implemented user stories:
11 |
12 | 
13 |
14 | GIF created with [LiceCap](http://www.cockos.com/licecap/).
15 |
16 | ## License
17 |
18 | Copyright [2018] [Tri Ngo Minh]
19 |
20 | Licensed under the Apache License, Version 2.0 (the "License");
21 | you may not use this file except in compliance with the License.
22 | You may obtain a copy of the License at
23 |
24 | http://www.apache.org/licenses/LICENSE-2.0
25 |
26 | Unless required by applicable law or agreed to in writing, software
27 | distributed under the License is distributed on an "AS IS" BASIS,
28 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29 | See the License for the specific language governing permissions and
30 | limitations under the License.
--------------------------------------------------------------------------------
/walkthrough.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngominhtrint/ParallaxHeader/ad2d06ce4c25488110eccf79304515ee3a00435e/walkthrough.gif
--------------------------------------------------------------------------------