/
PageboyScrollDetection.swift
190 lines (157 loc) 路 6.91 KB
/
PageboyScrollDetection.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//
// PageboyScrollDetection.swift
// Pageboy
//
// Created by Merrick Sapsford on 13/02/2017.
// Copyright 漏 2017 Merrick Sapsford. All rights reserved.
//
import Foundation
// MARK: - UIPageViewControllerDelegate, UIScrollViewDelegate
extension PageboyViewController: UIPageViewControllerDelegate, UIScrollViewDelegate {
public func pageViewController(_ pageViewController: UIPageViewController,
willTransitionTo pendingViewControllers: [UIViewController]) {
guard let viewController = pendingViewControllers.first,
let index = self.viewControllers?.index(of: viewController) else {
return
}
let direction = NavigationDirection.forPage(index, previousPage: self.currentIndex)
self.delegate?.pageboyViewController(self, willScrollToPageAtIndex: index, direction: direction)
}
public func pageViewController(_ pageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers: [UIViewController],
transitionCompleted completed: Bool) {
guard completed == true else {
return
}
if let viewController = pageViewController.viewControllers?.first,
let index = self.viewControllers?.index(of: viewController) {
self.updateCurrentPageIndexIfNeeded(index)
}
}
//
// MARK: UIScrollViewDelegate
//
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
let previousPagePosition = self.previousPagePosition ?? 0.0
// calculate offset / page size for relative orientation
var pageSize: CGFloat!
var contentOffset: CGFloat!
if self.navigationOrientation == .horizontal {
pageSize = scrollView.frame.size.width
contentOffset = scrollView.contentOffset.x
} else {
pageSize = scrollView.frame.size.height
contentOffset = scrollView.contentOffset.y
}
let scrollOffset = contentOffset - pageSize
let pageOffset = (CGFloat(self.currentIndex) * pageSize) + scrollOffset
var pagePosition = pageOffset / pageSize
// do not continue if a page change is detected
guard !self.detectCurrentPageIndexIfNeeded(pagePosition: pagePosition,
scrollView: scrollView) else {
return
}
// do not continue if previous offset equals current
if previousPagePosition == pageOffset {
return
}
// update relative page position for infinite overscroll if required
self.detectInfiniteOverscrollIfNeeded(pagePosition: &pagePosition)
// provide scroll updates
var offsetPoint: CGPoint!
let direction = NavigationDirection.forOffset(pagePosition, previousOffset: previousPagePosition)
if self.navigationOrientation == .horizontal {
offsetPoint = CGPoint(x: pagePosition, y: scrollView.contentOffset.y)
} else {
offsetPoint = CGPoint(x: scrollView.contentOffset.x, y: pagePosition)
}
self.delegate?.pageboyViewController(self,
didScrollToOffset: offsetPoint,
direction: direction)
self.previousPagePosition = pagePosition
}
//
// MARK: Utils
//
/// Detect whether the scroll view is overscrolling while infinite scroll is enabled
/// Adjusts pagePosition if required.
///
/// - Parameter pagePosition: the relative page position.
private func detectInfiniteOverscrollIfNeeded(pagePosition: inout CGFloat) {
let maxPagePosition = CGFloat((self.viewControllers?.count ?? 1) - 1)
let overscrolling = pagePosition < 0.0 || pagePosition > maxPagePosition
guard self.isInfiniteScrollEnabled && overscrolling else {
return
}
var integral: Double = 0.0
var progress = CGFloat(modf(fabs(Double(pagePosition)), &integral))
var maxInfinitePosition: CGFloat!
if pagePosition > 0.0 {
progress = 1.0 - progress
maxInfinitePosition = 0.0
} else {
maxInfinitePosition = maxPagePosition
}
var infinitePagePosition = maxPagePosition * progress
if fmod(progress, 1.0) == 0.0 {
infinitePagePosition = maxInfinitePosition
}
pagePosition = infinitePagePosition
print(overscrolling)
}
/// Detects whether a page boundary has been passed.
/// As pageViewController:didFinishAnimating is not reliable.
///
/// - Parameters:
/// - pageOffset: The current page scroll offset
/// - scrollView: The scroll view that is being scrolled.
/// - Returns: Whether a page transition has been detected.
private func detectCurrentPageIndexIfNeeded(pagePosition: CGFloat, scrollView: UIScrollView) -> Bool {
let isPagingForward = pagePosition > self.previousPagePosition ?? 0.0
if scrollView.isDragging {
if isPagingForward && pagePosition >= CGFloat(self.currentIndex + 1) {
self.updateCurrentPageIndexIfNeeded(self.currentIndex + 1)
return true
} else if !isPagingForward && pagePosition <= CGFloat(self.currentIndex - 1) {
self.updateCurrentPageIndexIfNeeded(self.currentIndex - 1)
return true
}
}
return false
}
/// Safely update the current page index.
///
/// - Parameter index: the proposed index.
private func updateCurrentPageIndexIfNeeded(_ index: Int) {
guard self.currentIndex != index, index >= 0 &&
index < self.viewControllers?.count ?? 0 else {
return
}
self.currentIndex = index
}
}
// MARK: - NavigationDirection detection
internal extension PageboyViewController.NavigationDirection {
var pageViewControllerNavDirection: UIPageViewControllerNavigationDirection {
get {
switch self {
case .reverse:
return .reverse
default:
return .forward
}
}
}
static func forPage(_ page: Int,
previousPage: Int) -> PageboyViewController.NavigationDirection {
return self.forOffset(CGFloat(page), previousOffset: CGFloat(previousPage))
}
static func forOffset(_ offset: CGFloat,
previousOffset: CGFloat) -> PageboyViewController.NavigationDirection {
if offset == previousOffset {
return .neutral
}
return offset > previousOffset ? .forward : .reverse
}
}