From 8ece0ea9f5b36e7cee6c7e6b57ee3785bedd7ed9 Mon Sep 17 00:00:00 2001 From: Josip Cavar Date: Mon, 30 Mar 2015 16:53:03 +0200 Subject: [PATCH 01/12] first version of custom subviews support --- PullToRefresh.xcodeproj/project.pbxproj | 8 +++++ PullToRefreshDemo/CustomSubview.swift | 34 ++++++++++++++++++ PullToRefreshDemo/CustomSubview.xib | 47 +++++++++++++++++++++++++ PullToRefreshDemo/ViewController.swift | 14 +++++++- Refresher/PullToRefreshExtension.swift | 9 +++++ Refresher/PullToRefreshView.swift | 9 +++++ 6 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 PullToRefreshDemo/CustomSubview.swift create mode 100644 PullToRefreshDemo/CustomSubview.xib diff --git a/PullToRefresh.xcodeproj/project.pbxproj b/PullToRefresh.xcodeproj/project.pbxproj index 9e1aba5..d7b5d83 100644 --- a/PullToRefresh.xcodeproj/project.pbxproj +++ b/PullToRefresh.xcodeproj/project.pbxproj @@ -23,6 +23,8 @@ C7AC6CFD19A894DF007107DF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C7AC6CF719A894DF007107DF /* Main.storyboard */; }; C7AC6CFE19A894DF007107DF /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C7AC6CF919A894DF007107DF /* Images.xcassets */; }; C7AC6D0019A894DF007107DF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7AC6CFB19A894DF007107DF /* ViewController.swift */; }; + C7B53A431AC996B80021914B /* CustomSubview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7B53A421AC996B80021914B /* CustomSubview.swift */; }; + C7B53A451AC9971C0021914B /* CustomSubview.xib in Resources */ = {isa = PBXBuildFile; fileRef = C7B53A441AC9971C0021914B /* CustomSubview.xib */; }; C7DA91CD19B31B7C00C4012B /* Animator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DA91CC19B31B7C00C4012B /* Animator.swift */; }; C7DA91CE19B348EA00C4012B /* Animator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DA91CC19B31B7C00C4012B /* Animator.swift */; }; /* End PBXBuildFile section */ @@ -83,6 +85,8 @@ C7AC6CF919A894DF007107DF /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; C7AC6CFA19A894DF007107DF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C7AC6CFB19A894DF007107DF /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + C7B53A421AC996B80021914B /* CustomSubview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomSubview.swift; sourceTree = ""; }; + C7B53A441AC9971C0021914B /* CustomSubview.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CustomSubview.xib; sourceTree = ""; }; C7D1CF8F199BB3C8009FD485 /* PullToRefreshDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PullToRefreshDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7DA91CC19B31B7C00C4012B /* Animator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animator.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -170,6 +174,8 @@ C7956A3D19A8966400CF6484 /* Supporting Files */, C7A355A119B612D5000DDC72 /* BeatAnimator.swift */, C7A355D019B62125000DDC72 /* PacmanAnimator.swift */, + C7B53A421AC996B80021914B /* CustomSubview.swift */, + C7B53A441AC9971C0021914B /* CustomSubview.xib */, ); path = PullToRefreshDemo; sourceTree = ""; @@ -327,6 +333,7 @@ files = ( C7AC6CFD19A894DF007107DF /* Main.storyboard in Resources */, C7AC6CFE19A894DF007107DF /* Images.xcassets in Resources */, + C7B53A451AC9971C0021914B /* CustomSubview.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -361,6 +368,7 @@ buildActionMask = 2147483647; files = ( C7A355D119B62125000DDC72 /* PacmanAnimator.swift in Sources */, + C7B53A431AC996B80021914B /* CustomSubview.swift in Sources */, C7AC6D0019A894DF007107DF /* ViewController.swift in Sources */, C7AC6CFC19A894DF007107DF /* AppDelegate.swift in Sources */, C7A355A219B612D5000DDC72 /* BeatAnimator.swift in Sources */, diff --git a/PullToRefreshDemo/CustomSubview.swift b/PullToRefreshDemo/CustomSubview.swift new file mode 100644 index 0000000..12fb990 --- /dev/null +++ b/PullToRefreshDemo/CustomSubview.swift @@ -0,0 +1,34 @@ +// +// CustomSubview.swift +// PullToRefresh +// +// Created by Josip Cavar on 30/03/15. +// Copyright (c) 2015 Josip Cavar. All rights reserved. +// + +import UIKit +import Refresher + +class CustomSubview: UIView, PullToRefreshViewAnimator { + + @IBOutlet weak var activityIndicator: UIActivityIndicatorView! + @IBOutlet weak var labelTitle: UILabel! + + func startAnimation() { + + activityIndicator.startAnimating() + } + + func stopAnimation() { + + activityIndicator.stopAnimating() + } + + func changeProgress(progress: CGFloat) { + + } + + func layoutLayers(superview: UIView) { + + } +} diff --git a/PullToRefreshDemo/CustomSubview.xib b/PullToRefreshDemo/CustomSubview.xib new file mode 100644 index 0000000..0631a14 --- /dev/null +++ b/PullToRefreshDemo/CustomSubview.xib @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PullToRefreshDemo/ViewController.swift b/PullToRefreshDemo/ViewController.swift index 2b5ff8a..639b219 100644 --- a/PullToRefreshDemo/ViewController.swift +++ b/PullToRefreshDemo/ViewController.swift @@ -42,7 +42,7 @@ class ViewController: UIViewController { } } } - */ + tableView.addPullToRefreshWithAction({ NSOperationQueue().addOperationWithBlock { sleep(2) @@ -51,6 +51,18 @@ class ViewController: UIViewController { } } }, withAnimator: PacmanAnimator()) + */ + + if let customSubview = NSBundle.mainBundle().loadNibNamed("CustomSubview", owner: self, options: nil).first as? CustomSubview { + tableView.addPullToRefreshWithAction({ + NSOperationQueue().addOperationWithBlock { + sleep(2) + NSOperationQueue.mainQueue().addOperationWithBlock { + self.tableView.stopPullToRefresh() + } + } + }, withAnimator: customSubview, withSubview: customSubview) + } } override func viewDidAppear(animated: Bool) { diff --git a/Refresher/PullToRefreshExtension.swift b/Refresher/PullToRefreshExtension.swift index b1190e4..d6dcf19 100644 --- a/Refresher/PullToRefreshExtension.swift +++ b/Refresher/PullToRefreshExtension.swift @@ -54,6 +54,15 @@ extension UIScrollView { addSubview(pullToRefreshView) } + // If you want to use your custom animation and custom subview when pull to refresh is animating, you should call this method and pass your animator and view objects. + public func addPullToRefreshWithAction(action :(() -> ()), withAnimator animator: PullToRefreshViewAnimator, withSubview subview: UIView) { + + let height = subview.frame.height + var pullToRefreshView = PullToRefreshView(action: action, frame: CGRectMake(0, -height, self.frame.size.width, height), animator: animator, subview: subview) + pullToRefreshView.tag = pullToRefreshTag + addSubview(pullToRefreshView) + } + // Manually start pull to refresh public func startPullToRefresh() { diff --git a/Refresher/PullToRefreshView.swift b/Refresher/PullToRefreshView.swift index e73ffd6..d1ce859 100644 --- a/Refresher/PullToRefreshView.swift +++ b/Refresher/PullToRefreshView.swift @@ -66,6 +66,15 @@ public class PullToRefreshView: UIView { self.init(frame: frame) self.action = action; } + + convenience init(action :(() -> ()), frame: CGRect, animator: PullToRefreshViewAnimator, subview: UIView) { + + self.init(frame: frame) + self.action = action; + self.animator = animator + subview.frame = self.bounds + addSubview(subview) + } convenience init(action :(() -> ()), frame: CGRect, animator: PullToRefreshViewAnimator) { From c23e8d5832baaedbf76747f43d87d9e361dace5a Mon Sep 17 00:00:00 2001 From: Josip Cavar Date: Sun, 26 Apr 2015 23:33:53 +0200 Subject: [PATCH 02/12] swift 1.2 --- PullToRefreshDemo/AppDelegate.swift | 23 +++++++++++------------ Refresher/PullToRefreshView.swift | 8 ++++---- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/PullToRefreshDemo/AppDelegate.swift b/PullToRefreshDemo/AppDelegate.swift index 7789340..5ddcf03 100644 --- a/PullToRefreshDemo/AppDelegate.swift +++ b/PullToRefreshDemo/AppDelegate.swift @@ -28,33 +28,32 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool { + func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. return true } - - func applicationWillResignActive(application: UIApplication!) { + + func applicationWillResignActive(application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } - - func applicationDidEnterBackground(application: UIApplication!) { + + func applicationDidEnterBackground(application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - - func applicationWillEnterForeground(application: UIApplication!) { + + func applicationWillEnterForeground(application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } - - func applicationDidBecomeActive(application: UIApplication!) { + + func applicationDidBecomeActive(application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - - func applicationWillTerminate(application: UIApplication!) { + + func applicationWillTerminate(application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - } diff --git a/Refresher/PullToRefreshView.swift b/Refresher/PullToRefreshView.swift index d1ce859..243baaf 100644 --- a/Refresher/PullToRefreshView.swift +++ b/Refresher/PullToRefreshView.swift @@ -121,8 +121,8 @@ public class PullToRefreshView: UIView { superview?.removeObserver(self, forKeyPath: contentOffsetKeyPath, context: &KVOContext) if (newSuperview != nil && newSuperview.isKindOfClass(UIScrollView)) { newSuperview.addObserver(self, forKeyPath: contentOffsetKeyPath, options: .Initial, context: &KVOContext) - scrollViewBouncesDefaultValue = (newSuperview as UIScrollView).bounces - scrollViewInsetsDefaultValue = (newSuperview as UIScrollView).contentInset + scrollViewBouncesDefaultValue = (newSuperview as! UIScrollView).bounces + scrollViewInsetsDefaultValue = (newSuperview as! UIScrollView).contentInset } } @@ -165,7 +165,7 @@ public class PullToRefreshView: UIView { private func startAnimating() { - var scrollView = superview as UIScrollView + var scrollView = superview as! UIScrollView var insets = scrollView.contentInset insets.top += self.frame.size.height @@ -184,7 +184,7 @@ public class PullToRefreshView: UIView { private func stopAnimating() { self.animator.stopAnimation() - var scrollView = superview as UIScrollView + var scrollView = superview as! UIScrollView scrollView.bounces = self.scrollViewBouncesDefaultValue UIView.animateWithDuration(0.3, animations: { () -> Void in scrollView.contentInset = self.scrollViewInsetsDefaultValue From 31dc6c47f93fe57c2a81c0c5cf7c2113fed593b9 Mon Sep 17 00:00:00 2001 From: Josip Cavar Date: Sun, 26 Apr 2015 23:34:27 +0200 Subject: [PATCH 03/12] remove code signing --- PullToRefresh.xcodeproj/project.pbxproj | 1 - 1 file changed, 1 deletion(-) diff --git a/PullToRefresh.xcodeproj/project.pbxproj b/PullToRefresh.xcodeproj/project.pbxproj index d7b5d83..9bbe414 100644 --- a/PullToRefresh.xcodeproj/project.pbxproj +++ b/PullToRefresh.xcodeproj/project.pbxproj @@ -288,7 +288,6 @@ }; C7D1CF8E199BB3C8009FD485 = { CreatedOnToolsVersion = 6.0; - DevelopmentTeam = 424L75GG87; }; }; }; From 891f539b8e9bab461535179a6ad535c4f5ad0219 Mon Sep 17 00:00:00 2001 From: Josip Cavar Date: Sun, 26 Apr 2015 23:35:05 +0200 Subject: [PATCH 04/12] settings --- .../project.xcworkspace/xcshareddata/PullToRefresh.xccheckout | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PullToRefresh.xcodeproj/project.xcworkspace/xcshareddata/PullToRefresh.xccheckout b/PullToRefresh.xcodeproj/project.xcworkspace/xcshareddata/PullToRefresh.xccheckout index ba4fca1..87f51ae 100644 --- a/PullToRefresh.xcodeproj/project.xcworkspace/xcshareddata/PullToRefresh.xccheckout +++ b/PullToRefresh.xcodeproj/project.xcworkspace/xcshareddata/PullToRefresh.xccheckout @@ -7,14 +7,14 @@ IDESourceControlProjectIdentifier F5CD0D2F-FAE8-4003-835D-672DF72A0E69 IDESourceControlProjectName - PullToRefresh + project IDESourceControlProjectOriginsDictionary 5F0EDA82EF8F22312981C50BB3AD526A2EBA15F4 https://github.com/jcavar/refresher.git IDESourceControlProjectPath - PullToRefresh.xcodeproj + PullToRefresh.xcodeproj/project.xcworkspace IDESourceControlProjectRelativeInstallPathDictionary 5F0EDA82EF8F22312981C50BB3AD526A2EBA15F4 From 31f329204c9f23ef2a37d9873ed78291bcd542f2 Mon Sep 17 00:00:00 2001 From: Josip Cavar Date: Sun, 26 Apr 2015 23:35:24 +0200 Subject: [PATCH 05/12] more swifty solution --- Refresher/PullToRefreshView.swift | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/Refresher/PullToRefreshView.swift b/Refresher/PullToRefreshView.swift index 243baaf..f9658e6 100644 --- a/Refresher/PullToRefreshView.swift +++ b/Refresher/PullToRefreshView.swift @@ -119,10 +119,10 @@ public class PullToRefreshView: UIView { public override func willMoveToSuperview(newSuperview: UIView!) { superview?.removeObserver(self, forKeyPath: contentOffsetKeyPath, context: &KVOContext) - if (newSuperview != nil && newSuperview.isKindOfClass(UIScrollView)) { - newSuperview.addObserver(self, forKeyPath: contentOffsetKeyPath, options: .Initial, context: &KVOContext) - scrollViewBouncesDefaultValue = (newSuperview as! UIScrollView).bounces - scrollViewInsetsDefaultValue = (newSuperview as! UIScrollView).contentInset + if let scrollView = newSuperview as? UIScrollView { + scrollView.addObserver(self, forKeyPath: contentOffsetKeyPath, options: .Initial, context: &KVOContext) + scrollViewBouncesDefaultValue = scrollView.bounces + scrollViewInsetsDefaultValue = scrollView.contentInset } } @@ -132,13 +132,11 @@ public class PullToRefreshView: UIView { public override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<()>) { if (context == &KVOContext) { - var scrollView = superview as? UIScrollView - if (keyPath == contentOffsetKeyPath && object as? UIScrollView == scrollView) { - var scrollView = object as? UIScrollView - if (scrollView != nil) { + if let scrollView = superview as? UIScrollView where object as? NSObject == scrollView { + if keyPath == contentOffsetKeyPath { var offsetWithoutInsets = previousOffset + scrollViewInsetsDefaultValue.top if (offsetWithoutInsets < -self.frame.size.height) { - if (scrollView?.dragging == false && loading == false) { + if (scrollView.dragging == false && loading == false) { loading = true } else if (loading == true) { labelTitle.text = NSLocalizedString("Loading ...", comment: "Refresher") @@ -152,7 +150,7 @@ public class PullToRefreshView: UIView { labelTitle.text = NSLocalizedString("Pull to refresh", comment: "Refresher") animator.changeProgress(-offsetWithoutInsets / self.frame.size.height) } - previousOffset = scrollView!.contentOffset.y + previousOffset = scrollView.contentOffset.y } } } else { @@ -176,8 +174,8 @@ public class PullToRefreshView: UIView { scrollView.contentInset = insets scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, -insets.top) }, completion: {finished in - self.animator.startAnimation() - self.action() + self.animator.startAnimation() + self.action() }) } @@ -186,9 +184,9 @@ public class PullToRefreshView: UIView { self.animator.stopAnimation() var scrollView = superview as! UIScrollView scrollView.bounces = self.scrollViewBouncesDefaultValue - UIView.animateWithDuration(0.3, animations: { () -> Void in + UIView.animateWithDuration(0.3, animations: { scrollView.contentInset = self.scrollViewInsetsDefaultValue - }) { (Bool) -> Void in + }) { finished in self.animator.changeProgress(0) } } From ccb5c907d2b3330cb63e7d49a79e79abae2ba91c Mon Sep 17 00:00:00 2001 From: Josip Cavar Date: Thu, 11 Jun 2015 11:12:29 +0200 Subject: [PATCH 06/12] architectural changes --- Refresher/Animator.swift | 60 +++++++++++++++++++------------ Refresher/PullToRefreshView.swift | 44 ++++++++++------------- 2 files changed, 56 insertions(+), 48 deletions(-) diff --git a/Refresher/Animator.swift b/Refresher/Animator.swift index ccd16b5..7477f48 100644 --- a/Refresher/Animator.swift +++ b/Refresher/Animator.swift @@ -27,9 +27,37 @@ import UIKit class Animator: PullToRefreshViewAnimator { - private var layerLoader: CAShapeLayer = CAShapeLayer() - private var layerSeparator: CAShapeLayer = CAShapeLayer() - + private class AnimatorView: UIView { + + private var layerLoader: CAShapeLayer = CAShapeLayer() + private var layerSeparator: CAShapeLayer = CAShapeLayer() + private let labelTitle = UILabel() + + init() { + + super.init + layer.addSublayer(layerLoader) + + if layerSeparator.superlayer == nil { + superview.layer.addSublayer(layerSeparator) + } + var bezierPathLoader = UIBezierPath() + bezierPathLoader.moveToPoint(CGPointMake(0, superview.frame.height - 3)) + bezierPathLoader.addLineToPoint(CGPoint(x: superview.frame.width, y: superview.frame.height - 3)) + + var bezierPathSeparator = UIBezierPath() + bezierPathSeparator.moveToPoint(CGPointMake(0, superview.frame.height - 1)) + bezierPathSeparator.addLineToPoint(CGPoint(x: superview.frame.width, y: superview.frame.height - 1)) + + layerLoader.path = bezierPathLoader.CGPath + layerSeparator.path = bezierPathSeparator.CGPath + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + init() { layerLoader.lineWidth = 4 @@ -41,7 +69,7 @@ class Animator: PullToRefreshViewAnimator { } - func startAnimation() { + func pullToRefreshAnimationDidStart(view: PullToRefreshView) { var pathAnimationEnd = CABasicAnimation(keyPath: "strokeEnd") pathAnimationEnd.duration = 0.5 @@ -60,29 +88,17 @@ class Animator: PullToRefreshViewAnimator { self.layerLoader.addAnimation(pathAnimationStart, forKey: "strokeStartAnimation") } - func stopAnimation() { - + func pullToRefreshAnimationDidEnd(view: PullToRefreshView) { + self.layerLoader.removeAllAnimations() } - func layoutLayers(superview: UIView) { + func pullToRefresh(view: PullToRefreshView, stateDidChange state: PullToRefreshViewState) { - if layerLoader.superlayer == nil { - superview.layer.addSublayer(layerLoader) - } - if layerSeparator.superlayer == nil { - superview.layer.addSublayer(layerSeparator) - } - var bezierPathLoader = UIBezierPath() - bezierPathLoader.moveToPoint(CGPointMake(0, superview.frame.height - 3)) - bezierPathLoader.addLineToPoint(CGPoint(x: superview.frame.width, y: superview.frame.height - 3)) - - var bezierPathSeparator = UIBezierPath() - bezierPathSeparator.moveToPoint(CGPointMake(0, superview.frame.height - 1)) - bezierPathSeparator.addLineToPoint(CGPoint(x: superview.frame.width, y: superview.frame.height - 1)) + } + + func pullToRefresh(view: PullToRefreshView, progressDidChange progress: CGFloat) { - layerLoader.path = bezierPathLoader.CGPath - layerSeparator.path = bezierPathSeparator.CGPath } func changeProgress(progress: CGFloat) { diff --git a/Refresher/PullToRefreshView.swift b/Refresher/PullToRefreshView.swift index f9658e6..b0e2bc3 100644 --- a/Refresher/PullToRefreshView.swift +++ b/Refresher/PullToRefreshView.swift @@ -27,17 +27,21 @@ import QuartzCore var KVOContext = "RefresherKVOContext" let contentOffsetKeyPath = "contentOffset" +public enum PullToRefreshViewState { + + case Loading +} + public protocol PullToRefreshViewAnimator { - func startAnimation() - func stopAnimation() - func changeProgress(progress: CGFloat) - func layoutLayers(superview: UIView) + func pullToRefreshAnimationDidStart(view: PullToRefreshView) + func pullToRefreshAnimationDidEnd(view: PullToRefreshView) + func pullToRefresh(view: PullToRefreshView, progressDidChange progress: CGFloat) + func pullToRefresh(view: PullToRefreshView, stateDidChange state: PullToRefreshViewState) } public class PullToRefreshView: UIView { - public let labelTitle = UILabel() // this maybe should be added in animator??? private var scrollViewBouncesDefaultValue: Bool = false private var scrollViewInsetsDefaultValue: UIEdgeInsets = UIEdgeInsetsZero @@ -87,12 +91,6 @@ public class PullToRefreshView: UIView { super.init(frame: frame) self.autoresizingMask = .FlexibleWidth - labelTitle.frame = bounds - labelTitle.textAlignment = .Center - labelTitle.autoresizingMask = .FlexibleLeftMargin | .FlexibleRightMargin - labelTitle.textColor = UIColor.blackColor() - labelTitle.text = NSLocalizedString("Pull to refresh", comment: "Refresher") - addSubview(labelTitle) } public required init(coder aDecoder: NSCoder) { @@ -110,12 +108,6 @@ public class PullToRefreshView: UIView { //MARK: UIView methods - public override func layoutSubviews() { - - super.layoutSubviews() - animator.layoutLayers(self) - } - public override func willMoveToSuperview(newSuperview: UIView!) { superview?.removeObserver(self, forKeyPath: contentOffsetKeyPath, context: &KVOContext) @@ -139,16 +131,16 @@ public class PullToRefreshView: UIView { if (scrollView.dragging == false && loading == false) { loading = true } else if (loading == true) { - labelTitle.text = NSLocalizedString("Loading ...", comment: "Refresher") + //labelTitle.text = NSLocalizedString("Loading ...", comment: "Refresher") } else { - labelTitle.text = NSLocalizedString("Release to refresh", comment: "Refresher") - animator.changeProgress(-offsetWithoutInsets / self.frame.size.height) + //labelTitle.text = NSLocalizedString("Release to refresh", comment: "Refresher") + animator.pullToRefresh(self, progressDidChange: -offsetWithoutInsets / self.frame.size.height) } } else if (loading == true) { - labelTitle.text = NSLocalizedString("Loading ...", comment: "Refresher") + //labelTitle.text = NSLocalizedString("Loading ...", comment: "Refresher") } else if (offsetWithoutInsets < 0) { - labelTitle.text = NSLocalizedString("Pull to refresh", comment: "Refresher") - animator.changeProgress(-offsetWithoutInsets / self.frame.size.height) + //labelTitle.text = NSLocalizedString("Pull to refresh", comment: "Refresher") + animator.pullToRefresh(self, progressDidChange: -offsetWithoutInsets / self.frame.size.height) } previousOffset = scrollView.contentOffset.y } @@ -174,20 +166,20 @@ public class PullToRefreshView: UIView { scrollView.contentInset = insets scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, -insets.top) }, completion: {finished in - self.animator.startAnimation() + self.animator.pullToRefreshAnimationDidStart(self) self.action() }) } private func stopAnimating() { - self.animator.stopAnimation() + self.animator.pullToRefreshAnimationDidEnd(self) var scrollView = superview as! UIScrollView scrollView.bounces = self.scrollViewBouncesDefaultValue UIView.animateWithDuration(0.3, animations: { scrollView.contentInset = self.scrollViewInsetsDefaultValue }) { finished in - self.animator.changeProgress(0) + self.animator.pullToRefresh(self, progressDidChange: 0) } } } From a63b7d5dafd05ed06f0e3c4bdee9623b3c2f3389 Mon Sep 17 00:00:00 2001 From: Josip Cavar Date: Sat, 13 Jun 2015 22:00:33 +0200 Subject: [PATCH 07/12] change architecture --- .../xcshareddata/PullToRefresh.xccheckout | 4 +- PullToRefreshDemo/Base.lproj/Main.storyboard | 6 +- PullToRefreshDemo/BeatAnimator.swift | 4 +- PullToRefreshDemo/CustomSubview.swift | 22 +++- PullToRefreshDemo/CustomSubview.xib | 12 +- PullToRefreshDemo/PacmanAnimator.swift | 66 ++++++----- PullToRefreshDemo/ViewController.swift | 27 +++-- Refresher/Animator.swift | 105 ++++++++---------- Refresher/PullToRefreshExtension.swift | 8 -- Refresher/PullToRefreshView.swift | 34 +++--- 10 files changed, 158 insertions(+), 130 deletions(-) diff --git a/PullToRefresh.xcodeproj/project.xcworkspace/xcshareddata/PullToRefresh.xccheckout b/PullToRefresh.xcodeproj/project.xcworkspace/xcshareddata/PullToRefresh.xccheckout index 87f51ae..ba4fca1 100644 --- a/PullToRefresh.xcodeproj/project.xcworkspace/xcshareddata/PullToRefresh.xccheckout +++ b/PullToRefresh.xcodeproj/project.xcworkspace/xcshareddata/PullToRefresh.xccheckout @@ -7,14 +7,14 @@ IDESourceControlProjectIdentifier F5CD0D2F-FAE8-4003-835D-672DF72A0E69 IDESourceControlProjectName - project + PullToRefresh IDESourceControlProjectOriginsDictionary 5F0EDA82EF8F22312981C50BB3AD526A2EBA15F4 https://github.com/jcavar/refresher.git IDESourceControlProjectPath - PullToRefresh.xcodeproj/project.xcworkspace + PullToRefresh.xcodeproj IDESourceControlProjectRelativeInstallPathDictionary 5F0EDA82EF8F22312981C50BB3AD526A2EBA15F4 diff --git a/PullToRefreshDemo/Base.lproj/Main.storyboard b/PullToRefreshDemo/Base.lproj/Main.storyboard index 2ad0bed..b9bc20b 100644 --- a/PullToRefreshDemo/Base.lproj/Main.storyboard +++ b/PullToRefreshDemo/Base.lproj/Main.storyboard @@ -1,7 +1,7 @@ - + - + @@ -57,7 +57,7 @@ - + diff --git a/PullToRefreshDemo/BeatAnimator.swift b/PullToRefreshDemo/BeatAnimator.swift index a210759..d73ff5b 100644 --- a/PullToRefreshDemo/BeatAnimator.swift +++ b/PullToRefreshDemo/BeatAnimator.swift @@ -25,6 +25,7 @@ import Foundation import Refresher import QuartzCore +/* class BeatAnimator: PullToRefreshViewAnimator { private var layerLoader: CAShapeLayer = CAShapeLayer() @@ -89,4 +90,5 @@ class BeatAnimator: PullToRefreshViewAnimator { self.layerLoader.strokeEnd = progress } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/PullToRefreshDemo/CustomSubview.swift b/PullToRefreshDemo/CustomSubview.swift index 12fb990..79cd611 100644 --- a/PullToRefreshDemo/CustomSubview.swift +++ b/PullToRefreshDemo/CustomSubview.swift @@ -9,26 +9,38 @@ import UIKit import Refresher + class CustomSubview: UIView, PullToRefreshViewAnimator { @IBOutlet weak var activityIndicator: UIActivityIndicatorView! @IBOutlet weak var labelTitle: UILabel! - func startAnimation() { + + func pullToRefreshAnimationDidStart(view: PullToRefreshView) { activityIndicator.startAnimating() + labelTitle.text = "Loading" } - func stopAnimation() { + func pullToRefreshAnimationDidEnd(view: PullToRefreshView) { activityIndicator.stopAnimating() + labelTitle.text = "" } - func changeProgress(progress: CGFloat) { + func pullToRefresh(view: PullToRefreshView, progressDidChange progress: CGFloat) { } - func layoutLayers(superview: UIView) { + func pullToRefresh(view: PullToRefreshView, stateDidChange state: PullToRefreshViewState) { + switch state { + case .Loading: + labelTitle.text = "Loading" + case .PullToRefresh: + labelTitle.text = "Pull to refresh" + case .ReleaseToRefresh: + labelTitle.text = "Release to refresh" + } } -} +} \ No newline at end of file diff --git a/PullToRefreshDemo/CustomSubview.xib b/PullToRefreshDemo/CustomSubview.xib index 0631a14..0bea6df 100644 --- a/PullToRefreshDemo/CustomSubview.xib +++ b/PullToRefreshDemo/CustomSubview.xib @@ -1,7 +1,7 @@ - + - + @@ -10,13 +10,13 @@ - + @@ -36,7 +36,7 @@ - + diff --git a/PullToRefreshDemo/PacmanAnimator.swift b/PullToRefreshDemo/PacmanAnimator.swift index 85ad2f6..c299817 100644 --- a/PullToRefreshDemo/PacmanAnimator.swift +++ b/PullToRefreshDemo/PacmanAnimator.swift @@ -26,12 +26,14 @@ import Refresher import QuartzCore import UIKit -class PacmanAnimator: PullToRefreshViewAnimator { +class PacmanAnimator: UIView, PullToRefreshViewAnimator { private var layerLoader: CAShapeLayer = CAShapeLayer() private var layerSeparator: CAShapeLayer = CAShapeLayer() - init() { + override init(frame: CGRect) { + + super.init(frame: frame) layerLoader.lineWidth = 8 layerLoader.strokeColor = UIColor(red: 0, green: 0.7, blue: 1, alpha: 1).CGColor @@ -41,10 +43,28 @@ class PacmanAnimator: PullToRefreshViewAnimator { layerSeparator.lineWidth = 8 layerSeparator.strokeColor = UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 1).CGColor layerSeparator.fillColor = UIColor.clearColor().CGColor + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func pullToRefresh(view: PullToRefreshView, progressDidChange progress: CGFloat) { + + self.layerLoader.strokeEnd = progress + } + + func pullToRefresh(view: PullToRefreshView, stateDidChange state: PullToRefreshViewState) { } - func startAnimation() { + func pullToRefreshAnimationDidEnd(view: PullToRefreshView) { + + self.layerLoader.removeAllAnimations() + + } + + func pullToRefreshAnimationDidStart(view: PullToRefreshView) { var pathAnimationEnd = CABasicAnimation(keyPath: "strokeEnd") pathAnimationEnd.duration = 0.5 @@ -63,31 +83,25 @@ class PacmanAnimator: PullToRefreshViewAnimator { self.layerLoader.addAnimation(pathAnimationStart, forKey: "strokeStartAnimation") } - func stopAnimation() { + override func layoutSubviews() { - self.layerLoader.removeAllAnimations() - } - - func layoutLayers(superview: UIView) { + super.layoutSubviews() - if layerSeparator.superlayer == nil { - superview.layer.addSublayer(layerSeparator) - } - if layerLoader.superlayer == nil { - superview.layer.addSublayer(layerLoader) + if let superview = superview { + if layerSeparator.superlayer == nil { + superview.layer.addSublayer(layerSeparator) + } + if layerLoader.superlayer == nil { + superview.layer.addSublayer(layerLoader) + } + var center = CGPoint(x: 30, y: superview.frame.size.height / 2) + var bezierPathLoader = UIBezierPath(arcCenter: center, radius: CGFloat(10), startAngle: CGFloat(0), endAngle: CGFloat(2 * M_PI), clockwise: true) + var bezierPathSeparator = UIBezierPath() + bezierPathSeparator.moveToPoint(CGPointMake(0, superview.frame.height - 1)) + bezierPathSeparator.addLineToPoint(CGPoint(x: superview.frame.width, y: superview.frame.height - 1)) + + layerLoader.path = bezierPathLoader.CGPath + layerSeparator.path = bezierPathLoader.CGPath } - var center = CGPoint(x: 30, y: superview.frame.size.height / 2) - var bezierPathLoader = UIBezierPath(arcCenter: center, radius: CGFloat(10), startAngle: CGFloat(0), endAngle: CGFloat(2 * M_PI), clockwise: true) - var bezierPathSeparator = UIBezierPath() - bezierPathSeparator.moveToPoint(CGPointMake(0, superview.frame.height - 1)) - bezierPathSeparator.addLineToPoint(CGPoint(x: superview.frame.width, y: superview.frame.height - 1)) - - layerLoader.path = bezierPathLoader.CGPath - layerSeparator.path = bezierPathLoader.CGPath - } - - func changeProgress(progress: CGFloat) { - - self.layerLoader.strokeEnd = progress } } \ No newline at end of file diff --git a/PullToRefreshDemo/ViewController.swift b/PullToRefreshDemo/ViewController.swift index 639b219..d4ab259 100644 --- a/PullToRefreshDemo/ViewController.swift +++ b/PullToRefreshDemo/ViewController.swift @@ -43,15 +43,14 @@ class ViewController: UIViewController { } } - tableView.addPullToRefreshWithAction({ - NSOperationQueue().addOperationWithBlock { - sleep(2) - NSOperationQueue.mainQueue().addOperationWithBlock { - self.tableView.stopPullToRefresh() - } + let pacmanAnimator = PacmanAnimator(frame: CGRectMake(0, 0, 320, 80)) + tableView.addPullToRefreshWithAction({ () -> () in + sleep(2) + NSOperationQueue.mainQueue().addOperationWithBlock { + self.tableView.stopPullToRefresh() } - }, withAnimator: PacmanAnimator()) - */ + }, withAnimator: pacmanAnimator, withSubview: pacmanAnimator) + */ if let customSubview = NSBundle.mainBundle().loadNibNamed("CustomSubview", owner: self, options: nil).first as? CustomSubview { tableView.addPullToRefreshWithAction({ @@ -63,6 +62,18 @@ class ViewController: UIViewController { } }, withAnimator: customSubview, withSubview: customSubview) } + + /* + + tableView.addPullToRefreshWithAction { + NSOperationQueue().addOperationWithBlock { + sleep(2) + NSOperationQueue.mainQueue().addOperationWithBlock { + self.tableView.stopPullToRefresh() + } + } + } + */ } override func viewDidAppear(animated: Bool) { diff --git a/Refresher/Animator.swift b/Refresher/Animator.swift index 7477f48..6d66a34 100644 --- a/Refresher/Animator.swift +++ b/Refresher/Animator.swift @@ -25,84 +25,75 @@ import Foundation import QuartzCore import UIKit -class Animator: PullToRefreshViewAnimator { +internal class AnimatorView: UIView { + + private let titleLabel: UILabel = { + let label = UILabel() + label.setTranslatesAutoresizingMaskIntoConstraints(false) + return label + }() + + private let activityIndicatorView: UIActivityIndicatorView = { + let activity = UIActivityIndicatorView(activityIndicatorStyle: .Gray) + activity.setTranslatesAutoresizingMaskIntoConstraints(false) + return activity + }() - private class AnimatorView: UIView { + override init(frame: CGRect) { - private var layerLoader: CAShapeLayer = CAShapeLayer() - private var layerSeparator: CAShapeLayer = CAShapeLayer() - private let labelTitle = UILabel() + super.init(frame: frame) + autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight + addSubview(titleLabel) + addSubview(activityIndicatorView) - init() { - - super.init - layer.addSublayer(layerLoader) - - if layerSeparator.superlayer == nil { - superview.layer.addSublayer(layerSeparator) - } - var bezierPathLoader = UIBezierPath() - bezierPathLoader.moveToPoint(CGPointMake(0, superview.frame.height - 3)) - bezierPathLoader.addLineToPoint(CGPoint(x: superview.frame.width, y: superview.frame.height - 3)) - - var bezierPathSeparator = UIBezierPath() - bezierPathSeparator.moveToPoint(CGPointMake(0, superview.frame.height - 1)) - bezierPathSeparator.addLineToPoint(CGPoint(x: superview.frame.width, y: superview.frame.height - 1)) - - layerLoader.path = bezierPathLoader.CGPath - layerSeparator.path = bezierPathSeparator.CGPath - } + let leftActivityConstraint = NSLayoutConstraint(item: activityIndicatorView, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: 16) + let centerActivityConstraint = NSLayoutConstraint(item: activityIndicatorView, attribute: .CenterY, relatedBy: .Equal, toItem: self, attribute: .CenterY, multiplier: 1, constant: 0) + + let leftTitleConstraint = NSLayoutConstraint(item: titleLabel, attribute: .Left, relatedBy: .Equal, toItem: activityIndicatorView, attribute: .Right, multiplier: 1, constant: 16) + let centerTitleConstraint = NSLayoutConstraint(item: self, attribute: .CenterY, relatedBy: .Equal, toItem: titleLabel, attribute: .CenterY, multiplier: 1, constant: 0) - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + addConstraints([leftActivityConstraint, centerActivityConstraint, leftTitleConstraint, centerTitleConstraint]) + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") } +} - init() { +class Animator: PullToRefreshViewAnimator { - layerLoader.lineWidth = 4 - layerLoader.strokeColor = UIColor(red: 0, green: 0.48, blue: 1, alpha: 1).CGColor - layerLoader.strokeEnd = 0 - - layerSeparator.lineWidth = 1 - layerSeparator.strokeColor = UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 1).CGColor - + internal let animatorView: AnimatorView + + init(frame: CGRect) { + + animatorView = AnimatorView(frame: frame) } func pullToRefreshAnimationDidStart(view: PullToRefreshView) { - var pathAnimationEnd = CABasicAnimation(keyPath: "strokeEnd") - pathAnimationEnd.duration = 0.5 - pathAnimationEnd.repeatCount = 100 - pathAnimationEnd.autoreverses = true - pathAnimationEnd.fromValue = 0.2 - pathAnimationEnd.toValue = 1 - self.layerLoader.addAnimation(pathAnimationEnd, forKey: "strokeEndAnimation") - - var pathAnimationStart = CABasicAnimation(keyPath: "strokeStart") - pathAnimationStart.duration = 0.5 - pathAnimationStart.repeatCount = 100 - pathAnimationStart.autoreverses = true - pathAnimationStart.fromValue = 0 - pathAnimationStart.toValue = 0.8 - self.layerLoader.addAnimation(pathAnimationStart, forKey: "strokeStartAnimation") + animatorView.activityIndicatorView.startAnimating() + animatorView.titleLabel.text = "Loading" } func pullToRefreshAnimationDidEnd(view: PullToRefreshView) { - self.layerLoader.removeAllAnimations() - } - - func pullToRefresh(view: PullToRefreshView, stateDidChange state: PullToRefreshViewState) { - + animatorView.activityIndicatorView.stopAnimating() + animatorView.titleLabel.text = "" } func pullToRefresh(view: PullToRefreshView, progressDidChange progress: CGFloat) { } - func changeProgress(progress: CGFloat) { + func pullToRefresh(view: PullToRefreshView, stateDidChange state: PullToRefreshViewState) { - self.layerLoader.strokeEnd = progress + switch state { + case .Loading: + animatorView.titleLabel.text = "Loading" + case .PullToRefresh: + animatorView.titleLabel.text = "Pull to refresh" + case .ReleaseToRefresh: + animatorView.titleLabel.text = "Release to refresh" + } } } \ No newline at end of file diff --git a/Refresher/PullToRefreshExtension.swift b/Refresher/PullToRefreshExtension.swift index d6dcf19..8496d5d 100644 --- a/Refresher/PullToRefreshExtension.swift +++ b/Refresher/PullToRefreshExtension.swift @@ -46,14 +46,6 @@ extension UIScrollView { addSubview(pullToRefreshView) } - // If you want to use your custom animation when pull to refresh is animating, you should call this method and pass your animator object. - public func addPullToRefreshWithAction(action :(() -> ()), withAnimator animator: PullToRefreshViewAnimator) { - - var pullToRefreshView = PullToRefreshView(action: action, frame: CGRectMake(0, -pullToRefreshDefaultHeight, self.frame.size.width, pullToRefreshDefaultHeight), animator: animator) - pullToRefreshView.tag = pullToRefreshTag - addSubview(pullToRefreshView) - } - // If you want to use your custom animation and custom subview when pull to refresh is animating, you should call this method and pass your animator and view objects. public func addPullToRefreshWithAction(action :(() -> ()), withAnimator animator: PullToRefreshViewAnimator, withSubview subview: UIView) { diff --git a/Refresher/PullToRefreshView.swift b/Refresher/PullToRefreshView.swift index b0e2bc3..f96fd36 100644 --- a/Refresher/PullToRefreshView.swift +++ b/Refresher/PullToRefreshView.swift @@ -30,6 +30,8 @@ let contentOffsetKeyPath = "contentOffset" public enum PullToRefreshViewState { case Loading + case PullToRefresh + case ReleaseToRefresh } public protocol PullToRefreshViewAnimator { @@ -46,7 +48,7 @@ public class PullToRefreshView: UIView { private var scrollViewBouncesDefaultValue: Bool = false private var scrollViewInsetsDefaultValue: UIEdgeInsets = UIEdgeInsetsZero - private var animator: PullToRefreshViewAnimator = Animator() + private var animator: PullToRefreshViewAnimator private var action: (() -> ()) = {} private var previousOffset: CGFloat = 0 @@ -67,34 +69,38 @@ public class PullToRefreshView: UIView { convenience init(action :(() -> ()), frame: CGRect) { - self.init(frame: frame) + var bounds = frame + bounds.origin.y = 0 + let animator = Animator(frame: bounds) + self.init(frame: frame, animator: animator) self.action = action; + addSubview(animator.animatorView) } convenience init(action :(() -> ()), frame: CGRect, animator: PullToRefreshViewAnimator, subview: UIView) { - self.init(frame: frame) + self.init(frame: frame, animator: animator) self.action = action; - self.animator = animator subview.frame = self.bounds addSubview(subview) } convenience init(action :(() -> ()), frame: CGRect, animator: PullToRefreshViewAnimator) { - self.init(frame: frame) + self.init(frame: frame, animator: animator) self.action = action; - self.animator = animator } - override init(frame: CGRect) { - + init(frame: CGRect, animator: PullToRefreshViewAnimator) { + + self.animator = animator super.init(frame: frame) self.autoresizingMask = .FlexibleWidth } public required init(coder aDecoder: NSCoder) { + self.animator = Animator(frame: CGRectZero) super.init(coder: aDecoder) // Currently it is not supported to load view from nib } @@ -130,16 +136,16 @@ public class PullToRefreshView: UIView { if (offsetWithoutInsets < -self.frame.size.height) { if (scrollView.dragging == false && loading == false) { loading = true - } else if (loading == true) { - //labelTitle.text = NSLocalizedString("Loading ...", comment: "Refresher") + } else if (loading) { + self.animator.pullToRefresh(self, stateDidChange: .Loading) } else { - //labelTitle.text = NSLocalizedString("Release to refresh", comment: "Refresher") + self.animator.pullToRefresh(self, stateDidChange: .ReleaseToRefresh) animator.pullToRefresh(self, progressDidChange: -offsetWithoutInsets / self.frame.size.height) } - } else if (loading == true) { - //labelTitle.text = NSLocalizedString("Loading ...", comment: "Refresher") + } else if (loading) { + self.animator.pullToRefresh(self, stateDidChange: .Loading) } else if (offsetWithoutInsets < 0) { - //labelTitle.text = NSLocalizedString("Pull to refresh", comment: "Refresher") + self.animator.pullToRefresh(self, stateDidChange: .PullToRefresh) animator.pullToRefresh(self, progressDidChange: -offsetWithoutInsets / self.frame.size.height) } previousOffset = scrollView.contentOffset.y From fd17de6077473cc6e6fd480af47842e4e5f90014 Mon Sep 17 00:00:00 2001 From: Josip Cavar Date: Sat, 27 Jun 2015 09:56:30 +0200 Subject: [PATCH 08/12] rename animator to delegate --- PullToRefreshDemo/CustomSubview.swift | 2 +- PullToRefreshDemo/PacmanAnimator.swift | 2 +- Refresher/Animator.swift | 2 +- Refresher/PullToRefreshExtension.swift | 2 +- Refresher/PullToRefreshView.swift | 10 +++++----- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/PullToRefreshDemo/CustomSubview.swift b/PullToRefreshDemo/CustomSubview.swift index 79cd611..ef42629 100644 --- a/PullToRefreshDemo/CustomSubview.swift +++ b/PullToRefreshDemo/CustomSubview.swift @@ -10,7 +10,7 @@ import UIKit import Refresher -class CustomSubview: UIView, PullToRefreshViewAnimator { +class CustomSubview: UIView, PullToRefreshViewDelegate { @IBOutlet weak var activityIndicator: UIActivityIndicatorView! @IBOutlet weak var labelTitle: UILabel! diff --git a/PullToRefreshDemo/PacmanAnimator.swift b/PullToRefreshDemo/PacmanAnimator.swift index c299817..fc33602 100644 --- a/PullToRefreshDemo/PacmanAnimator.swift +++ b/PullToRefreshDemo/PacmanAnimator.swift @@ -26,7 +26,7 @@ import Refresher import QuartzCore import UIKit -class PacmanAnimator: UIView, PullToRefreshViewAnimator { +class PacmanAnimator: UIView, PullToRefreshViewDelegate { private var layerLoader: CAShapeLayer = CAShapeLayer() private var layerSeparator: CAShapeLayer = CAShapeLayer() diff --git a/Refresher/Animator.swift b/Refresher/Animator.swift index 6d66a34..e60fba9 100644 --- a/Refresher/Animator.swift +++ b/Refresher/Animator.swift @@ -60,7 +60,7 @@ internal class AnimatorView: UIView { } } -class Animator: PullToRefreshViewAnimator { +class Animator: PullToRefreshViewDelegate { internal let animatorView: AnimatorView diff --git a/Refresher/PullToRefreshExtension.swift b/Refresher/PullToRefreshExtension.swift index 8496d5d..12592c1 100644 --- a/Refresher/PullToRefreshExtension.swift +++ b/Refresher/PullToRefreshExtension.swift @@ -47,7 +47,7 @@ extension UIScrollView { } // If you want to use your custom animation and custom subview when pull to refresh is animating, you should call this method and pass your animator and view objects. - public func addPullToRefreshWithAction(action :(() -> ()), withAnimator animator: PullToRefreshViewAnimator, withSubview subview: UIView) { + public func addPullToRefreshWithAction(action :(() -> ()), withAnimator animator: PullToRefreshViewDelegate, withSubview subview: UIView) { let height = subview.frame.height var pullToRefreshView = PullToRefreshView(action: action, frame: CGRectMake(0, -height, self.frame.size.width, height), animator: animator, subview: subview) diff --git a/Refresher/PullToRefreshView.swift b/Refresher/PullToRefreshView.swift index f96fd36..3a6b29b 100644 --- a/Refresher/PullToRefreshView.swift +++ b/Refresher/PullToRefreshView.swift @@ -34,7 +34,7 @@ public enum PullToRefreshViewState { case ReleaseToRefresh } -public protocol PullToRefreshViewAnimator { +public protocol PullToRefreshViewDelegate { func pullToRefreshAnimationDidStart(view: PullToRefreshView) func pullToRefreshAnimationDidEnd(view: PullToRefreshView) @@ -48,7 +48,7 @@ public class PullToRefreshView: UIView { private var scrollViewBouncesDefaultValue: Bool = false private var scrollViewInsetsDefaultValue: UIEdgeInsets = UIEdgeInsetsZero - private var animator: PullToRefreshViewAnimator + private var animator: PullToRefreshViewDelegate private var action: (() -> ()) = {} private var previousOffset: CGFloat = 0 @@ -77,7 +77,7 @@ public class PullToRefreshView: UIView { addSubview(animator.animatorView) } - convenience init(action :(() -> ()), frame: CGRect, animator: PullToRefreshViewAnimator, subview: UIView) { + convenience init(action :(() -> ()), frame: CGRect, animator: PullToRefreshViewDelegate, subview: UIView) { self.init(frame: frame, animator: animator) self.action = action; @@ -85,13 +85,13 @@ public class PullToRefreshView: UIView { addSubview(subview) } - convenience init(action :(() -> ()), frame: CGRect, animator: PullToRefreshViewAnimator) { + convenience init(action :(() -> ()), frame: CGRect, animator: PullToRefreshViewDelegate) { self.init(frame: frame, animator: animator) self.action = action; } - init(frame: CGRect, animator: PullToRefreshViewAnimator) { + init(frame: CGRect, animator: PullToRefreshViewDelegate) { self.animator = animator super.init(frame: frame) From a2aa82d5ca99653a2317af42e5e89652461be25e Mon Sep 17 00:00:00 2001 From: Josip Cavar Date: Sat, 27 Jun 2015 10:31:39 +0200 Subject: [PATCH 09/12] example fixes --- PullToRefreshDemo/BeatAnimator.swift | 78 +++++++++++++++----------- PullToRefreshDemo/PacmanAnimator.swift | 16 +++--- PullToRefreshDemo/ViewController.swift | 32 ++++++----- 3 files changed, 69 insertions(+), 57 deletions(-) diff --git a/PullToRefreshDemo/BeatAnimator.swift b/PullToRefreshDemo/BeatAnimator.swift index d73ff5b..d54179a 100644 --- a/PullToRefreshDemo/BeatAnimator.swift +++ b/PullToRefreshDemo/BeatAnimator.swift @@ -25,13 +25,15 @@ import Foundation import Refresher import QuartzCore -/* -class BeatAnimator: PullToRefreshViewAnimator { + +class BeatAnimator: UIView, PullToRefreshViewDelegate { - private var layerLoader: CAShapeLayer = CAShapeLayer() - private var layerSeparator: CAShapeLayer = CAShapeLayer() + private var layerLoader = CAShapeLayer() + private var layerSeparator = CAShapeLayer() - init() { + override init(frame: CGRect) { + + super.init(frame: frame) layerLoader.lineWidth = 4 layerLoader.strokeColor = UIColor(red: 0.13, green: 0.79, blue: 0.31, alpha: 1).CGColor @@ -39,56 +41,64 @@ class BeatAnimator: PullToRefreshViewAnimator { layerSeparator.lineWidth = 1 layerSeparator.strokeColor = UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 1).CGColor + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func pullToRefresh(view: PullToRefreshView, progressDidChange progress: CGFloat) { + layerLoader.strokeEnd = progress } - func startAnimation() { + func pullToRefresh(view: PullToRefreshView, stateDidChange state: PullToRefreshViewState) { - var pathAnimationEnd = CABasicAnimation(keyPath: "strokeEnd") + } + + func pullToRefreshAnimationDidEnd(view: PullToRefreshView) { + + layerLoader.removeAllAnimations() + + } + + func pullToRefreshAnimationDidStart(view: PullToRefreshView) { + + let pathAnimationEnd = CABasicAnimation(keyPath: "strokeEnd") pathAnimationEnd.duration = 0.5 pathAnimationEnd.repeatCount = 100 pathAnimationEnd.autoreverses = true pathAnimationEnd.fromValue = 1 pathAnimationEnd.toValue = 0.8 - self.layerLoader.addAnimation(pathAnimationEnd, forKey: "strokeEndAnimation") + layerLoader.addAnimation(pathAnimationEnd, forKey: "strokeEndAnimation") - var pathAnimationStart = CABasicAnimation(keyPath: "strokeStart") + let pathAnimationStart = CABasicAnimation(keyPath: "strokeStart") pathAnimationStart.duration = 0.5 pathAnimationStart.repeatCount = 100 pathAnimationStart.autoreverses = true pathAnimationStart.fromValue = 0 pathAnimationStart.toValue = 0.2 - self.layerLoader.addAnimation(pathAnimationStart, forKey: "strokeStartAnimation") + layerLoader.addAnimation(pathAnimationStart, forKey: "strokeStartAnimation") } - func stopAnimation() { + override func layoutSubviews() { - self.layerLoader.removeAllAnimations() - } - - func layoutLayers(superview: UIView) { + super.layoutSubviews() - if layerLoader.superlayer == nil { + if let superview = superview { superview.layer.addSublayer(layerLoader) - } - if layerSeparator.superlayer == nil { superview.layer.addSublayer(layerSeparator) + + let bezierPathLoader = UIBezierPath() + bezierPathLoader.moveToPoint(CGPointMake(0, superview.frame.height - 3)) + bezierPathLoader.addLineToPoint(CGPoint(x: superview.frame.width, y: superview.frame.height - 3)) + + let bezierPathSeparator = UIBezierPath() + bezierPathSeparator.moveToPoint(CGPointMake(0, superview.frame.height - 1)) + bezierPathSeparator.addLineToPoint(CGPoint(x: superview.frame.width, y: superview.frame.height - 1)) + + layerLoader.path = bezierPathLoader.CGPath + layerSeparator.path = bezierPathSeparator.CGPath } - var bezierPathLoader = UIBezierPath() - bezierPathLoader.moveToPoint(CGPointMake(0, superview.frame.height - 3)) - bezierPathLoader.addLineToPoint(CGPoint(x: superview.frame.width, y: superview.frame.height - 3)) - - var bezierPathSeparator = UIBezierPath() - bezierPathSeparator.moveToPoint(CGPointMake(0, superview.frame.height - 1)) - bezierPathSeparator.addLineToPoint(CGPoint(x: superview.frame.width, y: superview.frame.height - 1)) - - layerLoader.path = bezierPathLoader.CGPath - layerSeparator.path = bezierPathSeparator.CGPath - } - - func changeProgress(progress: CGFloat) { - - self.layerLoader.strokeEnd = progress } } -*/ \ No newline at end of file diff --git a/PullToRefreshDemo/PacmanAnimator.swift b/PullToRefreshDemo/PacmanAnimator.swift index fc33602..7cfd885 100644 --- a/PullToRefreshDemo/PacmanAnimator.swift +++ b/PullToRefreshDemo/PacmanAnimator.swift @@ -28,8 +28,8 @@ import UIKit class PacmanAnimator: UIView, PullToRefreshViewDelegate { - private var layerLoader: CAShapeLayer = CAShapeLayer() - private var layerSeparator: CAShapeLayer = CAShapeLayer() + private var layerLoader = CAShapeLayer() + private var layerSeparator = CAShapeLayer() override init(frame: CGRect) { @@ -51,7 +51,7 @@ class PacmanAnimator: UIView, PullToRefreshViewDelegate { func pullToRefresh(view: PullToRefreshView, progressDidChange progress: CGFloat) { - self.layerLoader.strokeEnd = progress + layerLoader.strokeEnd = progress } func pullToRefresh(view: PullToRefreshView, stateDidChange state: PullToRefreshViewState) { @@ -60,27 +60,27 @@ class PacmanAnimator: UIView, PullToRefreshViewDelegate { func pullToRefreshAnimationDidEnd(view: PullToRefreshView) { - self.layerLoader.removeAllAnimations() + layerLoader.removeAllAnimations() } func pullToRefreshAnimationDidStart(view: PullToRefreshView) { - var pathAnimationEnd = CABasicAnimation(keyPath: "strokeEnd") + let pathAnimationEnd = CABasicAnimation(keyPath: "strokeEnd") pathAnimationEnd.duration = 0.5 pathAnimationEnd.repeatCount = 100 pathAnimationEnd.autoreverses = true pathAnimationEnd.fromValue = 1 pathAnimationEnd.toValue = 0.8 - self.layerLoader.addAnimation(pathAnimationEnd, forKey: "strokeEndAnimation") + layerLoader.addAnimation(pathAnimationEnd, forKey: "strokeEndAnimation") - var pathAnimationStart = CABasicAnimation(keyPath: "strokeStart") + let pathAnimationStart = CABasicAnimation(keyPath: "strokeStart") pathAnimationStart.duration = 0.5 pathAnimationStart.repeatCount = 100 pathAnimationStart.autoreverses = true pathAnimationStart.fromValue = 0 pathAnimationStart.toValue = 0.2 - self.layerLoader.addAnimation(pathAnimationStart, forKey: "strokeStartAnimation") + layerLoader.addAnimation(pathAnimationStart, forKey: "strokeStartAnimation") } override func layoutSubviews() { diff --git a/PullToRefreshDemo/ViewController.swift b/PullToRefreshDemo/ViewController.swift index d4ab259..e4b267e 100644 --- a/PullToRefreshDemo/ViewController.swift +++ b/PullToRefreshDemo/ViewController.swift @@ -31,27 +31,31 @@ class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + /* - tableView.addPullToRefreshWithAction { - + let beatAnimator = BeatAnimator(frame: CGRectMake(0, 0, 320, 80)) + tableView.addPullToRefreshWithAction({ () -> () in NSOperationQueue().addOperationWithBlock { - - sleep(5) + sleep(2) NSOperationQueue.mainQueue().addOperationWithBlock { self.tableView.stopPullToRefresh() } } - } + }, withAnimator: beatAnimator, withSubview: beatAnimator) + */ + let pacmanAnimator = PacmanAnimator(frame: CGRectMake(0, 0, 320, 80)) tableView.addPullToRefreshWithAction({ () -> () in - sleep(2) - NSOperationQueue.mainQueue().addOperationWithBlock { - self.tableView.stopPullToRefresh() + NSOperationQueue().addOperationWithBlock { + sleep(2) + NSOperationQueue.mainQueue().addOperationWithBlock { + self.tableView.stopPullToRefresh() + } } - }, withAnimator: pacmanAnimator, withSubview: pacmanAnimator) - */ - + }, withAnimator: pacmanAnimator, withSubview: pacmanAnimator) + + /* if let customSubview = NSBundle.mainBundle().loadNibNamed("CustomSubview", owner: self, options: nil).first as? CustomSubview { tableView.addPullToRefreshWithAction({ NSOperationQueue().addOperationWithBlock { @@ -63,8 +67,6 @@ class ViewController: UIViewController { }, withAnimator: customSubview, withSubview: customSubview) } - /* - tableView.addPullToRefreshWithAction { NSOperationQueue().addOperationWithBlock { sleep(2) @@ -72,8 +74,8 @@ class ViewController: UIViewController { self.tableView.stopPullToRefresh() } } - } - */ + } + */ } override func viewDidAppear(animated: Bool) { From 8d591b720f4da37d8579aae3efccb106bb41156a Mon Sep 17 00:00:00 2001 From: Josip Cavar Date: Sat, 27 Jun 2015 10:35:21 +0200 Subject: [PATCH 10/12] refactorings --- PullToRefreshDemo/BeatAnimator.swift | 12 ++++++++---- PullToRefreshDemo/PacmanAnimator.swift | 10 +++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/PullToRefreshDemo/BeatAnimator.swift b/PullToRefreshDemo/BeatAnimator.swift index d54179a..56540c5 100644 --- a/PullToRefreshDemo/BeatAnimator.swift +++ b/PullToRefreshDemo/BeatAnimator.swift @@ -28,8 +28,8 @@ import QuartzCore class BeatAnimator: UIView, PullToRefreshViewDelegate { - private var layerLoader = CAShapeLayer() - private var layerSeparator = CAShapeLayer() + private let layerLoader = CAShapeLayer() + private let layerSeparator = CAShapeLayer() override init(frame: CGRect) { @@ -86,8 +86,12 @@ class BeatAnimator: UIView, PullToRefreshViewDelegate { super.layoutSubviews() if let superview = superview { - superview.layer.addSublayer(layerLoader) - superview.layer.addSublayer(layerSeparator) + if layerLoader.superlayer == nil { + superview.layer.addSublayer(layerSeparator) + } + if layerSeparator.superlayer == nil { + superview.layer.addSublayer(layerSeparator) + } let bezierPathLoader = UIBezierPath() bezierPathLoader.moveToPoint(CGPointMake(0, superview.frame.height - 3)) diff --git a/PullToRefreshDemo/PacmanAnimator.swift b/PullToRefreshDemo/PacmanAnimator.swift index 7cfd885..145c146 100644 --- a/PullToRefreshDemo/PacmanAnimator.swift +++ b/PullToRefreshDemo/PacmanAnimator.swift @@ -28,8 +28,8 @@ import UIKit class PacmanAnimator: UIView, PullToRefreshViewDelegate { - private var layerLoader = CAShapeLayer() - private var layerSeparator = CAShapeLayer() + private let layerLoader = CAShapeLayer() + private let layerSeparator = CAShapeLayer() override init(frame: CGRect) { @@ -94,9 +94,9 @@ class PacmanAnimator: UIView, PullToRefreshViewDelegate { if layerLoader.superlayer == nil { superview.layer.addSublayer(layerLoader) } - var center = CGPoint(x: 30, y: superview.frame.size.height / 2) - var bezierPathLoader = UIBezierPath(arcCenter: center, radius: CGFloat(10), startAngle: CGFloat(0), endAngle: CGFloat(2 * M_PI), clockwise: true) - var bezierPathSeparator = UIBezierPath() + let center = CGPoint(x: 30, y: superview.frame.size.height / 2) + let bezierPathLoader = UIBezierPath(arcCenter: center, radius: CGFloat(10), startAngle: CGFloat(0), endAngle: CGFloat(2 * M_PI), clockwise: true) + let bezierPathSeparator = UIBezierPath() bezierPathSeparator.moveToPoint(CGPointMake(0, superview.frame.height - 1)) bezierPathSeparator.addLineToPoint(CGPoint(x: superview.frame.width, y: superview.frame.height - 1)) From d56f0162f869c6d795715c019ba92655a271e6c5 Mon Sep 17 00:00:00 2001 From: Josip Cavar Date: Sun, 2 Aug 2015 20:57:53 +0200 Subject: [PATCH 11/12] refactoring and api improvements --- Refresher/Animator.swift | 5 ----- Refresher/PullToRefreshExtension.swift | 23 +++++++++++++---------- Refresher/PullToRefreshView.swift | 23 ++++++----------------- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/Refresher/Animator.swift b/Refresher/Animator.swift index e60fba9..edbd595 100644 --- a/Refresher/Animator.swift +++ b/Refresher/Animator.swift @@ -40,7 +40,6 @@ internal class AnimatorView: UIView { }() override init(frame: CGRect) { - super.init(frame: frame) autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight addSubview(titleLabel) @@ -65,18 +64,15 @@ class Animator: PullToRefreshViewDelegate { internal let animatorView: AnimatorView init(frame: CGRect) { - animatorView = AnimatorView(frame: frame) } func pullToRefreshAnimationDidStart(view: PullToRefreshView) { - animatorView.activityIndicatorView.startAnimating() animatorView.titleLabel.text = "Loading" } func pullToRefreshAnimationDidEnd(view: PullToRefreshView) { - animatorView.activityIndicatorView.stopAnimating() animatorView.titleLabel.text = "" } @@ -86,7 +82,6 @@ class Animator: PullToRefreshViewDelegate { } func pullToRefresh(view: PullToRefreshView, stateDidChange state: PullToRefreshViewState) { - switch state { case .Loading: animatorView.titleLabel.text = "Loading" diff --git a/Refresher/PullToRefreshExtension.swift b/Refresher/PullToRefreshExtension.swift index 12592c1..571beb7 100644 --- a/Refresher/PullToRefreshExtension.swift +++ b/Refresher/PullToRefreshExtension.swift @@ -31,39 +31,42 @@ extension UIScrollView { // Actual UIView subclass which is added as subview to desired UIScrollView. If you want customize appearance of this object, do that after addPullToRefreshWithAction public var pullToRefreshView: PullToRefreshView? { - get { - var pullToRefreshView = viewWithTag(pullToRefreshTag) + let pullToRefreshView = viewWithTag(pullToRefreshTag) return pullToRefreshView as? PullToRefreshView } } // If you want to add pull to refresh functionality to your UIScrollView just call this method and pass action closure you want to execute while pull to refresh is animating. If you want to stop pull to refresh you must do that manually calling stopPullToRefreshView methods on your scroll view - public func addPullToRefreshWithAction(action :(() -> ())) { - - var pullToRefreshView = PullToRefreshView(action: action, frame: CGRectMake(0, -pullToRefreshDefaultHeight, self.frame.size.width, pullToRefreshDefaultHeight)) + public func addPullToRefreshWithAction(action:(() -> ())) { + let pullToRefreshView = PullToRefreshView(action: action, frame: CGRectMake(0, -pullToRefreshDefaultHeight, self.frame.size.width, pullToRefreshDefaultHeight)) pullToRefreshView.tag = pullToRefreshTag addSubview(pullToRefreshView) } // If you want to use your custom animation and custom subview when pull to refresh is animating, you should call this method and pass your animator and view objects. - public func addPullToRefreshWithAction(action :(() -> ()), withAnimator animator: PullToRefreshViewDelegate, withSubview subview: UIView) { - + public func addPullToRefreshWithAction(action:(() -> ()), withAnimator animator: PullToRefreshViewDelegate, withSubview subview: UIView) { let height = subview.frame.height - var pullToRefreshView = PullToRefreshView(action: action, frame: CGRectMake(0, -height, self.frame.size.width, height), animator: animator, subview: subview) + let pullToRefreshView = PullToRefreshView(action: action, frame: CGRectMake(0, -height, self.frame.size.width, height), animator: animator, subview: subview) + pullToRefreshView.tag = pullToRefreshTag + addSubview(pullToRefreshView) + } + + // + public func addPullToRefreshWithAction(action:(() -> ()), withAnimator animator: T) { + let height = animator.frame.height + let pullToRefreshView = PullToRefreshView(action: action, frame: CGRectMake(0, -height, self.frame.size.width, height), animator: animator, subview: animator) pullToRefreshView.tag = pullToRefreshTag addSubview(pullToRefreshView) } // Manually start pull to refresh public func startPullToRefresh() { - pullToRefreshView?.loading = true } // Manually stop pull to refresh public func stopPullToRefresh() { - pullToRefreshView?.loading = false } } \ No newline at end of file diff --git a/Refresher/PullToRefreshView.swift b/Refresher/PullToRefreshView.swift index 3a6b29b..69e1ac4 100644 --- a/Refresher/PullToRefreshView.swift +++ b/Refresher/PullToRefreshView.swift @@ -24,8 +24,8 @@ import UIKit import QuartzCore -var KVOContext = "RefresherKVOContext" -let contentOffsetKeyPath = "contentOffset" +private var KVOContext = "RefresherKVOContext" +private let ContentOffsetKeyPath = "contentOffset" public enum PullToRefreshViewState { @@ -44,7 +44,6 @@ public protocol PullToRefreshViewDelegate { public class PullToRefreshView: UIView { - private var scrollViewBouncesDefaultValue: Bool = false private var scrollViewInsetsDefaultValue: UIEdgeInsets = UIEdgeInsetsZero @@ -68,7 +67,6 @@ public class PullToRefreshView: UIView { //MARK: Object lifecycle methods convenience init(action :(() -> ()), frame: CGRect) { - var bounds = frame bounds.origin.y = 0 let animator = Animator(frame: bounds) @@ -78,7 +76,6 @@ public class PullToRefreshView: UIView { } convenience init(action :(() -> ()), frame: CGRect, animator: PullToRefreshViewDelegate, subview: UIView) { - self.init(frame: frame, animator: animator) self.action = action; subview.frame = self.bounds @@ -86,39 +83,34 @@ public class PullToRefreshView: UIView { } convenience init(action :(() -> ()), frame: CGRect, animator: PullToRefreshViewDelegate) { - self.init(frame: frame, animator: animator) self.action = action; } init(frame: CGRect, animator: PullToRefreshViewDelegate) { - self.animator = animator super.init(frame: frame) self.autoresizingMask = .FlexibleWidth } public required init(coder aDecoder: NSCoder) { - self.animator = Animator(frame: CGRectZero) super.init(coder: aDecoder) // Currently it is not supported to load view from nib } deinit { - var scrollView = superview as? UIScrollView - scrollView?.removeObserver(self, forKeyPath: contentOffsetKeyPath, context: &KVOContext) + scrollView?.removeObserver(self, forKeyPath: ContentOffsetKeyPath, context: &KVOContext) } //MARK: UIView methods public override func willMoveToSuperview(newSuperview: UIView!) { - - superview?.removeObserver(self, forKeyPath: contentOffsetKeyPath, context: &KVOContext) + superview?.removeObserver(self, forKeyPath: ContentOffsetKeyPath, context: &KVOContext) if let scrollView = newSuperview as? UIScrollView { - scrollView.addObserver(self, forKeyPath: contentOffsetKeyPath, options: .Initial, context: &KVOContext) + scrollView.addObserver(self, forKeyPath: ContentOffsetKeyPath, options: .Initial, context: &KVOContext) scrollViewBouncesDefaultValue = scrollView.bounces scrollViewInsetsDefaultValue = scrollView.contentInset } @@ -128,10 +120,9 @@ public class PullToRefreshView: UIView { //MARK: KVO methods public override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<()>) { - if (context == &KVOContext) { if let scrollView = superview as? UIScrollView where object as? NSObject == scrollView { - if keyPath == contentOffsetKeyPath { + if keyPath == ContentOffsetKeyPath { var offsetWithoutInsets = previousOffset + scrollViewInsetsDefaultValue.top if (offsetWithoutInsets < -self.frame.size.height) { if (scrollView.dragging == false && loading == false) { @@ -160,7 +151,6 @@ public class PullToRefreshView: UIView { //MARK: PullToRefreshView methods private func startAnimating() { - var scrollView = superview as! UIScrollView var insets = scrollView.contentInset insets.top += self.frame.size.height @@ -178,7 +168,6 @@ public class PullToRefreshView: UIView { } private func stopAnimating() { - self.animator.pullToRefreshAnimationDidEnd(self) var scrollView = superview as! UIScrollView scrollView.bounces = self.scrollViewBouncesDefaultValue From 687f3d483179c2b8d1c49e51bd76a59af38a505f Mon Sep 17 00:00:00 2001 From: Josip Cavar Date: Sun, 2 Aug 2015 20:58:01 +0200 Subject: [PATCH 12/12] example project --- PullToRefresh.xcodeproj/project.pbxproj | 30 +++++-- PullToRefreshDemo/Base.lproj/Main.storyboard | 79 +++++++++++++++++-- PullToRefreshDemo/BeatAnimator.swift | 10 +-- .../ChooseModeViewController.swift | 46 +++++++++++ PullToRefreshDemo/CustomSubview.swift | 3 - PullToRefreshDemo/PacmanAnimator.swift | 7 -- ...wift => PullToRefreshViewController.swift} | 77 +++++++++--------- 7 files changed, 184 insertions(+), 68 deletions(-) create mode 100644 PullToRefreshDemo/ChooseModeViewController.swift rename PullToRefreshDemo/{ViewController.swift => PullToRefreshViewController.swift} (59%) diff --git a/PullToRefresh.xcodeproj/project.pbxproj b/PullToRefresh.xcodeproj/project.pbxproj index 9bbe414..f5b64ae 100644 --- a/PullToRefresh.xcodeproj/project.pbxproj +++ b/PullToRefresh.xcodeproj/project.pbxproj @@ -22,7 +22,8 @@ C7AC6CFC19A894DF007107DF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7AC6CF619A894DF007107DF /* AppDelegate.swift */; }; C7AC6CFD19A894DF007107DF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C7AC6CF719A894DF007107DF /* Main.storyboard */; }; C7AC6CFE19A894DF007107DF /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C7AC6CF919A894DF007107DF /* Images.xcassets */; }; - C7AC6D0019A894DF007107DF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7AC6CFB19A894DF007107DF /* ViewController.swift */; }; + C7AC6D0019A894DF007107DF /* PullToRefreshViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7AC6CFB19A894DF007107DF /* PullToRefreshViewController.swift */; }; + C7AEDC171B6D53D100E09098 /* ChooseModeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7AEDC161B6D53D100E09098 /* ChooseModeViewController.swift */; }; C7B53A431AC996B80021914B /* CustomSubview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7B53A421AC996B80021914B /* CustomSubview.swift */; }; C7B53A451AC9971C0021914B /* CustomSubview.xib in Resources */ = {isa = PBXBuildFile; fileRef = C7B53A441AC9971C0021914B /* CustomSubview.xib */; }; C7DA91CD19B31B7C00C4012B /* Animator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DA91CC19B31B7C00C4012B /* Animator.swift */; }; @@ -84,7 +85,8 @@ C7AC6CF819A894DF007107DF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; C7AC6CF919A894DF007107DF /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; C7AC6CFA19A894DF007107DF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C7AC6CFB19A894DF007107DF /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + C7AC6CFB19A894DF007107DF /* PullToRefreshViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PullToRefreshViewController.swift; sourceTree = ""; }; + C7AEDC161B6D53D100E09098 /* ChooseModeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChooseModeViewController.swift; sourceTree = ""; }; C7B53A421AC996B80021914B /* CustomSubview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomSubview.swift; sourceTree = ""; }; C7B53A441AC9971C0021914B /* CustomSubview.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CustomSubview.xib; sourceTree = ""; }; C7D1CF8F199BB3C8009FD485 /* PullToRefreshDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PullToRefreshDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -168,16 +170,33 @@ isa = PBXGroup; children = ( C7AC6CF619A894DF007107DF /* AppDelegate.swift */, - C7AC6CFB19A894DF007107DF /* ViewController.swift */, + C7AEDC181B6D53DA00E09098 /* ViewController */, C7AC6CF719A894DF007107DF /* Main.storyboard */, C7AC6CF919A894DF007107DF /* Images.xcassets */, C7956A3D19A8966400CF6484 /* Supporting Files */, + C7AEDC191B6D53E000E09098 /* Animator */, + ); + path = PullToRefreshDemo; + sourceTree = ""; + }; + C7AEDC181B6D53DA00E09098 /* ViewController */ = { + isa = PBXGroup; + children = ( + C7AC6CFB19A894DF007107DF /* PullToRefreshViewController.swift */, + C7AEDC161B6D53D100E09098 /* ChooseModeViewController.swift */, + ); + name = ViewController; + sourceTree = ""; + }; + C7AEDC191B6D53E000E09098 /* Animator */ = { + isa = PBXGroup; + children = ( C7A355A119B612D5000DDC72 /* BeatAnimator.swift */, C7A355D019B62125000DDC72 /* PacmanAnimator.swift */, C7B53A421AC996B80021914B /* CustomSubview.swift */, C7B53A441AC9971C0021914B /* CustomSubview.xib */, ); - path = PullToRefreshDemo; + name = Animator; sourceTree = ""; }; C7D1CF86199BB3C8009FD485 = { @@ -368,8 +387,9 @@ files = ( C7A355D119B62125000DDC72 /* PacmanAnimator.swift in Sources */, C7B53A431AC996B80021914B /* CustomSubview.swift in Sources */, - C7AC6D0019A894DF007107DF /* ViewController.swift in Sources */, + C7AC6D0019A894DF007107DF /* PullToRefreshViewController.swift in Sources */, C7AC6CFC19A894DF007107DF /* AppDelegate.swift in Sources */, + C7AEDC171B6D53D100E09098 /* ChooseModeViewController.swift in Sources */, C7A355A219B612D5000DDC72 /* BeatAnimator.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/PullToRefreshDemo/Base.lproj/Main.storyboard b/PullToRefreshDemo/Base.lproj/Main.storyboard index b9bc20b..6401a8e 100644 --- a/PullToRefreshDemo/Base.lproj/Main.storyboard +++ b/PullToRefreshDemo/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -14,27 +14,94 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + @@ -57,7 +124,7 @@ - + diff --git a/PullToRefreshDemo/BeatAnimator.swift b/PullToRefreshDemo/BeatAnimator.swift index 56540c5..0cc3b43 100644 --- a/PullToRefreshDemo/BeatAnimator.swift +++ b/PullToRefreshDemo/BeatAnimator.swift @@ -32,7 +32,6 @@ class BeatAnimator: UIView, PullToRefreshViewDelegate { private let layerSeparator = CAShapeLayer() override init(frame: CGRect) { - super.init(frame: frame) layerLoader.lineWidth = 4 @@ -48,7 +47,6 @@ class BeatAnimator: UIView, PullToRefreshViewDelegate { } func pullToRefresh(view: PullToRefreshView, progressDidChange progress: CGFloat) { - layerLoader.strokeEnd = progress } @@ -57,13 +55,10 @@ class BeatAnimator: UIView, PullToRefreshViewDelegate { } func pullToRefreshAnimationDidEnd(view: PullToRefreshView) { - layerLoader.removeAllAnimations() - } func pullToRefreshAnimationDidStart(view: PullToRefreshView) { - let pathAnimationEnd = CABasicAnimation(keyPath: "strokeEnd") pathAnimationEnd.duration = 0.5 pathAnimationEnd.repeatCount = 100 @@ -82,17 +77,14 @@ class BeatAnimator: UIView, PullToRefreshViewDelegate { } override func layoutSubviews() { - super.layoutSubviews() - if let superview = superview { if layerLoader.superlayer == nil { - superview.layer.addSublayer(layerSeparator) + superview.layer.addSublayer(layerLoader) } if layerSeparator.superlayer == nil { superview.layer.addSublayer(layerSeparator) } - let bezierPathLoader = UIBezierPath() bezierPathLoader.moveToPoint(CGPointMake(0, superview.frame.height - 3)) bezierPathLoader.addLineToPoint(CGPoint(x: superview.frame.width, y: superview.frame.height - 3)) diff --git a/PullToRefreshDemo/ChooseModeViewController.swift b/PullToRefreshDemo/ChooseModeViewController.swift new file mode 100644 index 0000000..c415334 --- /dev/null +++ b/PullToRefreshDemo/ChooseModeViewController.swift @@ -0,0 +1,46 @@ +// +// ChooseModeViewController.swift +// PullToRefresh +// +// Created by Josip Cavar on 01/08/15. +// Copyright (c) 2015 Josip Cavar. All rights reserved. +// + +import UIKit + +class ChooseModeViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + @IBAction func defaultAction(sender: AnyObject) { + showControllerWithMode(.Default) + } + + @IBAction func beatAction(sender: AnyObject) { + showControllerWithMode(.Beat) + } + + @IBAction func pacmanAction(sender: AnyObject) { + showControllerWithMode(.Pacman) + } + + @IBAction func customAction(sender: AnyObject) { + showControllerWithMode(.Custom) + } + + func showControllerWithMode(mode: ExampleMode) { + if let pullToRefreshViewControler = self.storyboard?.instantiateViewControllerWithIdentifier("PullToRefreshViewController") as? PullToRefreshViewController { + pullToRefreshViewControler.exampleMode = mode + navigationController?.pushViewController(pullToRefreshViewControler, animated: true) + } + } +} diff --git a/PullToRefreshDemo/CustomSubview.swift b/PullToRefreshDemo/CustomSubview.swift index ef42629..d2a4d39 100644 --- a/PullToRefreshDemo/CustomSubview.swift +++ b/PullToRefreshDemo/CustomSubview.swift @@ -15,15 +15,12 @@ class CustomSubview: UIView, PullToRefreshViewDelegate { @IBOutlet weak var activityIndicator: UIActivityIndicatorView! @IBOutlet weak var labelTitle: UILabel! - func pullToRefreshAnimationDidStart(view: PullToRefreshView) { - activityIndicator.startAnimating() labelTitle.text = "Loading" } func pullToRefreshAnimationDidEnd(view: PullToRefreshView) { - activityIndicator.stopAnimating() labelTitle.text = "" } diff --git a/PullToRefreshDemo/PacmanAnimator.swift b/PullToRefreshDemo/PacmanAnimator.swift index 145c146..ef42cfe 100644 --- a/PullToRefreshDemo/PacmanAnimator.swift +++ b/PullToRefreshDemo/PacmanAnimator.swift @@ -32,7 +32,6 @@ class PacmanAnimator: UIView, PullToRefreshViewDelegate { private let layerSeparator = CAShapeLayer() override init(frame: CGRect) { - super.init(frame: frame) layerLoader.lineWidth = 8 @@ -50,7 +49,6 @@ class PacmanAnimator: UIView, PullToRefreshViewDelegate { } func pullToRefresh(view: PullToRefreshView, progressDidChange progress: CGFloat) { - layerLoader.strokeEnd = progress } @@ -59,13 +57,10 @@ class PacmanAnimator: UIView, PullToRefreshViewDelegate { } func pullToRefreshAnimationDidEnd(view: PullToRefreshView) { - layerLoader.removeAllAnimations() - } func pullToRefreshAnimationDidStart(view: PullToRefreshView) { - let pathAnimationEnd = CABasicAnimation(keyPath: "strokeEnd") pathAnimationEnd.duration = 0.5 pathAnimationEnd.repeatCount = 100 @@ -84,9 +79,7 @@ class PacmanAnimator: UIView, PullToRefreshViewDelegate { } override func layoutSubviews() { - super.layoutSubviews() - if let superview = superview { if layerSeparator.superlayer == nil { superview.layer.addSublayer(layerSeparator) diff --git a/PullToRefreshDemo/ViewController.swift b/PullToRefreshDemo/PullToRefreshViewController.swift similarity index 59% rename from PullToRefreshDemo/ViewController.swift rename to PullToRefreshDemo/PullToRefreshViewController.swift index e4b267e..bfad565 100644 --- a/PullToRefreshDemo/ViewController.swift +++ b/PullToRefreshDemo/PullToRefreshViewController.swift @@ -24,39 +24,33 @@ import UIKit import Refresher -class ViewController: UIViewController { +enum ExampleMode { + case Default + case Beat + case Pacman + case Custom +} + +class PullToRefreshViewController: UIViewController { @IBOutlet weak var tableView: UITableView! + var exampleMode = ExampleMode.Default override func viewDidLoad() { - super.viewDidLoad() - /* - let beatAnimator = BeatAnimator(frame: CGRectMake(0, 0, 320, 80)) - tableView.addPullToRefreshWithAction({ () -> () in - NSOperationQueue().addOperationWithBlock { - sleep(2) - NSOperationQueue.mainQueue().addOperationWithBlock { - self.tableView.stopPullToRefresh() - } - } - }, withAnimator: beatAnimator, withSubview: beatAnimator) - - */ - - let pacmanAnimator = PacmanAnimator(frame: CGRectMake(0, 0, 320, 80)) - tableView.addPullToRefreshWithAction({ () -> () in - NSOperationQueue().addOperationWithBlock { - sleep(2) - NSOperationQueue.mainQueue().addOperationWithBlock { - self.tableView.stopPullToRefresh() + switch exampleMode { + case .Default: + tableView.addPullToRefreshWithAction { + NSOperationQueue().addOperationWithBlock { + sleep(2) + NSOperationQueue.mainQueue().addOperationWithBlock { + self.tableView.stopPullToRefresh() + } } } - }, withAnimator: pacmanAnimator, withSubview: pacmanAnimator) - - /* - if let customSubview = NSBundle.mainBundle().loadNibNamed("CustomSubview", owner: self, options: nil).first as? CustomSubview { + case .Beat: + let beatAnimator = BeatAnimator(frame: CGRectMake(0, 0, 320, 80)) tableView.addPullToRefreshWithAction({ NSOperationQueue().addOperationWithBlock { sleep(2) @@ -64,39 +58,46 @@ class ViewController: UIViewController { self.tableView.stopPullToRefresh() } } - }, withAnimator: customSubview, withSubview: customSubview) - } - - tableView.addPullToRefreshWithAction { - NSOperationQueue().addOperationWithBlock { - sleep(2) - NSOperationQueue.mainQueue().addOperationWithBlock { - self.tableView.stopPullToRefresh() + }, withAnimator: beatAnimator) + case .Pacman: + let pacmanAnimator = PacmanAnimator(frame: CGRectMake(0, 0, 320, 80)) + tableView.addPullToRefreshWithAction({ + NSOperationQueue().addOperationWithBlock { + sleep(2) + NSOperationQueue.mainQueue().addOperationWithBlock { + self.tableView.stopPullToRefresh() + } } + }, withAnimator: pacmanAnimator) + case .Custom: + if let customSubview = NSBundle.mainBundle().loadNibNamed("CustomSubview", owner: self, options: nil).first as? CustomSubview { + tableView.addPullToRefreshWithAction({ + NSOperationQueue().addOperationWithBlock { + sleep(2) + NSOperationQueue.mainQueue().addOperationWithBlock { + self.tableView.stopPullToRefresh() + } + } + }, withAnimator: customSubview) } } - */ } override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) - // Test refreshing programatically tableView.startPullToRefresh() } override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int { - return 50 } func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! { - var cell = UITableViewCell(style: .Default, reuseIdentifier: "Cell") cell.textLabel?.text = "Row " + String(indexPath.row + 1) return cell