2 // ScrollScrubber.swift
5 // Created by Heberti Almeida on 7/14/16.
6 // Copyright © 2016 FolioReader. All rights reserved.
11 func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
22 func > <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
31 enum ScrollType: Int {
33 // `chapter` is only for the collection view if vertical with horizontal content is used
37 enum ScrollDirection: Int {
49 class ScrollScrubber: NSObject, UIScrollViewDelegate {
50 weak var delegate: FolioReaderCenter?
56 var usingSlider = false
59 var scrollStart: CGFloat!
60 var scrollDelta: CGFloat!
61 var scrollDeltaTimer: Timer!
63 fileprivate weak var readerContainer: FolioReaderContainer?
65 fileprivate var readerConfig: FolioReaderConfig {
66 guard let readerContainer = readerContainer else { return FolioReaderConfig() }
67 return readerContainer.readerConfig
70 fileprivate var folioReader: FolioReader {
71 guard let readerContainer = readerContainer else { return FolioReader() }
72 return readerContainer.folioReader
77 self.slider.frame = frame
81 init(frame:CGRect, withReaderContainer readerContainer: FolioReaderContainer) {
83 self.readerContainer = readerContainer
88 slider.layer.anchorPoint = CGPoint(x: 0, y: 0)
89 slider.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
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)
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)
106 func reloadColors() {
107 slider.minimumTrackTintColor = readerConfig.tintColor
108 slider.maximumTrackTintColor = folioReader.isNight(readerConfig.nightModeSeparatorColor, readerConfig.menuSeparatorColor)
111 // MARK: - slider events
113 @objc func sliderTouchDown(_ slider:UISlider) {
118 @objc func sliderTouchUp(_ slider:UISlider) {
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)
129 // MARK: - show / hide
136 if slider.alpha <= 0 {
137 UIView.animate(withDuration: showSpeed, animations: {
139 self.slider.alpha = 1
141 }, completion: { (Bool) -> Void in
142 self.hideAfterDelay()
146 if usingSlider == false {
156 UIView.animate(withDuration: hideSpeed, animations: {
157 self.slider.alpha = 0
161 func hideAfterDelay() {
163 hideTimer = Timer.scheduledTimer(timeInterval: hideDelay, target: self, selector: #selector(ScrollScrubber.hide), userInfo: nil, repeats: false)
168 if hideTimer != nil {
169 hideTimer.invalidate()
173 if visible == false {
174 slider.layer.removeAllAnimations()
180 func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
182 if scrollDeltaTimer != nil {
183 scrollDeltaTimer.invalidate()
184 scrollDeltaTimer = nil
187 if scrollStart == nil {
188 scrollStart = scrollView.contentOffset.forDirection(withConfiguration: readerConfig)
192 func scrollViewDidScroll(_ scrollView: UIScrollView) {
193 guard (readerConfig.scrollDirection == .vertical ||
194 readerConfig.scrollDirection == .defaultVertical ||
195 readerConfig.scrollDirection == .horizontalWithVerticalContent) else {
199 if visible && usingSlider == false {
203 if (slider.alpha > 0) {
205 } else if delegate?.currentPage != nil && scrollStart != nil {
206 scrollDelta = scrollView.contentOffset.forDirection(withConfiguration: readerConfig) - scrollStart
208 guard let pageHeight = folioReader.readerCenter?.pageHeight,
209 (scrollDeltaTimer == nil && scrollDelta > (pageHeight * 0.2 ) || (scrollDelta * -1) > (pageHeight * 0.2)) else {
214 self.resetScrollDelta()
218 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
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)
227 @objc func resetScrollDelta() {
228 if scrollDeltaTimer != nil {
229 scrollDeltaTimer.invalidate()
230 scrollDeltaTimer = nil
233 scrollStart = (scrollView()?.contentOffset.forDirection(withConfiguration: readerConfig) ?? 0)
237 func setSliderVal() {
238 slider.value = Float(scrollTop() / height())
241 // MARK: - utility methods
243 fileprivate func scrollView() -> UIScrollView? {
244 return delegate?.currentPage?.webView?.scrollView
247 fileprivate func height() -> CGFloat {
248 guard let currentPage = delegate?.currentPage,
249 let pageHeight = folioReader.readerCenter?.pageHeight,
250 let webView = currentPage.webView else {
254 return webView.scrollView.contentSize.height - pageHeight + 44
257 fileprivate func scrollTop() -> CGFloat {
258 guard let currentPage = delegate?.currentPage, let webView = currentPage.webView else {
261 return webView.scrollView.contentOffset.forDirection(withConfiguration: readerConfig)