Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swift 4 and KVO #74

Closed
wants to merge 2 commits into
base: develop
from
Closed
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.
+57 −72
Diff settings

Always

Just for now

Next

Swift 4 and KVO

  • Loading branch information...
dpopov committed Sep 18, 2017
commit a36a40031964d89219c844ef0d302fbe978306e2
@@ -13,7 +13,7 @@ public enum Position {
case top, bottom
}

open class PullToRefresh: NSObject {
@objcMembers open class PullToRefresh: NSObject {

open var position: Position = .top

@@ -26,7 +26,7 @@ open class PullToRefresh: NSObject {
let refreshView: UIView
var action: (() -> ())?

fileprivate var isObserving = false
fileprivate var observations: [NSKeyValueObservation] = []
fileprivate let animator: RefreshViewAnimator

// MARK: - ScrollView & Observing
@@ -84,85 +84,68 @@ open class PullToRefresh: NSObject {
self.init(refreshView: refreshView, animator: DefaultViewAnimator(refreshView: refreshView), height: height, position: position)
}

deinit {
removeScrollViewObserving()
}

// MARK: KVO
fileprivate var KVOContext = "PullToRefreshKVOContext"
fileprivate let contentOffsetKeyPath = "contentOffset"
fileprivate let contentInsetKeyPath = "contentInset"
fileprivate let contentSizeKeyPath = "contentSize"
fileprivate var previousScrollViewOffset: CGPoint = CGPoint.zero

override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if (context == &KVOContext && keyPath == contentOffsetKeyPath && object as? UIScrollView == scrollView) {
var offset: CGFloat
switch position {
case .top:
offset = previousScrollViewOffset.y + scrollViewDefaultInsets.top

case .bottom:
if scrollView!.contentSize.height > scrollView!.bounds.height {
offset = scrollView!.contentSize.height - previousScrollViewOffset.y - scrollView!.bounds.height
} else {
offset = scrollView!.contentSize.height - previousScrollViewOffset.y
}
}
let refreshViewHeight = refreshView.frame.size.height

switch offset {
case 0 where (state != .loading): state = .initial
case -refreshViewHeight...0 where (state != .loading && state != .finished):
state = .releasing(progress: -offset / refreshViewHeight)

case -1000...(-refreshViewHeight):
if state == .releasing(progress: 1) && scrollView?.isDragging == false {
state = .loading
} else if state != .loading && state != .finished {
state = .releasing(progress: 1)
}
default: break
}
} else if (context == &KVOContext && keyPath == contentSizeKeyPath && object as? UIScrollView == scrollView) {
if case .bottom = position {
refreshView.frame = CGRect(x: 0, y: scrollView!.contentSize.height, width: scrollView!.bounds.width, height: refreshView.bounds.height)

fileprivate func addScrollViewObserving() {
guard let scrollView = scrollView, self.observations.isEmpty else {
return
}

self.observations.append(scrollView.observe(\.contentOffset) { (scrollView, _) in
self.processOffsetChange(inScrollView: scrollView)
})

self.observations.append(scrollView.observe(\.contentSize) { (scrollView, _) in
if case .bottom = self.position {
self.refreshView.frame = CGRect(x: 0, y: scrollView.contentSize.height, width: scrollView.bounds.width, height: self.refreshView.bounds.height)
}
} else if (context == &KVOContext && keyPath == contentInsetKeyPath && object as? UIScrollView == scrollView) {
self.previousScrollViewOffset.y = scrollView.contentOffset.y
})

self.observations.append(scrollView.observe(\.contentOffset) { (scrollView, _) in
if self.state == .initial {
scrollViewDefaultInsets = scrollView!.contentInset
self.scrollViewDefaultInsets = scrollView.contentInset
}

} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}

previousScrollViewOffset.y = scrollView?.contentOffset.y ?? 0
}
self.previousScrollViewOffset.y = scrollView.contentOffset.y
})
}

fileprivate func addScrollViewObserving() {
guard let scrollView = scrollView, !isObserving else {
return
func processOffsetChange(inScrollView scrollView: UIScrollView) {
var offset: CGFloat
switch self.position {
case .top:
offset = self.previousScrollViewOffset.y + self.scrollViewDefaultInsets.top

case .bottom:
if scrollView.contentSize.height > scrollView.bounds.height {
offset = scrollView.contentSize.height - self.previousScrollViewOffset.y - scrollView.bounds.height
} else {
offset = scrollView.contentSize.height - self.previousScrollViewOffset.y
}
}
let refreshViewHeight = self.refreshView.frame.size.height

scrollView.addObserver(self, forKeyPath: contentOffsetKeyPath, options: .initial, context: &KVOContext)
scrollView.addObserver(self, forKeyPath: contentSizeKeyPath, options: .initial, context: &KVOContext)
scrollView.addObserver(self, forKeyPath: contentInsetKeyPath, options: .new, context: &KVOContext)

isObserving = true
switch offset {
case 0 where (self.state != .loading): self.state = .initial
case -refreshViewHeight...0 where (self.state != .loading && self.state != .finished):
self.state = .releasing(progress: -offset / refreshViewHeight)

case -1000...(-refreshViewHeight):
if self.state == .releasing(progress: 1) && scrollView.isDragging == false {
self.state = .loading
} else if self.state != .loading && self.state != .finished {
self.state = .releasing(progress: 1)
}
default: break
}
self.previousScrollViewOffset.y = scrollView.contentOffset.y
}

fileprivate func removeScrollViewObserving() {
guard let scrollView = scrollView, isObserving else {
return
}

scrollView.removeObserver(self, forKeyPath: contentOffsetKeyPath, context: &KVOContext)
scrollView.removeObserver(self, forKeyPath: contentSizeKeyPath, context: &KVOContext)
scrollView.removeObserver(self, forKeyPath: contentInsetKeyPath, context: &KVOContext)

isObserving = false
self.observations.forEach({$0.invalidate()})
self.observations = []
}
}

@@ -215,7 +215,7 @@
};
4DDDB1C61B0F7EE700DE9D93 = {
CreatedOnToolsVersion = 6.3.1;
LastSwiftMigration = 0800;
LastSwiftMigration = 0900;
};
};
};
@@ -437,7 +437,8 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
SWIFT_SWIFT3_OBJC_INFERENCE = On;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -459,7 +460,8 @@
PRODUCT_BUNDLE_IDENTIFIER = "Yalantis.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
SWIFT_SWIFT3_OBJC_INFERENCE = On;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.