Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion Infinity.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "Infinity"
s.version = "3.0.2"
s.version = "3.0.3"
s.summary = "A simple way to make UIScrollView support pull-to-refresh & infinity-scroll"
s.homepage = "https://github.com/danisfabric/Infinity"
s.license = 'MIT'
Expand Down
Binary file modified Infinity/.DS_Store
Binary file not shown.
86 changes: 67 additions & 19 deletions Infinity/InfiniteScroller.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public func == (left: InfiniteScrollState, right: InfiniteScrollState) -> Bool {
}

class InfiniteScroller: NSObject {

weak var scrollView: UIScrollView? {
willSet {
removeScrollViewObserving(scrollView)
Expand All @@ -50,23 +51,25 @@ class InfiniteScroller: NSObject {
}
var animator: CustomInfiniteScrollAnimator
var containerView: FooterContainerView
var direction: InfinityScrollDirection
var action: (() -> Void)?
var enable = true

// Values
var defaultContentInset = UIEdgeInsets()
var defaultHeightToTrigger: CGFloat = 0
var defaultDistanceToTrigger: CGFloat = 0
// 是否在底部留出bottom inset,还是直接黏着内容,紧跟contentSize
var stickToContent = true {
didSet {
adjustFooterFrame()
}
}

init(height: CGFloat, animator: CustomInfiniteScrollAnimator) {
self.defaultHeightToTrigger = height
init(height: CGFloat, direction: InfinityScrollDirection, animator: CustomInfiniteScrollAnimator) {
self.defaultDistanceToTrigger = height
self.animator = animator
self.containerView = FooterContainerView()
self.direction = direction
}

// MARK: - Observe Scroll View
Expand Down Expand Up @@ -99,22 +102,42 @@ class InfiniteScroller: NSObject {
else if keyPath == "contentOffset" {
let point = (change![.newKey]! as AnyObject).cgPointValue!

guard lastOffset.y != point.y else {
return
if direction == .vertical{
guard lastOffset.y != point.y else {
return
}
}
else{
guard lastOffset.x != point.x else {
return
}
}
guard !updatingState && enable else {
return
}

var distance: CGFloat = 0
if stickToContent {
distance = scrollView!.contentSize.height - point.y - scrollView!.frame.height
}else {

switch (direction, stickToContent){
case (.vertical, true):
distance = scrollView!.contentSize.height - point.y - scrollView!.frame.height
case (.vertical, false):
distance = scrollView!.contentSize.height + self.defaultContentInset.bottom - point.y - scrollView!.frame.height
case (.horizontal, true):
distance = scrollView!.contentSize.width - point.x - scrollView!.frame.width
case (.horizontal, false):
distance = scrollView!.contentSize.width + self.defaultContentInset.right - point.x - scrollView!.frame.width
default: return
}

// 要保证scrollView里面是有内容的, 且保证是在上滑
if distance < 0 && self.state != .loading && scrollView!.contentSize.height > 0 && point.y > lastOffset.y {
self.state = .loading
if self.state != .loading{
var verticalShouldLoad: Bool = distance < 0 && scrollView!.contentSize.height > 0 && point.y > lastOffset.y
var horizontalShouldLoad: Bool = distance < 0 && scrollView!.contentSize.width > 0 && point.x > lastOffset.x
var shouldLoad: Bool = direction == .vertical ? verticalShouldLoad : horizontalShouldLoad
if shouldLoad{
self.state = .loading
}
}

lastOffset = point
Expand All @@ -134,8 +157,16 @@ class InfiniteScroller: NSObject {
case .loading where oldValue == .none:

self.updatingState = true
let jumpToBottom = self.defaultHeightToTrigger + self.defaultContentInset.bottom
let inset = UIEdgeInsets(top: self.defaultContentInset.top, left: self.defaultContentInset.left, bottom: jumpToBottom, right: self.defaultContentInset.right)
var inset: UIEdgeInsets!
if self.direction == .vertical{
let jumpToBottom = self.defaultDistanceToTrigger + self.defaultContentInset.bottom
inset = UIEdgeInsets(top: self.defaultContentInset.top, left: self.defaultContentInset.left, bottom: jumpToBottom, right: self.defaultContentInset.right)
}
else{
let jumpToBottom = self.defaultDistanceToTrigger + self.defaultContentInset.right
inset = UIEdgeInsets(top: self.defaultContentInset.top, left: self.defaultContentInset.left, bottom: self.defaultContentInset.bottom, right: jumpToBottom)
}

self.scrollView?.setContentInset(inset, completion: { [unowned self] (finished) -> Void in
self.updatingState = false
})
Expand All @@ -154,16 +185,34 @@ class InfiniteScroller: NSObject {

func adjustFooterFrame() {
if let scrollView = scrollView {
if stickToContent {
containerView.frame = CGRect(x: 0, y: scrollView.contentSize.height, width: scrollView.bounds.width, height: defaultHeightToTrigger)
}else {
containerView.frame = CGRect(x: 0, y: scrollView.contentSize.height + self.defaultContentInset.bottom, width: scrollView.bounds.width, height: defaultHeightToTrigger)
}
containerView.frame = containerFrame(scrollView: scrollView)
}
}

func containerFrame(scrollView: UIScrollView) -> CGRect{
switch (direction, stickToContent){
case (.vertical, true):
return CGRect(x: 0, y: scrollView.contentSize.height, width: scrollView.bounds.width, height: defaultDistanceToTrigger)
case (.vertical, false):
return CGRect(x: 0, y: scrollView.contentSize.height + self.defaultContentInset.bottom, width: scrollView.bounds.width, height: defaultDistanceToTrigger)
case (.horizontal, true):
return CGRect(x: scrollView.contentSize.width, y: 0, width: defaultDistanceToTrigger, height: scrollView.bounds.height)
case (.horizontal, false):
return CGRect(x: scrollView.contentSize.width + self.defaultContentInset.right, y: 0, width: defaultDistanceToTrigger, height: scrollView.bounds.height)
default: return CGRect.zero
}

}

// MARK: - Infinity Scroll
func beginInfiniteScrolling() {
scrollView?.setContentOffset(CGPoint(x: 0, y: (scrollView!.contentSize.height + defaultContentInset.bottom - scrollView!.frame.height + defaultHeightToTrigger)), animated: true)
if direction == .vertical{
scrollView?.setContentOffset(CGPoint(x: 0, y: (scrollView!.contentSize.height + defaultContentInset.bottom - scrollView!.frame.height + defaultDistanceToTrigger)), animated: true)
}
else{
scrollView?.setContentOffset(CGPoint(x: (scrollView!.contentSize.width + defaultContentInset.right - scrollView!.frame.width + defaultDistanceToTrigger), y: 0), animated: true)
}

}
func endInfiniteScrolling() {
self.state = .none
Expand All @@ -174,7 +223,6 @@ class FooterContainerView: UIView {

override func layoutSubviews() {
super.layoutSubviews()

for view in subviews {
view.center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
}
Expand Down
75 changes: 43 additions & 32 deletions Infinity/PullToRefresher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ public func == (left: PullToRefreshState, right: PullToRefreshState) -> Bool {
}

class PullToRefresher: NSObject {
func containerFrame(scrollView: UIScrollView) -> CGRect{
let horizontalFrame = CGRect(x: -defaultDistanceToTrigger + animatorOffset.horizontal, y: animatorOffset.vertical, width: defaultDistanceToTrigger, height: scrollView.frame.height)
let verticalFrame = CGRect(x: 0 + animatorOffset.horizontal, y: -defaultDistanceToTrigger + animatorOffset.vertical, width: scrollView.frame.width, height: defaultDistanceToTrigger)
return direction == .horizontal ? horizontalFrame : verticalFrame
}

weak var scrollView: UIScrollView? {
willSet {
removeScrollViewObserving(scrollView)
Expand All @@ -48,31 +54,34 @@ class PullToRefresher: NSObject {

containerView.scrollView = scrollView
scrollView.addSubview(containerView)
containerView.frame = CGRect(x: 0 + animatorOffset.horizontal, y: -defaultHeightToTrigger + animatorOffset.vertical, width: scrollView.frame.width, height: defaultHeightToTrigger)
containerView.frame = containerFrame(scrollView: scrollView)
}
}
}
var animator: CustomPullToRefreshAnimator
var containerView: HeaderContainerView
var direction: InfinityScrollDirection
var action:(()->Void)?
var enable = true

var animatorOffset: UIOffset = UIOffset() {
didSet {
if let scrollView = scrollView {
containerView.frame = CGRect(x: 0 + animatorOffset.horizontal, y: -defaultHeightToTrigger + animatorOffset.vertical, width: scrollView.frame.width, height: defaultHeightToTrigger)
containerView.frame = containerFrame(scrollView: scrollView)
}
}
}
// Values
var defaultContentInset: UIEdgeInsets = UIEdgeInsets()
var defaultHeightToTrigger: CGFloat = 0
var defaultDistanceToTrigger: CGFloat = 0
var scrollbackImmediately = true

init(height: CGFloat, animator: CustomPullToRefreshAnimator) {
self.defaultHeightToTrigger = height
init(height: CGFloat, direction: InfinityScrollDirection, animator: CustomPullToRefreshAnimator) {
self.defaultDistanceToTrigger = height
self.animator = animator
self.containerView = HeaderContainerView()
self.direction = direction

}
// MARK: - Observe Scroll View
var KVOContext = "PullToRefreshKVOContext"
Expand All @@ -85,21 +94,22 @@ class PullToRefresher: NSObject {
scrollView?.removeObserver(self, forKeyPath: "contentOffset", context: &KVOContext)
scrollView?.removeObserver(self, forKeyPath: "contentInset", context: &KVOContext)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &KVOContext {
if keyPath == "contentOffset" {
guard !updatingState && enable else {
return
}
let point = (change![.newKey]! as AnyObject).cgPointValue!
let offsetY = point.y + defaultContentInset.top
switch offsetY {
let topOffset = direction == .horizontal ? point.x + defaultContentInset.left : point.y + defaultContentInset.top
print("TOP topOffset: \(topOffset)")
switch topOffset {
case 0 where state != .loading:
state = .none
case -defaultHeightToTrigger...0 where state != .loading:
state = .releasing(progress: min(-offsetY / defaultHeightToTrigger, 1.0))
case (-CGFloat.greatestFiniteMagnitude)...(-defaultHeightToTrigger) where state == .releasing(progress:1):
case -defaultDistanceToTrigger...0 where state != .loading:
state = .releasing(progress: min(-topOffset / defaultDistanceToTrigger, 1.0))
case (-CGFloat.greatestFiniteMagnitude)...(-defaultDistanceToTrigger) where state == .releasing(progress:1):
if scrollView!.isDragging {
state = .releasing(progress: 1.0)
}else {
Expand All @@ -123,34 +133,33 @@ class PullToRefresher: NSObject {
var updatingState = false
var state: PullToRefreshState = .none {
didSet {
self.animator.animateState(state)

DispatchQueue.main.async {
self.animator.animateState(self.state)
switch self.state {
case .none where oldValue == .loading:
case .none:
guard scrollView?.contentInset != self.defaultContentInset else { return }
if !self.scrollbackImmediately {
self.updatingState = true
if self.scrollView is UICollectionView {
self.scrollView?.setContentInset(self.defaultContentInset, completion: { [unowned self] (finished) -> Void in
self.updatingState = false
})
} else {
self.scrollView?.setContentInset(self.defaultContentInset, completion: { [unowned self] (finished) -> Void in
self.updatingState = false
})
}
self.scrollView?.setContentInset(self.defaultContentInset, completion: { [unowned self] (finished) -> Void in
self.updatingState = false
})
}

case .loading where oldValue != .loading:
if !self.scrollbackImmediately {
self.updatingState = true
var inset = self.defaultContentInset
inset.top += self.defaultHeightToTrigger
self.scrollView?.setContentInset(inset, completion: { [unowned self] (finished) -> Void in
self.updatingState = false
})
self.action?()
if !self.scrollbackImmediately {
self.updatingState = true
var inset = self.defaultContentInset
if self.direction == .horizontal{
inset.left += self.defaultDistanceToTrigger
}
else{
inset.top += self.defaultDistanceToTrigger
}
self.scrollView?.setContentInset(inset, completion: { [unowned self] (finished) -> Void in
self.updatingState = false
})
self.action?()
}
default:
break
}
Expand All @@ -159,7 +168,9 @@ class PullToRefresher: NSObject {
}
// MARK: - Refresh
func beginRefreshing() {
self.scrollView?.setContentOffset(CGPoint(x: 0, y: -(defaultHeightToTrigger + defaultContentInset.top + 1)), animated: true)
let horizontalContentOffset = CGPoint(x: 0, y: -(defaultDistanceToTrigger + defaultContentInset.top + 1))
let verticalContentOffset = CGPoint(x: -(defaultDistanceToTrigger + defaultContentInset.left + 1), y: 0)
self.scrollView?.setContentOffset(direction == .horizontal ? horizontalContentOffset : verticalContentOffset , animated: true)
}
func endRefreshing() {
self.state = .none
Expand Down
Loading