added iOS source code
[wl-app.git] / iOS / Pods / FolioReaderKit / Source / ScrollScrubber.swift
1 //
2 //  ScrollScrubber.swift
3 //  FolioReaderKit
4 //
5 //  Created by Heberti Almeida on 7/14/16.
6 //  Copyright © 2016 FolioReader. All rights reserved.
7 //
8
9 import UIKit
10
11 func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
12     switch (lhs, rhs) {
13     case let (l?, r?):
14         return l < r
15     case (nil, _?):
16         return true
17     default:
18         return false
19     }
20 }
21
22 func > <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
23     switch (lhs, rhs) {
24     case let (l?, r?):
25         return l > r
26     default:
27         return rhs < lhs
28     }
29 }
30
31 enum ScrollType: Int {
32     case page
33     // `chapter` is only for the collection view if vertical with horizontal content is used
34     case chapter
35 }
36
37 enum ScrollDirection: Int {
38     case none
39     case right
40     case left
41     case up
42     case down
43
44     init() {
45         self = .none
46     }
47 }
48
49 class ScrollScrubber: NSObject, UIScrollViewDelegate {
50     weak var delegate: FolioReaderCenter?
51     var showSpeed = 0.6
52     var hideSpeed = 0.6
53     var hideDelay = 1.0
54
55     var visible = false
56     var usingSlider = false
57     var slider: UISlider!
58     var hideTimer: Timer!
59     var scrollStart: CGFloat!
60     var scrollDelta: CGFloat!
61     var scrollDeltaTimer: Timer!
62
63     fileprivate weak var readerContainer: FolioReaderContainer?
64
65     fileprivate var readerConfig: FolioReaderConfig {
66         guard let readerContainer = readerContainer else { return FolioReaderConfig() }
67         return readerContainer.readerConfig
68     }
69
70     fileprivate var folioReader: FolioReader {
71         guard let readerContainer = readerContainer else { return FolioReader() }
72         return readerContainer.folioReader
73     }
74
75     var frame: CGRect {
76         didSet {
77             self.slider.frame = frame
78         }
79     }
80
81     init(frame:CGRect, withReaderContainer readerContainer: FolioReaderContainer) {
82         self.frame = frame
83         self.readerContainer = readerContainer
84
85         super.init()
86
87         slider = UISlider()
88         slider.layer.anchorPoint = CGPoint(x: 0, y: 0)
89         slider.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
90         slider.alpha = 0
91         self.reloadColors()
92
93         // less obtrusive knob and fixes jump: http://stackoverflow.com/a/22301039/484780
94         let thumbImg = UIImage(readerImageNamed: "knob")
95         let thumbImgColor = thumbImg?.imageTintColor(readerConfig.tintColor)?.withRenderingMode(.alwaysOriginal)
96         slider.setThumbImage(thumbImgColor, for: UIControlState())
97         slider.setThumbImage(thumbImgColor, for: .selected)
98         slider.setThumbImage(thumbImgColor, for: .highlighted)
99
100         slider.addTarget(self, action: #selector(ScrollScrubber.sliderChange(_:)), for: .valueChanged)
101         slider.addTarget(self, action: #selector(ScrollScrubber.sliderTouchDown(_:)), for: .touchDown)
102         slider.addTarget(self, action: #selector(ScrollScrubber.sliderTouchUp(_:)), for: .touchUpInside)
103         slider.addTarget(self, action: #selector(ScrollScrubber.sliderTouchUp(_:)), for: .touchUpOutside)
104     }
105
106     func reloadColors() {
107         slider.minimumTrackTintColor = readerConfig.tintColor
108         slider.maximumTrackTintColor = folioReader.isNight(readerConfig.nightModeSeparatorColor, readerConfig.menuSeparatorColor)
109     }
110
111     // MARK: - slider events
112
113     @objc func sliderTouchDown(_ slider:UISlider) {
114         usingSlider = true
115         show()
116     }
117
118     @objc func sliderTouchUp(_ slider:UISlider) {
119         usingSlider = false
120         hideAfterDelay()
121     }
122
123     @objc func sliderChange(_ slider:UISlider) {
124         let movePosition = (height() * CGFloat(slider.value))
125         let offset = readerConfig.isDirection(CGPoint(x: 0, y: movePosition), CGPoint(x: movePosition, y: 0), CGPoint(x: 0, y: movePosition))
126         scrollView()?.setContentOffset(offset, animated: false)
127     }
128
129     // MARK: - show / hide
130
131     func show() {
132         cancelHide()
133
134         visible = true
135
136         if slider.alpha <= 0 {
137             UIView.animate(withDuration: showSpeed, animations: {
138
139                 self.slider.alpha = 1
140
141             }, completion: { (Bool) -> Void in
142                 self.hideAfterDelay()
143             })
144         } else {
145             slider.alpha = 1
146             if usingSlider == false {
147                 hideAfterDelay()
148             }
149         }
150     }
151
152
153     @objc func hide() {
154         visible = false
155         resetScrollDelta()
156         UIView.animate(withDuration: hideSpeed, animations: {
157             self.slider.alpha = 0
158         })
159     }
160
161     func hideAfterDelay() {
162         cancelHide()
163         hideTimer = Timer.scheduledTimer(timeInterval: hideDelay, target: self, selector: #selector(ScrollScrubber.hide), userInfo: nil, repeats: false)
164     }
165
166     func cancelHide() {
167
168         if hideTimer != nil {
169             hideTimer.invalidate()
170             hideTimer = nil
171         }
172
173         if visible == false {
174             slider.layer.removeAllAnimations()
175         }
176
177         visible = true
178     }
179
180     func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
181
182         if scrollDeltaTimer != nil {
183             scrollDeltaTimer.invalidate()
184             scrollDeltaTimer = nil
185         }
186
187         if scrollStart == nil {
188             scrollStart = scrollView.contentOffset.forDirection(withConfiguration: readerConfig)
189         }
190     }
191
192     func scrollViewDidScroll(_ scrollView: UIScrollView) {
193         guard (readerConfig.scrollDirection == .vertical ||
194             readerConfig.scrollDirection == .defaultVertical ||
195             readerConfig.scrollDirection == .horizontalWithVerticalContent) else {
196                 return
197         }
198
199         if visible && usingSlider == false {
200             setSliderVal()
201         }
202
203         if (slider.alpha > 0) {
204             self.show()
205         } else if delegate?.currentPage != nil && scrollStart != nil {
206             scrollDelta = scrollView.contentOffset.forDirection(withConfiguration: readerConfig) - scrollStart
207
208             guard let pageHeight = folioReader.readerCenter?.pageHeight,
209                 (scrollDeltaTimer == nil && scrollDelta > (pageHeight * 0.2 ) || (scrollDelta * -1) > (pageHeight * 0.2)) else {
210                     return
211             }
212
213             self.show()
214             self.resetScrollDelta()
215         }
216     }
217
218     func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
219         resetScrollDelta()
220     }
221
222     func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
223         scrollDeltaTimer = Timer(timeInterval:0.5, target: self, selector: #selector(ScrollScrubber.resetScrollDelta), userInfo: nil, repeats: false)
224         RunLoop.current.add(scrollDeltaTimer, forMode: RunLoopMode.commonModes)
225     }
226
227     @objc func resetScrollDelta() {
228         if scrollDeltaTimer != nil {
229             scrollDeltaTimer.invalidate()
230             scrollDeltaTimer = nil
231         }
232
233         scrollStart = (scrollView()?.contentOffset.forDirection(withConfiguration: readerConfig) ?? 0)
234         scrollDelta = 0
235     }
236
237     func setSliderVal() {
238         slider.value = Float(scrollTop() / height())
239     }
240
241     // MARK: - utility methods
242
243     fileprivate func scrollView() -> UIScrollView? {
244         return delegate?.currentPage?.webView?.scrollView
245     }
246
247     fileprivate func height() -> CGFloat {
248         guard let currentPage = delegate?.currentPage,
249             let pageHeight = folioReader.readerCenter?.pageHeight,
250             let webView = currentPage.webView else {
251                 return 0
252         }
253
254         return webView.scrollView.contentSize.height - pageHeight + 44
255     }
256     
257     fileprivate func scrollTop() -> CGFloat {
258         guard let currentPage = delegate?.currentPage, let webView = currentPage.webView else {
259             return 0
260         }
261         return webView.scrollView.contentOffset.forDirection(withConfiguration: readerConfig)
262     }
263 }