Skip to content

Commit

Permalink
Merge pull request #87 from Yalantis/develop
Browse files Browse the repository at this point in the history
Merge with Develop branch
  • Loading branch information
Sergey D authored Nov 16, 2017
2 parents 4d3b2c5 + 369edbd commit e6245c6
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 120 deletions.
78 changes: 21 additions & 57 deletions PullToRefresh/PullToRefresh.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,17 @@ open class PullToRefresh: NSObject {
open var springDamping: CGFloat = 0.4
open var initialSpringVelocity: CGFloat = 0.8
open var animationOptions: UIViewAnimationOptions = [.curveLinear]
open var shouldBeVisibleWhileScrolling: Bool = false {
willSet{
if shouldBeVisibleWhileScrolling {
sendRefreshViewToScrollView()
}
}
}
open var shouldBeVisibleWhileScrolling: Bool = false

let refreshView: UIView
var action: (() -> ())?

weak var scrollView: UIScrollView? {
willSet {
if #available(iOS 11.0, *) {
scrollView?.removeAdjustedContentInsetsHandler(forPosition: position)
}
removeScrollViewObserving()
}
didSet {
if let scrollView = scrollView {
if #available(iOS 11.0, *) {
scrollView.addAdjustedContentInsetsHandler(forPosition: position) { [weak self] (adjustedInsets) in
self?.scrollViewDefaultAdjustedInsets = adjustedInsets
}
}
scrollViewDefaultInsets = scrollView.contentInset
addScrollViewObserving()
}
Expand All @@ -59,7 +45,6 @@ open class PullToRefresh: NSObject {
// MARK: - ScrollView & Observing

fileprivate var scrollViewDefaultInsets: UIEdgeInsets = .zero
fileprivate var scrollViewDefaultAdjustedInsets: UIEdgeInsets = .zero
fileprivate var previousScrollViewOffset: CGPoint = CGPoint.zero

// MARK: - State
Expand Down Expand Up @@ -135,25 +120,21 @@ extension PullToRefresh {
override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if (context == &KVO.context && keyPath == KVO.ScrollViewPath.contentOffset && object as? UIScrollView == scrollView) {
var offset: CGFloat
var topInsetY: CGFloat
if #available(iOS 11, *) {
topInsetY = scrollView!.adjustedContentInset.top
} else {
topInsetY = scrollView!.contentInset.top
}
switch position {
case .top:
offset = previousScrollViewOffset.y + topInsetY
offset = previousScrollViewOffset.y + scrollViewDefaultInsets.top

case .bottom:
if scrollView!.contentSize.height > scrollView!.bounds.height {
offset = scrollView!.contentSize.height - previousScrollViewOffset.y - scrollView!.bounds.height + topInsetY
offset = scrollView!.contentSize.height - previousScrollViewOffset.y - scrollView!.bounds.height
} else {
offset = scrollView!.contentSize.height - previousScrollViewOffset.y + topInsetY
offset = scrollView!.contentSize.height - previousScrollViewOffset.y
}
if #available(iOS 11, *) {
offset += scrollView!.safeAreaInsets.top
}
}
let refreshViewHeight = refreshView.frame.size.height

switch offset {
case 0 where (state != .loading): state = .initial
case -refreshViewHeight...0 where (state != .loading && state != .finished):
Expand All @@ -178,7 +159,7 @@ extension PullToRefresh {
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
previousScrollViewOffset.y = scrollView?.contentOffset.y ?? 0
previousScrollViewOffset.y = scrollView?.normalizedContentOffset.y ?? 0
}

fileprivate func addScrollViewObserving() {
Expand Down Expand Up @@ -211,24 +192,21 @@ extension PullToRefresh {
extension PullToRefresh {

func startRefreshing() {
if self.state != .initial {
guard state == .initial, let scrollView = scrollView else {
return
}

var offsetY: CGFloat
switch position {
case .top:
offsetY = -refreshView.frame.height - scrollViewDefaultInsets.top

if #available(iOS 11, *) {
offsetY -= scrollView.safeAreaInsets.top
}
case .bottom:
offsetY = scrollView!.contentSize.height + refreshView.frame.height + scrollViewDefaultInsets.bottom - scrollView!.bounds.height
}

scrollView?.setContentOffset(CGPoint(x: 0, y: offsetY), animated: true)
let delayTime = DispatchTime.now() + Double(Int64(0.27 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: delayTime) { [weak self] in
self?.state = .loading
offsetY = scrollView.contentSize.height + refreshView.frame.height + scrollViewDefaultInsets.bottom - scrollView.bounds.height
}
state = .loading
scrollView.setContentOffset(CGPoint(x: 0, y: offsetY), animated: true)
}

func endRefreshing() {
Expand All @@ -253,14 +231,12 @@ private extension PullToRefresh {
animations: {
switch self.position {
case .top:
let insets = self.refreshView.frame.height + self.scrollViewDefaultInsets.top
scrollView.contentInset.top = insets
let offsetY = self.defaultInsets.top + self.refreshView.frame.height
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: -offsetY)

let insetY = self.refreshView.frame.height + self.scrollViewDefaultInsets.top
scrollView.contentInset.top = insetY
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: -insetY)
case .bottom:
let insets = self.refreshView.frame.height + self.scrollViewDefaultInsets.bottom
scrollView.contentInset.bottom = insets
let insetY = self.refreshView.frame.height + self.scrollViewDefaultInsets.bottom
scrollView.contentInset.bottom = insetY
}
},
completion: { _ in
Expand All @@ -284,9 +260,6 @@ private extension PullToRefresh {
options: animationOptions,
animations: {
self.scrollView?.contentInset = self.scrollViewDefaultInsets
if case .top = self.position {
self.scrollView?.contentOffset.y = -self.defaultInsets.top
}
},
completion: { _ in
self.addScrollViewObserving()
Expand All @@ -299,18 +272,10 @@ private extension PullToRefresh {
// MARK: - Helpers
private extension PullToRefresh {

var defaultInsets: UIEdgeInsets {
if #available(iOS 11, *) {
return scrollViewDefaultAdjustedInsets
} else {
return scrollViewDefaultInsets
}
}

var isCurrentlyVisible: Bool {
guard let scrollView = scrollView else { return false }

return scrollView.contentOffset.y <= -defaultInsets.top
return scrollView.normalizedContentOffset.y <= -scrollViewDefaultInsets.top
}

func bringRefreshViewToSuperview() {
Expand All @@ -331,4 +296,3 @@ private extension PullToRefresh {
}

}

89 changes: 26 additions & 63 deletions PullToRefresh/UIScrollView+PullToRefresh.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,79 +110,42 @@ public extension UIScrollView {
}
}

private var topPullToRefreshInsetsHandlerKey: UInt8 = 0
private var bottomPullToRefreshInsetsHandlerKey: UInt8 = 0
private var implementationSwapedKey: UInt8 = 0
internal func - (lhs: UIEdgeInsets, rhs: UIEdgeInsets) -> UIEdgeInsets {
return UIEdgeInsets(
top: lhs.top - rhs.top,
left: lhs.left - rhs.left,
bottom: lhs.bottom - rhs.bottom,
right: lhs.right - rhs.right
)
}

@available(iOS 11.0, *)
extension UIScrollView {

private var topPullToRefreshInsetsHandler: ((UIEdgeInsets) -> Void)? {
var normalizedContentOffset: CGPoint {
get {
return objc_getAssociatedObject(self, &topPullToRefreshInsetsHandlerKey) as? ((UIEdgeInsets) -> Void)
}
set {
objc_setAssociatedObject(self, &topPullToRefreshInsetsHandlerKey, newValue, .OBJC_ASSOCIATION_RETAIN)
let contentOffset = self.contentOffset
let contentInset = self.effectiveContentInset

let output = CGPoint(x: contentOffset.x + contentInset.left, y: contentOffset.y + contentInset.top)
return output
}
}

private var bottomPullToRefreshInsetsHandler: ((UIEdgeInsets) -> Void)? {
var effectiveContentInset: UIEdgeInsets {
get {
return objc_getAssociatedObject(self, &bottomPullToRefreshInsetsHandlerKey) as? ((UIEdgeInsets) -> Void)
if #available(iOS 11, *) {
return adjustedContentInset
} else {
return contentInset
}
}
set {
objc_setAssociatedObject(self, &bottomPullToRefreshInsetsHandlerKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}

private var isImplementationSwaped: Bool {
get{
return objc_getAssociatedObject(self, &implementationSwapedKey) as? Bool ?? false
}
set{
objc_setAssociatedObject(self, &implementationSwapedKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}

internal func addAdjustedContentInsetsHandler(forPosition position: Position, handler: @escaping ((UIEdgeInsets) -> Void)) {
switch position {
case .top:
topPullToRefreshInsetsHandler = handler
case .bottom:
bottomPullToRefreshInsetsHandler = handler
}
if !isImplementationSwaped {
swapAdjustedContentInsetDidChangeImplementation()
isImplementationSwaped = true
}
}

private func swapAdjustedContentInsetDidChangeImplementation() {
let originalSelector = #selector(adjustedContentInsetDidChange)
let swizzledSelector = #selector(patchedAdjustedContentInsetDidChange)

if let originalMethod = class_getInstanceMethod(UIScrollView.self, originalSelector),
let swizzledMethod = class_getInstanceMethod(UIScrollView.self, swizzledSelector) {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}

internal func removeAdjustedContentInsetsHandler(forPosition position: Position) {
switch position {
case .top:
topPullToRefreshInsetsHandler = nil
case .bottom:
bottomPullToRefreshInsetsHandler = nil
}
if topPullToRefreshInsetsHandler == nil && bottomPullToRefreshInsetsHandler == nil {
swapAdjustedContentInsetDidChangeImplementation()
isImplementationSwaped = false
set {
if #available(iOS 11.0, *), contentInsetAdjustmentBehavior != .never {
contentInset = newValue - safeAreaInsets
} else {
contentInset = newValue
}
}
}

@objc internal func patchedAdjustedContentInsetDidChange() {
topPullToRefreshInsetsHandler?(adjustedContentInset)
bottomPullToRefreshInsetsHandler?(adjustedContentInset)
patchedAdjustedContentInsetDidChange()
}
}

0 comments on commit e6245c6

Please sign in to comment.