├── .gitignore ├── .gitmodules ├── ITPullToRefreshScrollView ├── Classes │ ├── DuxScrollViewAnimation.h │ ├── DuxScrollViewAnimation.m │ ├── ITPullToRefreshClipView.h │ ├── ITPullToRefreshClipView.m │ ├── ITPullToRefreshEdgeView.h │ ├── ITPullToRefreshEdgeView.m │ ├── ITPullToRefreshScrollView.h │ └── ITPullToRefreshScrollView.m ├── ITPullToRefreshScrollView.xcodeproj │ └── project.pbxproj ├── ITPullToRefreshScrollView │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── ITPullToRefreshScrollView-Info.plist │ ├── ITPullToRefreshScrollView-Prefix.pch │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── en.lproj │ │ ├── Credits.rtf │ │ └── InfoPlist.strings │ └── main.m └── ITPullToRefreshScrollViewTests │ ├── ITPullToRefreshScrollViewTests-Info.plist │ ├── ITPullToRefreshScrollViewTests.m │ └── en.lproj │ └── InfoPlist.strings ├── LICENSE ├── README.md └── demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Modules/NSBKeyframeAnimation"] 2 | path = Modules/NSBKeyframeAnimation 3 | url = https://github.com/iluuu1994/NSBKeyframeAnimation 4 | [submodule "Modules/ITProgressIndicator"] 5 | path = Modules/ITProgressIndicator 6 | url = https://github.com/iluuu1994/ITProgressIndicator 7 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/Classes/DuxScrollViewAnimation.h: -------------------------------------------------------------------------------- 1 | // 2 | // DuxScrollViewAnimation.h 3 | // Dux 4 | // 5 | // Created by Abhi Beckert on 2011-11-30. 6 | // 7 | // This is free and unencumbered software released into the public domain. 8 | // For more information, please refer to 9 | // 10 | 11 | #import 12 | 13 | @interface DuxScrollViewAnimation : NSAnimation 14 | 15 | @property (retain) NSScrollView *scrollView; 16 | @property NSPoint originPoint; 17 | @property NSPoint targetPoint; 18 | 19 | + (void)animatedScrollPointToCenter:(NSPoint)targetPoint inScrollView:(NSScrollView *)scrollView; 20 | + (void)animatedScrollToPoint:(NSPoint)targetPoint inScrollView:(NSScrollView *)scrollView; 21 | + (void)animatedScrollToPoint:(NSPoint)targetPoint inScrollView:(NSScrollView *)scrollView delegate:(id)delegate; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/Classes/DuxScrollViewAnimation.m: -------------------------------------------------------------------------------- 1 | // 2 | // DuxScrollViewAnimation.m 3 | // Dux 4 | // 5 | // Created by Abhi Beckert on 2011-11-30. 6 | // 7 | // This is free and unencumbered software released into the public domain. 8 | // For more information, please refer to 9 | // 10 | 11 | #import "DuxScrollViewAnimation.h" 12 | 13 | @implementation DuxScrollViewAnimation 14 | 15 | @synthesize scrollView; 16 | @synthesize originPoint; 17 | @synthesize targetPoint; 18 | 19 | + (void)animatedScrollPointToCenter:(NSPoint)targetPoint inScrollView:(NSScrollView *)scrollView 20 | { 21 | NSRect visibleRect = scrollView.documentVisibleRect; 22 | 23 | targetPoint = NSMakePoint(targetPoint.x - (NSWidth(visibleRect) / 2), targetPoint.y - (NSHeight(visibleRect) / 2)); 24 | 25 | [self animatedScrollToPoint:targetPoint inScrollView:scrollView]; 26 | } 27 | 28 | + (void)animatedScrollToPoint:(NSPoint)targetPoint inScrollView:(NSScrollView *)scrollView { 29 | [self animatedScrollToPoint:targetPoint inScrollView:scrollView delegate:nil]; 30 | } 31 | 32 | + (void)animatedScrollToPoint:(NSPoint)targetPoint inScrollView:(NSScrollView *)scrollView delegate:(id)delegate; 33 | { 34 | DuxScrollViewAnimation *animation = [[DuxScrollViewAnimation alloc] initWithDuration:0.2 animationCurve:NSAnimationEaseInOut]; 35 | 36 | animation.delegate = delegate; 37 | animation.scrollView = scrollView; 38 | animation.originPoint = scrollView.documentVisibleRect.origin; 39 | animation.targetPoint = targetPoint; 40 | 41 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 42 | [animation startAnimation]; 43 | }); 44 | } 45 | 46 | - (void)setCurrentProgress:(NSAnimationProgress)progress 47 | { 48 | typedef float (^MyAnimationCurveBlock)(float, float, float); 49 | MyAnimationCurveBlock cubicEaseInOut = ^ float (float t, float start, float end) { 50 | t *= 2.; 51 | if (t < 1.) return end/2 * t * t * t + start - 1.f; 52 | t -= 2; 53 | return end/2*(t * t * t + 2) + start - 1.f; 54 | }; 55 | 56 | dispatch_sync(dispatch_get_main_queue(), ^{ 57 | NSPoint progressPoint = self.originPoint; 58 | progressPoint.x += cubicEaseInOut(progress, 0, self.targetPoint.x - self.originPoint.x); 59 | progressPoint.y += cubicEaseInOut(progress, 0, self.targetPoint.y - self.originPoint.y); 60 | 61 | [self.scrollView.documentView scrollPoint:progressPoint]; 62 | [self.scrollView displayIfNeeded]; 63 | }); 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/Classes/ITPullToRefreshClipView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ITPullToRefreshClipView.h 3 | // ITPullToRefreshScrollView 4 | // 5 | // Created by Ilija Tovilo on 9/25/13. 6 | // Copyright (c) 2013 Ilija Tovilo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | * This class is used by `ITPullToRefreshScrollView`. 13 | * You don't need to interact with it directly. 14 | */ 15 | @interface ITPullToRefreshClipView : NSClipView 16 | @end 17 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/Classes/ITPullToRefreshClipView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ITPullToRefreshClipView.m 3 | // ITPullToRefreshScrollView 4 | // 5 | // Created by Ilija Tovilo on 9/25/13. 6 | // Copyright (c) 2013 Ilija Tovilo. All rights reserved. 7 | // 8 | 9 | #import "ITPullToRefreshClipView.h" 10 | #import "ITPullToRefreshScrollView.h" 11 | #import "ITPullToRefreshEdgeView.h" 12 | 13 | @implementation ITPullToRefreshClipView 14 | 15 | #pragma mark - Properties 16 | 17 | -(NSView *) headerView { return [(ITPullToRefreshScrollView *)self.superview edgeViewForEdge:ITPullToRefreshEdgeTop]; } 18 | -(NSView*) footerView { return [(ITPullToRefreshScrollView *)self.superview edgeViewForEdge:ITPullToRefreshEdgeBottom]; } 19 | -(NSUInteger)refreshingSides { return [(ITPullToRefreshScrollView *)self.superview refreshingEdges]; } 20 | 21 | 22 | #pragma mark - NSClipView 23 | 24 | - (NSPoint)constrainScrollPoint:(NSPoint)proposedNewOrigin 25 | { 26 | NSPoint constrained = [super constrainScrollPoint:proposedNewOrigin]; 27 | const NSRect clipViewBounds = self.bounds; 28 | NSView* const documentView = self.documentView; 29 | const NSRect documentFrame = documentView.frame; 30 | 31 | const NSUInteger refreshingSides = [self refreshingSides]; 32 | 33 | if (refreshingSides & ITPullToRefreshEdgeTop && proposedNewOrigin.y <= 0) { 34 | const NSRect headerFrame = [self headerView].frame; 35 | constrained.y = MAX(-headerFrame.size.height, proposedNewOrigin.y); 36 | } 37 | 38 | if((refreshingSides & ITPullToRefreshEdgeBottom) ) { 39 | const NSRect footerFrame = [self footerView].frame; 40 | if (proposedNewOrigin.y > documentFrame.size.height - clipViewBounds.size.height) { 41 | const CGFloat maxHeight = documentFrame.size.height - clipViewBounds.size.height + footerFrame.size.height + 1; 42 | constrained.y = MIN(maxHeight, proposedNewOrigin.y); 43 | } 44 | } 45 | 46 | return constrained; 47 | } 48 | 49 | -(NSRect)documentRect 50 | { 51 | NSRect documentRect = [super documentRect]; 52 | 53 | const NSUInteger refreshingSides = [self refreshingSides]; 54 | 55 | if (refreshingSides & ITPullToRefreshEdgeTop) { 56 | const NSRect headerFrame = [self headerView].frame; 57 | documentRect.size.height += headerFrame.size.height; 58 | documentRect.origin.y -= headerFrame.size.height; 59 | } 60 | 61 | if(refreshingSides & ITPullToRefreshEdgeBottom) { 62 | const NSRect footerFrame = [self footerView].frame; 63 | documentRect.size.height += footerFrame.size.height ; 64 | } 65 | 66 | return documentRect; 67 | } 68 | 69 | 70 | #pragma mark - NSView 71 | 72 | -(BOOL)isFlipped 73 | { 74 | return YES; 75 | } 76 | 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/Classes/ITPullToRefreshEdgeView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ITPullToRefreshEdgeView.h 3 | // ITPullToRefreshScrollView 4 | // 5 | // Created by Ilija Tovilo on 9/25/13. 6 | // Copyright (c) 2013 Ilija Tovilo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ITPullToRefreshScrollView.h" 11 | 12 | /** 13 | * This view is used by the `ITPullToRefreshScrollView` to display the edges. 14 | * You can extend from this class to create custom edges. 15 | */ 16 | @interface ITPullToRefreshEdgeView : NSView 17 | 18 | /** 19 | * Use this method to initialise an edge view 20 | * 21 | * @param edge - The edge which will the edge view be used for 22 | * 23 | * @return A new instance of `ITPullToRefreshEdgeView` 24 | */ 25 | - (instancetype)initWithEdge:(ITPullToRefreshEdge)edge; 26 | 27 | 28 | 29 | // -------------------------- 30 | // ------------------- Events 31 | // -------------------------- 32 | 33 | /** 34 | * This method is called when the edge is triggered 35 | * 36 | * @param scrollView - The sender scroll view 37 | */ 38 | - (void)pullToRefreshScrollViewDidTriggerRefresh:(ITPullToRefreshScrollView *)scrollView; 39 | 40 | /** 41 | * This method is called when the edge is untriggered 42 | * 43 | * @param scrollView - The sender scroll view 44 | */ 45 | - (void)pullToRefreshScrollViewDidUntriggerRefresh:(ITPullToRefreshScrollView *)scrollView; 46 | 47 | /** 48 | * This method is called when the edge starts refreshing 49 | * 50 | * @param scrollView - The sender scroll view 51 | */ 52 | - (void)pullToRefreshScrollViewDidStartRefreshing:(ITPullToRefreshScrollView *)scrollView; 53 | 54 | /** 55 | * This method is called when the edge stops refreshing 56 | * 57 | * @param scrollView - The sender scroll view 58 | */ 59 | - (void)pullToRefreshScrollViewDidStopRefreshing:(ITPullToRefreshScrollView *)scrollView; 60 | 61 | /** 62 | * This is the final method called when the scroll view stopped animating 63 | * 64 | * @param scrollView - The sender scroll view 65 | */ 66 | - (void)pullToRefreshScrollViewDidStopAnimating:(ITPullToRefreshScrollView *)scrollView; 67 | 68 | /** 69 | * This method is called when part of the edge view becomes visible 70 | * 71 | * @param scrollView - The sender scroll view 72 | * @param progress - The amount of the edge view that is visible (from 0.0 to 1.0) 73 | */ 74 | - (void)pullToRefreshScrollView:(ITPullToRefreshScrollView *)scrollView didScrollWithProgress:(CGFloat)progress; 75 | 76 | 77 | 78 | // -------------------------- 79 | // ------------ Customisation 80 | // -------------------------- 81 | 82 | /** 83 | * Override this to remove the progress indicator and create other components 84 | */ 85 | - (void)installComponents; 86 | 87 | /** 88 | * The height of the edge view. 89 | * Override this method to achieve a custom edge view height. 90 | * 91 | * @return The height of the edge view 92 | */ 93 | - (CGFloat)edgeViewHeight; 94 | 95 | /** 96 | * Override this method to draw a custom background. 97 | * You can also just override `drawRect:` and to all the drawing on your own. 98 | * 99 | * @param The rect which should be drawn on 100 | */ 101 | - (void)drawBackgroundInRect:(NSRect)dirtyRect; 102 | 103 | 104 | @property ITPullToRefreshEdge edgeViewEdge; 105 | 106 | @end 107 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/Classes/ITPullToRefreshEdgeView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ITPullToRefreshEdgeView.m 3 | // ITPullToRefreshScrollView 4 | // 5 | // Created by Ilija Tovilo on 9/25/13. 6 | // Copyright (c) 2013 Ilija Tovilo. All rights reserved. 7 | // 8 | 9 | #import "ITPullToRefreshEdgeView.h" 10 | #import 11 | #import "ITProgressIndicator.h" 12 | #import "NSBKeyframeAnimation.h" 13 | 14 | #define kDefaultEdgeViewHeight 30 15 | #define kSpinnerSize 30 16 | 17 | #define kMinSpinAnimationDuration 2.0 18 | #define kMaxSpinAnimationDuration 8.0 19 | #define kSpringRange 0.4 20 | 21 | @interface ITPullToRefreshEdgeView () { 22 | CGFloat _cachedProgress; 23 | } 24 | @property (strong) ITProgressIndicator *progressIndicator; 25 | @end 26 | 27 | @implementation ITPullToRefreshEdgeView 28 | 29 | 30 | #pragma mark - Init 31 | 32 | - (instancetype)initWithEdge:(ITPullToRefreshEdge)edge { 33 | if (self = [super init]) { 34 | _edgeViewEdge = edge; 35 | [self installComponents]; 36 | } 37 | 38 | return self; 39 | } 40 | 41 | - (void)installComponents { 42 | self.progressIndicator = [[ITProgressIndicator alloc] initWithFrame:NSMakeRect(0, 0, kSpinnerSize, kSpinnerSize)]; 43 | 44 | [self.progressIndicator setWantsLayer:YES]; 45 | self.progressIndicator.animates = NO; 46 | self.progressIndicator.hideWhenStopped = NO; 47 | self.progressIndicator.isIndeterminate = NO; 48 | self.progressIndicator.progress = 0.0; 49 | self.progressIndicator.numberOfLines = 12; 50 | self.progressIndicator.widthOfLine = 2.0; 51 | self.progressIndicator.innerMargin = 5; 52 | self.progressIndicator.color = [NSColor colorWithDeviceWhite:0.4 alpha:1.0]; 53 | self.progressIndicator.layer.contentsGravity = kCAGravityCenter; 54 | [self.progressIndicator.layer setAnchorPoint:CGPointMake(0.5, 0.5)]; 55 | 56 | [self addSubview:self.progressIndicator]; 57 | 58 | 59 | // Install Layout Constraints 60 | { 61 | [self.progressIndicator setTranslatesAutoresizingMaskIntoConstraints:NO]; 62 | 63 | [self.progressIndicator addConstraint:[NSLayoutConstraint constraintWithItem:self.progressIndicator 64 | attribute:NSLayoutAttributeWidth 65 | relatedBy:NSLayoutRelationEqual 66 | toItem:nil 67 | attribute:NSLayoutAttributeNotAnAttribute 68 | multiplier:1 69 | constant:kSpinnerSize]]; 70 | [self.progressIndicator addConstraint:[NSLayoutConstraint constraintWithItem:self.progressIndicator 71 | attribute:NSLayoutAttributeHeight 72 | relatedBy:NSLayoutRelationEqual 73 | toItem:nil 74 | attribute:NSLayoutAttributeNotAnAttribute 75 | multiplier:1 76 | constant:kSpinnerSize]]; 77 | 78 | [self addConstraint:[NSLayoutConstraint constraintWithItem:self.progressIndicator 79 | attribute:NSLayoutAttributeCenterY 80 | relatedBy:0 81 | toItem:self 82 | attribute:NSLayoutAttributeCenterY 83 | multiplier:1 84 | constant:0]]; 85 | [self addConstraint:[NSLayoutConstraint constraintWithItem:self.progressIndicator 86 | attribute:NSLayoutAttributeCenterX 87 | relatedBy:0 88 | toItem:self 89 | attribute:NSLayoutAttributeCenterX 90 | multiplier:1 91 | constant:0]]; 92 | } 93 | } 94 | 95 | 96 | #pragma mark - Constraints 97 | 98 | - (void)viewDidMoveToSuperview { 99 | [self setUpConstraints]; 100 | } 101 | 102 | - (void)setUpConstraints { 103 | [self setTranslatesAutoresizingMaskIntoConstraints:NO]; 104 | 105 | [self addConstraint:[NSLayoutConstraint constraintWithItem:self 106 | attribute:NSLayoutAttributeHeight 107 | relatedBy:NSLayoutRelationEqual 108 | toItem:nil 109 | attribute:NSLayoutAttributeNotAnAttribute 110 | multiplier:1 111 | constant:[self edgeViewHeight]]]; 112 | 113 | [self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self 114 | attribute:NSLayoutAttributeLeft 115 | relatedBy:NSLayoutRelationEqual 116 | toItem:self.superview 117 | attribute:NSLayoutAttributeLeft 118 | multiplier:1.0 119 | constant:0.0]]; 120 | [self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self 121 | attribute:NSLayoutAttributeRight 122 | relatedBy:NSLayoutRelationEqual 123 | toItem:self.superview 124 | attribute:NSLayoutAttributeRight 125 | multiplier:1.0 126 | constant:0.0]]; 127 | 128 | if (self.edgeViewEdge & ITPullToRefreshEdgeTop) 129 | { 130 | [self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self 131 | attribute:NSLayoutAttributeBottom 132 | relatedBy:NSLayoutRelationEqual 133 | toItem:[(NSClipView *)self.superview documentView] 134 | attribute:NSLayoutAttributeTop 135 | multiplier:1.0 136 | constant:0.0]]; 137 | } 138 | else if (self.edgeViewEdge & ITPullToRefreshEdgeBottom) 139 | { 140 | [self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self 141 | attribute:NSLayoutAttributeTop 142 | relatedBy:NSLayoutRelationEqual 143 | toItem:[(NSClipView *)self.superview documentView] 144 | attribute:NSLayoutAttributeBottom 145 | multiplier:1.0 146 | constant:0.0]]; 147 | } 148 | } 149 | 150 | 151 | #pragma mark - Customisation Methods 152 | 153 | - (CGFloat)edgeViewHeight { 154 | return kDefaultEdgeViewHeight; 155 | } 156 | 157 | - (void)drawRect:(NSRect)dirtyRect { 158 | [self drawBackgroundInRect:dirtyRect]; 159 | } 160 | 161 | - (void)drawBackgroundInRect:(NSRect)dirtyRect { 162 | [[NSColor colorWithDeviceWhite:0.96 alpha:1.0] set]; 163 | NSRectFill(dirtyRect); 164 | } 165 | 166 | - (void)pullToRefreshScrollView:(ITPullToRefreshScrollView *)scrollView didScrollWithProgress:(CGFloat)progress { 167 | _cachedProgress = progress; 168 | 169 | if (progress < 1.0) { 170 | self.progressIndicator.isIndeterminate = NO; 171 | self.progressIndicator.progress = progress; 172 | } 173 | } 174 | 175 | - (void)pullToRefreshScrollViewDidTriggerRefresh:(ITPullToRefreshScrollView *)scrollView { 176 | self.progressIndicator.isIndeterminate = NO; 177 | self.progressIndicator.progress = 1.0; 178 | } 179 | 180 | - (void)pullToRefreshScrollViewDidUntriggerRefresh:(ITPullToRefreshScrollView *)scrollView { 181 | 182 | } 183 | 184 | - (void)pullToRefreshScrollViewDidStartRefreshing:(ITPullToRefreshScrollView *)scrollView { 185 | CGFloat tension = (_cachedProgress - 1.0 <= kSpringRange)?_cachedProgress - 1:kSpringRange; 186 | CGFloat duration = kMaxSpinAnimationDuration - ((kMaxSpinAnimationDuration - kMinSpinAnimationDuration) * (1.0 / kSpringRange * tension)); 187 | 188 | [self.progressIndicator.layer addAnimation:[self rotationAnimationWithDuration:duration] forKey:@"rotation"]; 189 | self.progressIndicator.isIndeterminate = YES; 190 | self.progressIndicator.animates = YES; 191 | } 192 | 193 | - (void)pullToRefreshScrollViewDidStopRefreshing:(ITPullToRefreshScrollView *)scrollView { 194 | self.progressIndicator.animates = NO; 195 | self.progressIndicator.isIndeterminate = NO; 196 | 197 | [self.progressIndicator.layer addAnimation:[self shrinkAnimation] forKey:@"shrink"]; 198 | } 199 | 200 | - (void)pullToRefreshScrollViewDidStopAnimating:(ITPullToRefreshScrollView *)scrollView { 201 | [self.progressIndicator.layer removeAnimationForKey:@"rotation"]; 202 | } 203 | 204 | - (CAAnimation *)shrinkAnimation { 205 | NSBKeyframeAnimation *animation = [NSBKeyframeAnimation animationWithKeyPath:@"transform.scale" 206 | duration:0.3 207 | startValue:1 208 | endValue:0.0 209 | function:NSBKeyframeAnimationFunctionEaseOutCubic]; 210 | 211 | return animation; 212 | } 213 | 214 | - (CAAnimation *)rotationAnimationWithDuration:(CGFloat)duration { 215 | NSBKeyframeAnimation *animation = [NSBKeyframeAnimation animationWithKeyPath:@"transform" 216 | duration:duration 217 | startValue:0 218 | endValue:-2 * M_PI 219 | function:NSBKeyframeAnimationFunctionEaseOutCubic]; 220 | 221 | [animation setValueFunction:[CAValueFunction functionWithName: kCAValueFunctionRotateZ]]; 222 | 223 | return animation; 224 | } 225 | 226 | @end 227 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/Classes/ITPullToRefreshScrollView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ITPullToRefreshScrollView.h 3 | // ITPullToRefreshScrollView 4 | // 5 | // Created by Ilija Tovilo on 9/25/13. 6 | // Copyright (c) 2013 Ilija Tovilo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #ifndef ITPullToRefreshScrollViewConsts 12 | #define ITPullToRefreshScrollViewConsts 13 | 14 | 15 | /** 16 | * Enum that defines the edges of the ITPullToRefreshScrollView that can be refreshed 17 | */ 18 | typedef NS_ENUM(NSUInteger, ITPullToRefreshEdge) { 19 | ITPullToRefreshEdgeNone = 0, 20 | ITPullToRefreshEdgeTop = 1, 21 | ITPullToRefreshEdgeBottom = 1 << 1 22 | }; 23 | 24 | #endif 25 | 26 | @class ITPullToRefreshScrollView; 27 | 28 | /** 29 | * The delegate of the scroll view must implement this protocol. 30 | */ 31 | @protocol ITPullToRefreshScrollViewDelegate 32 | 33 | @optional 34 | /** 35 | * This method get's invoked when the scroll view started refreshing 36 | * 37 | * @param scrollView - The scroll view that started refreshing 38 | * @param edge - The edge that started refreshing 39 | */ 40 | - (void)pullToRefreshView:(ITPullToRefreshScrollView *)scrollView 41 | didStartRefreshingEdge:(ITPullToRefreshEdge)edge; 42 | 43 | /** 44 | * This method get's invoked when the scroll view stopped refreshing 45 | * 46 | * @param scrollView - The scroll view that stopped refreshing 47 | * @param edge - The edge that stopped refreshing 48 | */ 49 | - (void)pullToRefreshView:(ITPullToRefreshScrollView *)scrollView 50 | didStopRefreshingEdge:(ITPullToRefreshEdge)edge; 51 | 52 | @end 53 | 54 | 55 | @class ITPullToRefreshEdgeView; 56 | 57 | /** 58 | * ITPullToRefreshScrollView is subclass of the NSScrollView class. 59 | * It supports refreshing by scrolling. 60 | */ 61 | @interface ITPullToRefreshScrollView : NSScrollView 62 | 63 | /** 64 | * The delegate instance will receive notifications from the scroll view. 65 | * Look at the `ITPullToRefreshScrollViewDelegate` protocol for more information. 66 | */ 67 | @property (weak) IBOutlet id delegate; 68 | 69 | /** 70 | * Defines, which edges should be refreshable. 71 | * To assign multiple edges, simply add them with a bitwise OR operator: 72 | * 73 | * scrollView.refreshableEdges = ITPullToRefreshEdgeTop | ITPullToRefreshEdgeBottom 74 | */ 75 | @property (nonatomic) NSUInteger refreshableEdges; 76 | 77 | /** 78 | * A bitwise representation of the triggered edges. 79 | * 80 | * Edges are triggered, if the whole edge view is visible in the scroll view, 81 | * and the scroll gesture did not end yet. 82 | */ 83 | @property (readonly) NSUInteger triggeredEdges; 84 | 85 | /** 86 | * A bitwise representation of the refreshing edges. 87 | */ 88 | @property (readonly) NSUInteger refreshingEdges; 89 | 90 | /** 91 | * Get's the edge view for an edge 92 | * 93 | * @param edge - The edge for which the edge view should be returned 94 | * 95 | * @return The edge view for a specific edge 96 | */ 97 | - (ITPullToRefreshEdgeView *)edgeViewForEdge:(ITPullToRefreshEdge)edge; 98 | 99 | /** 100 | * Should be invoked by the delegate, when the refresh action is done. 101 | * 102 | * @param edge - The edge that should stop refreshing 103 | */ 104 | - (void)stopRefreshingEdge:(ITPullToRefreshEdge)edge; 105 | 106 | @end 107 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/Classes/ITPullToRefreshScrollView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ITPullToRefreshScrollView.m 3 | // ITPullToRefreshScrollView 4 | // 5 | // Created by Ilija Tovilo on 9/25/13. 6 | // Copyright (c) 2013 Ilija Tovilo. All rights reserved. 7 | // 8 | 9 | #import "ITPullToRefreshScrollView.h" 10 | #import 11 | #import "ITPullToRefreshEdgeView.h" 12 | #import "ITPullToRefreshClipView.h" 13 | #import "DuxScrollViewAnimation.h" 14 | 15 | void dispatch_sync_on_main(dispatch_block_t block); 16 | void dispatch_sync_on_main(dispatch_block_t block) { 17 | if ([NSThread isMainThread]) { 18 | block(); 19 | } else { 20 | dispatch_sync(dispatch_get_main_queue(), block); 21 | } 22 | } 23 | 24 | 25 | @interface ITPullToRefreshScrollView () { 26 | BOOL _isLocked; 27 | NSUInteger _edgesToBeRemoved; 28 | NSMutableDictionary *_edgeViews; 29 | } 30 | @end 31 | 32 | 33 | @implementation ITPullToRefreshScrollView 34 | 35 | #pragma mark - Init 36 | 37 | - (id)initWithCoder:(NSCoder *)coder 38 | { 39 | self = [super initWithCoder:coder]; 40 | if (self) { 41 | [self initialSetup]; 42 | } 43 | return self; 44 | } 45 | 46 | - (id)initWithFrame:(NSRect)frame 47 | { 48 | self = [super initWithFrame:frame]; 49 | if (self) { 50 | [self initialSetup]; 51 | } 52 | return self; 53 | } 54 | 55 | - (void)initialSetup { 56 | _edgeViews = [NSMutableDictionary dictionary]; 57 | [self installCustomClipView]; 58 | 59 | self.contentView.postsBoundsChangedNotifications = YES; 60 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(boundsDidChange:) name:NSViewBoundsDidChangeNotification object:self.contentView]; 61 | } 62 | 63 | - (IBAction)boundsDidChange:(NSNotification *)notification { 64 | [self scrollingChangedWithEvent:nil]; 65 | } 66 | 67 | - (void)installCustomClipView { 68 | if ([self.contentView isKindOfClass:[ITPullToRefreshClipView class]]) 69 | return; 70 | 71 | ITPullToRefreshClipView *newClipView = [[ITPullToRefreshClipView alloc] initWithFrame:NSZeroRect]; 72 | 73 | newClipView.documentView = self.contentView.documentView; 74 | self.contentView = newClipView; 75 | 76 | newClipView.translatesAutoresizingMaskIntoConstraints = NO; 77 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat: 78 | @"H:|[view]|" options:0 metrics:nil views:@{@"view": newClipView}]]; 79 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat: 80 | @"V:|[view]|" options:0 metrics:nil views:@{@"view": newClipView}]]; 81 | } 82 | 83 | 84 | #pragma mark - NSResponder 85 | 86 | - (void)scrollingChangedWithEvent:(NSEvent *)theEvent { 87 | if (_isLocked) return; 88 | 89 | void (^scrollBlock)(ITPullToRefreshEdge edge, CGFloat (^progressBlock)(void)) = 90 | ^(ITPullToRefreshEdge edge, CGFloat (^progressBlock)()) { 91 | if (!(_refreshingEdges & edge)) { 92 | ITPullToRefreshEdgeView *edgeView = [self edgeViewForEdge:edge]; 93 | CGFloat progress = progressBlock(); 94 | 95 | if ((edge & self.refreshableEdges) && progress > 0) { 96 | [edgeView pullToRefreshScrollView:self didScrollWithProgress:progress]; 97 | 98 | if (progress >= 1.0) { 99 | if (!(self.triggeredEdges & edge)) { 100 | [edgeView pullToRefreshScrollViewDidTriggerRefresh:self]; 101 | } 102 | 103 | _triggeredEdges |= edge; 104 | } else { 105 | if (self.triggeredEdges & edge) { 106 | [edgeView pullToRefreshScrollViewDidUntriggerRefresh:self]; 107 | } 108 | 109 | _triggeredEdges &= ~edge; 110 | } 111 | } 112 | } 113 | }; 114 | 115 | // Update edges 116 | if ((ITPullToRefreshEdgeTop & self.refreshableEdges)) { 117 | scrollBlock(ITPullToRefreshEdgeTop, ^{ 118 | ITPullToRefreshEdgeView *edgeView = [self edgeViewForEdge:ITPullToRefreshEdgeTop]; 119 | return (1.0 / 120 | edgeView.frame.size.height * 121 | -self.contentView.bounds.origin.y); 122 | }); 123 | } 124 | if ((ITPullToRefreshEdgeBottom & self.refreshableEdges)) { 125 | scrollBlock(ITPullToRefreshEdgeBottom, ^{ 126 | ITPullToRefreshEdgeView *edgeView = [self edgeViewForEdge:ITPullToRefreshEdgeBottom]; 127 | 128 | return (1.0 / 129 | edgeView.bounds.size.height * 130 | -(edgeView.frame.origin.y - (self.contentView.bounds.origin.y + self.contentView.bounds.size.height))); 131 | }); 132 | } 133 | } 134 | 135 | - (void)scrollingEndedWithEvent:(NSEvent *)theEvent { 136 | if (_isLocked) return; 137 | 138 | [self enumerateThroughEdges:^(ITPullToRefreshEdge edge) { 139 | if (_triggeredEdges & edge) { 140 | _triggeredEdges &= ~edge; 141 | _refreshingEdges |= edge; 142 | 143 | [[self edgeViewForEdge:edge] pullToRefreshScrollViewDidStartRefreshing:self]; 144 | 145 | if ([self.delegate respondsToSelector:@selector(pullToRefreshView:didStartRefreshingEdge:)]) { 146 | [self.delegate pullToRefreshView:self didStartRefreshingEdge:edge]; 147 | } 148 | } 149 | }]; 150 | } 151 | 152 | - (void)scrollWheel:(NSEvent *)theEvent { 153 | if (_isLocked) return; 154 | 155 | const NSEventPhase eventPhase = theEvent.phase; 156 | 157 | if (eventPhase & NSEventPhaseChanged) { 158 | [self scrollingChangedWithEvent:theEvent]; 159 | } else if(eventPhase & NSEventPhaseEnded) { 160 | [self scrollingEndedWithEvent:theEvent]; 161 | } 162 | 163 | [super scrollWheel:theEvent]; 164 | } 165 | 166 | - (void)stopRefreshingEdge:(ITPullToRefreshEdge)edge { 167 | dispatch_sync_on_main(^{ 168 | [[self edgeViewForEdge:edge] pullToRefreshScrollViewDidStopRefreshing:self]; 169 | 170 | if (_refreshingEdges & edge) { 171 | _isLocked = YES; 172 | 173 | NSPoint scrollPoint = self.contentView.bounds.origin; 174 | NSRect cvb = self.contentView.bounds; 175 | NSRect evf = [self edgeViewForEdge:edge].frame; 176 | 177 | if (edge == ITPullToRefreshEdgeTop) { 178 | if (cvb.origin.y < 0) { 179 | scrollPoint.y += -cvb.origin.y; 180 | } else { 181 | scrollPoint.y += [self edgeViewForEdge:edge].frame.size.height; 182 | } 183 | } else if (edge == ITPullToRefreshEdgeBottom) { 184 | if (cvb.origin.y + cvb.size.height > evf.origin.y) { 185 | scrollPoint.y -= -(evf.origin.y - (cvb.origin.y + cvb.size.height)); 186 | } else { 187 | scrollPoint.y -= evf.size.height; 188 | } 189 | } 190 | 191 | _edgesToBeRemoved |= edge; 192 | [DuxScrollViewAnimation animatedScrollToPoint:scrollPoint 193 | inScrollView:self 194 | delegate:self]; 195 | } 196 | }); 197 | } 198 | 199 | - (void)animationDidEnd:(NSAnimation *)animation { 200 | dispatch_sync_on_main(^{ 201 | [self enumerateThroughEdges:^(ITPullToRefreshEdge edge) { 202 | if (_edgesToBeRemoved & edge) { 203 | _edgesToBeRemoved &= ~edge; 204 | _refreshingEdges &= ~edge; 205 | 206 | [self imitateScrollingEventForEdge:edge]; 207 | 208 | [[self edgeViewForEdge:edge] pullToRefreshScrollViewDidStopAnimating:self]; 209 | 210 | if ([self.delegate respondsToSelector:@selector(pullToRefreshView:didStopRefreshingEdge:)]) { 211 | [self.delegate pullToRefreshView:self didStopRefreshingEdge:edge]; 212 | } 213 | } 214 | }]; 215 | 216 | if (!_edgesToBeRemoved) _isLocked = NO; 217 | }); 218 | } 219 | 220 | - (void)imitateScrollingEventForEdge:(ITPullToRefreshEdge)edge { 221 | NSInteger amount = 0; 222 | 223 | if (edge == ITPullToRefreshEdgeTop) amount = 1; 224 | else if (edge == ITPullToRefreshEdgeBottom) amount = -1; 225 | 226 | CGEventRef cgEvent = CGEventCreateScrollWheelEvent(NULL, 227 | kCGScrollEventUnitLine, 228 | 1, 229 | (int)amount, 230 | 0); 231 | 232 | NSEvent *scrollEvent = [NSEvent eventWithCGEvent:cgEvent]; 233 | [self scrollWheel:scrollEvent]; 234 | CFRelease(cgEvent); 235 | } 236 | 237 | 238 | #pragma mark - ITPullToRefreshScrollView Methods 239 | 240 | + (Class)edgeViewClassForEdge:(ITPullToRefreshEdge)edge { 241 | return [ITPullToRefreshEdgeView class]; 242 | } 243 | 244 | 245 | #pragma mark - Properties 246 | 247 | - (ITPullToRefreshEdgeView *)edgeViewForEdge:(ITPullToRefreshEdge)edge { 248 | if (!_edgeViews[@( edge )]) { 249 | [_edgeViews setObject:[[[[self class] edgeViewClassForEdge:ITPullToRefreshEdgeBottom] alloc] initWithEdge:edge] 250 | forKey:@( edge )]; 251 | } 252 | 253 | return _edgeViews[@( edge )]; 254 | } 255 | 256 | - (void)setRefreshableEdges:(NSUInteger)refreshableEdges { 257 | _refreshableEdges = refreshableEdges; 258 | 259 | [self enumerateThroughEdges:^(ITPullToRefreshEdge edge) { 260 | if (_refreshableEdges & edge) 261 | { 262 | ITPullToRefreshEdgeView *edgeView = [self edgeViewForEdge:edge]; 263 | 264 | [self.contentView addSubview:edgeView]; 265 | } 266 | else 267 | { 268 | [[self edgeViewForEdge:edge] removeFromSuperview]; 269 | } 270 | }]; 271 | } 272 | 273 | - (void)enumerateThroughEdges:(void (^)(ITPullToRefreshEdge))block { 274 | for (ITPullToRefreshEdge edge = ITPullToRefreshEdgeTop; edge <= ITPullToRefreshEdgeBottom; edge++) { 275 | block(edge); 276 | } 277 | } 278 | 279 | @end 280 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/ITPullToRefreshScrollView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 91915E3917F98453002031B0 /* NSBKeyframeAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 91915E3617F98453002031B0 /* NSBKeyframeAnimation.m */; }; 11 | 91915E3A17F98453002031B0 /* NSBKeyframeAnimationFunctions.c in Sources */ = {isa = PBXBuildFile; fileRef = 91915E3717F98453002031B0 /* NSBKeyframeAnimationFunctions.c */; }; 12 | 91915E3F17F984A1002031B0 /* ITProgressIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 91915E3C17F984A1002031B0 /* ITProgressIndicator.m */; }; 13 | 91EE6BFC17FAC2F9004A307A /* DuxScrollViewAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 91EE6BFB17FAC2F9004A307A /* DuxScrollViewAnimation.m */; }; 14 | 91F1EF0D17F2D4FD00B5017E /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91F1EF0C17F2D4FD00B5017E /* Cocoa.framework */; }; 15 | 91F1EF1717F2D4FD00B5017E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 91F1EF1517F2D4FD00B5017E /* InfoPlist.strings */; }; 16 | 91F1EF1917F2D4FD00B5017E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 91F1EF1817F2D4FD00B5017E /* main.m */; }; 17 | 91F1EF1D17F2D4FD00B5017E /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 91F1EF1B17F2D4FD00B5017E /* Credits.rtf */; }; 18 | 91F1EF2017F2D4FD00B5017E /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 91F1EF1F17F2D4FD00B5017E /* AppDelegate.m */; }; 19 | 91F1EF2317F2D4FD00B5017E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 91F1EF2117F2D4FD00B5017E /* MainMenu.xib */; }; 20 | 91F1EF2517F2D4FD00B5017E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 91F1EF2417F2D4FD00B5017E /* Images.xcassets */; }; 21 | 91F1EF2C17F2D4FD00B5017E /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91F1EF2B17F2D4FD00B5017E /* XCTest.framework */; }; 22 | 91F1EF2D17F2D4FD00B5017E /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91F1EF0C17F2D4FD00B5017E /* Cocoa.framework */; }; 23 | 91F1EF3517F2D4FD00B5017E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 91F1EF3317F2D4FD00B5017E /* InfoPlist.strings */; }; 24 | 91F1EF3717F2D4FD00B5017E /* ITPullToRefreshScrollViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 91F1EF3617F2D4FD00B5017E /* ITPullToRefreshScrollViewTests.m */; }; 25 | 91F1EF4C17F2DAE900B5017E /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91F1EF4B17F2DAE900B5017E /* QuartzCore.framework */; }; 26 | 91F1EF6117F2FD0C00B5017E /* ITPullToRefreshClipView.m in Sources */ = {isa = PBXBuildFile; fileRef = 91F1EF5C17F2FD0C00B5017E /* ITPullToRefreshClipView.m */; }; 27 | 91F1EF6217F2FD0C00B5017E /* ITPullToRefreshEdgeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 91F1EF5E17F2FD0C00B5017E /* ITPullToRefreshEdgeView.m */; }; 28 | 91F1EF6317F2FD0C00B5017E /* ITPullToRefreshScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 91F1EF6017F2FD0C00B5017E /* ITPullToRefreshScrollView.m */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXContainerItemProxy section */ 32 | 91F1EF2E17F2D4FD00B5017E /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = 91F1EF0117F2D4FD00B5017E /* Project object */; 35 | proxyType = 1; 36 | remoteGlobalIDString = 91F1EF0817F2D4FD00B5017E; 37 | remoteInfo = ITPullToRefreshScrollView; 38 | }; 39 | /* End PBXContainerItemProxy section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | 91915E3517F98453002031B0 /* NSBKeyframeAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSBKeyframeAnimation.h; path = ../../Modules/NSBKeyframeAnimation/NSBKeyframeAnimation/Classes/NSBKeyframeAnimation/NSBKeyframeAnimation.h; sourceTree = ""; }; 43 | 91915E3617F98453002031B0 /* NSBKeyframeAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NSBKeyframeAnimation.m; path = ../../Modules/NSBKeyframeAnimation/NSBKeyframeAnimation/Classes/NSBKeyframeAnimation/NSBKeyframeAnimation.m; sourceTree = ""; }; 44 | 91915E3717F98453002031B0 /* NSBKeyframeAnimationFunctions.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = NSBKeyframeAnimationFunctions.c; path = ../../Modules/NSBKeyframeAnimation/NSBKeyframeAnimation/Classes/NSBKeyframeAnimation/NSBKeyframeAnimationFunctions.c; sourceTree = ""; }; 45 | 91915E3817F98453002031B0 /* NSBKeyframeAnimationFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSBKeyframeAnimationFunctions.h; path = ../../Modules/NSBKeyframeAnimation/NSBKeyframeAnimation/Classes/NSBKeyframeAnimation/NSBKeyframeAnimationFunctions.h; sourceTree = ""; }; 46 | 91915E3B17F984A1002031B0 /* ITProgressIndicator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ITProgressIndicator.h; path = ../../Modules/ITProgressIndicator/ITProgressIndicator/Classes/ITProgressIndicator.h; sourceTree = ""; }; 47 | 91915E3C17F984A1002031B0 /* ITProgressIndicator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ITProgressIndicator.m; path = ../../Modules/ITProgressIndicator/ITProgressIndicator/Classes/ITProgressIndicator.m; sourceTree = ""; }; 48 | 91EE6BFA17FAC2F9004A307A /* DuxScrollViewAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DuxScrollViewAnimation.h; path = Classes/DuxScrollViewAnimation.h; sourceTree = SOURCE_ROOT; }; 49 | 91EE6BFB17FAC2F9004A307A /* DuxScrollViewAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DuxScrollViewAnimation.m; path = Classes/DuxScrollViewAnimation.m; sourceTree = SOURCE_ROOT; }; 50 | 91F1EF0917F2D4FD00B5017E /* ITPullToRefreshScrollView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ITPullToRefreshScrollView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 91F1EF0C17F2D4FD00B5017E /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 52 | 91F1EF0F17F2D4FD00B5017E /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 53 | 91F1EF1017F2D4FD00B5017E /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 54 | 91F1EF1117F2D4FD00B5017E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 55 | 91F1EF1417F2D4FD00B5017E /* ITPullToRefreshScrollView-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ITPullToRefreshScrollView-Info.plist"; sourceTree = ""; }; 56 | 91F1EF1617F2D4FD00B5017E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 57 | 91F1EF1817F2D4FD00B5017E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 58 | 91F1EF1A17F2D4FD00B5017E /* ITPullToRefreshScrollView-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ITPullToRefreshScrollView-Prefix.pch"; sourceTree = ""; }; 59 | 91F1EF1C17F2D4FD00B5017E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; 60 | 91F1EF1E17F2D4FD00B5017E /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 61 | 91F1EF1F17F2D4FD00B5017E /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 62 | 91F1EF2217F2D4FD00B5017E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 63 | 91F1EF2417F2D4FD00B5017E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 64 | 91F1EF2A17F2D4FD00B5017E /* ITPullToRefreshScrollViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ITPullToRefreshScrollViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | 91F1EF2B17F2D4FD00B5017E /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 66 | 91F1EF3217F2D4FD00B5017E /* ITPullToRefreshScrollViewTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ITPullToRefreshScrollViewTests-Info.plist"; sourceTree = ""; }; 67 | 91F1EF3417F2D4FD00B5017E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 68 | 91F1EF3617F2D4FD00B5017E /* ITPullToRefreshScrollViewTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ITPullToRefreshScrollViewTests.m; sourceTree = ""; }; 69 | 91F1EF4B17F2DAE900B5017E /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 70 | 91F1EF5B17F2FD0C00B5017E /* ITPullToRefreshClipView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ITPullToRefreshClipView.h; path = Classes/ITPullToRefreshClipView.h; sourceTree = SOURCE_ROOT; }; 71 | 91F1EF5C17F2FD0C00B5017E /* ITPullToRefreshClipView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ITPullToRefreshClipView.m; path = Classes/ITPullToRefreshClipView.m; sourceTree = SOURCE_ROOT; }; 72 | 91F1EF5D17F2FD0C00B5017E /* ITPullToRefreshEdgeView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ITPullToRefreshEdgeView.h; path = Classes/ITPullToRefreshEdgeView.h; sourceTree = SOURCE_ROOT; }; 73 | 91F1EF5E17F2FD0C00B5017E /* ITPullToRefreshEdgeView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ITPullToRefreshEdgeView.m; path = Classes/ITPullToRefreshEdgeView.m; sourceTree = SOURCE_ROOT; }; 74 | 91F1EF5F17F2FD0C00B5017E /* ITPullToRefreshScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ITPullToRefreshScrollView.h; path = Classes/ITPullToRefreshScrollView.h; sourceTree = SOURCE_ROOT; }; 75 | 91F1EF6017F2FD0C00B5017E /* ITPullToRefreshScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ITPullToRefreshScrollView.m; path = Classes/ITPullToRefreshScrollView.m; sourceTree = SOURCE_ROOT; }; 76 | /* End PBXFileReference section */ 77 | 78 | /* Begin PBXFrameworksBuildPhase section */ 79 | 91F1EF0617F2D4FD00B5017E /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | 91F1EF4C17F2DAE900B5017E /* QuartzCore.framework in Frameworks */, 84 | 91F1EF0D17F2D4FD00B5017E /* Cocoa.framework in Frameworks */, 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | 91F1EF2717F2D4FD00B5017E /* Frameworks */ = { 89 | isa = PBXFrameworksBuildPhase; 90 | buildActionMask = 2147483647; 91 | files = ( 92 | 91F1EF2D17F2D4FD00B5017E /* Cocoa.framework in Frameworks */, 93 | 91F1EF2C17F2D4FD00B5017E /* XCTest.framework in Frameworks */, 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | /* End PBXFrameworksBuildPhase section */ 98 | 99 | /* Begin PBXGroup section */ 100 | 912A2D7817F965CB00EB403A /* NSBKeyframeAnimation */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 91915E3517F98453002031B0 /* NSBKeyframeAnimation.h */, 104 | 91915E3617F98453002031B0 /* NSBKeyframeAnimation.m */, 105 | 91915E3717F98453002031B0 /* NSBKeyframeAnimationFunctions.c */, 106 | 91915E3817F98453002031B0 /* NSBKeyframeAnimationFunctions.h */, 107 | ); 108 | name = NSBKeyframeAnimation; 109 | sourceTree = ""; 110 | }; 111 | 91DA1D0017F895810076E848 /* Scroll Animation */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 91EE6BFA17FAC2F9004A307A /* DuxScrollViewAnimation.h */, 115 | 91EE6BFB17FAC2F9004A307A /* DuxScrollViewAnimation.m */, 116 | ); 117 | name = "Scroll Animation"; 118 | sourceTree = ""; 119 | }; 120 | 91DA1D0717F8A93D0076E848 /* ITProgressIndicator */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 91915E3B17F984A1002031B0 /* ITProgressIndicator.h */, 124 | 91915E3C17F984A1002031B0 /* ITProgressIndicator.m */, 125 | ); 126 | name = ITProgressIndicator; 127 | sourceTree = ""; 128 | }; 129 | 91F1EF0017F2D4FD00B5017E = { 130 | isa = PBXGroup; 131 | children = ( 132 | 91F1EF1217F2D4FD00B5017E /* ITPullToRefreshScrollView */, 133 | 91F1EF3017F2D4FD00B5017E /* ITPullToRefreshScrollViewTests */, 134 | 91F1EF0B17F2D4FD00B5017E /* Frameworks */, 135 | 91F1EF0A17F2D4FD00B5017E /* Products */, 136 | ); 137 | sourceTree = ""; 138 | }; 139 | 91F1EF0A17F2D4FD00B5017E /* Products */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 91F1EF0917F2D4FD00B5017E /* ITPullToRefreshScrollView.app */, 143 | 91F1EF2A17F2D4FD00B5017E /* ITPullToRefreshScrollViewTests.xctest */, 144 | ); 145 | name = Products; 146 | sourceTree = ""; 147 | }; 148 | 91F1EF0B17F2D4FD00B5017E /* Frameworks */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | 91F1EF4B17F2DAE900B5017E /* QuartzCore.framework */, 152 | 91F1EF0C17F2D4FD00B5017E /* Cocoa.framework */, 153 | 91F1EF2B17F2D4FD00B5017E /* XCTest.framework */, 154 | 91F1EF0E17F2D4FD00B5017E /* Other Frameworks */, 155 | ); 156 | name = Frameworks; 157 | sourceTree = ""; 158 | }; 159 | 91F1EF0E17F2D4FD00B5017E /* Other Frameworks */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | 91F1EF0F17F2D4FD00B5017E /* AppKit.framework */, 163 | 91F1EF1017F2D4FD00B5017E /* CoreData.framework */, 164 | 91F1EF1117F2D4FD00B5017E /* Foundation.framework */, 165 | ); 166 | name = "Other Frameworks"; 167 | sourceTree = ""; 168 | }; 169 | 91F1EF1217F2D4FD00B5017E /* ITPullToRefreshScrollView */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 91F1EF1E17F2D4FD00B5017E /* AppDelegate.h */, 173 | 91F1EF1F17F2D4FD00B5017E /* AppDelegate.m */, 174 | 91F1EF5317F2F32A00B5017E /* Classes */, 175 | 91DA1D0717F8A93D0076E848 /* ITProgressIndicator */, 176 | 912A2D7817F965CB00EB403A /* NSBKeyframeAnimation */, 177 | 91DA1D0017F895810076E848 /* Scroll Animation */, 178 | 91F1EF2117F2D4FD00B5017E /* MainMenu.xib */, 179 | 91F1EF2417F2D4FD00B5017E /* Images.xcassets */, 180 | 91F1EF1317F2D4FD00B5017E /* Supporting Files */, 181 | ); 182 | path = ITPullToRefreshScrollView; 183 | sourceTree = ""; 184 | }; 185 | 91F1EF1317F2D4FD00B5017E /* Supporting Files */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | 91F1EF1417F2D4FD00B5017E /* ITPullToRefreshScrollView-Info.plist */, 189 | 91F1EF1517F2D4FD00B5017E /* InfoPlist.strings */, 190 | 91F1EF1817F2D4FD00B5017E /* main.m */, 191 | 91F1EF1A17F2D4FD00B5017E /* ITPullToRefreshScrollView-Prefix.pch */, 192 | 91F1EF1B17F2D4FD00B5017E /* Credits.rtf */, 193 | ); 194 | name = "Supporting Files"; 195 | sourceTree = ""; 196 | }; 197 | 91F1EF3017F2D4FD00B5017E /* ITPullToRefreshScrollViewTests */ = { 198 | isa = PBXGroup; 199 | children = ( 200 | 91F1EF3617F2D4FD00B5017E /* ITPullToRefreshScrollViewTests.m */, 201 | 91F1EF3117F2D4FD00B5017E /* Supporting Files */, 202 | ); 203 | path = ITPullToRefreshScrollViewTests; 204 | sourceTree = ""; 205 | }; 206 | 91F1EF3117F2D4FD00B5017E /* Supporting Files */ = { 207 | isa = PBXGroup; 208 | children = ( 209 | 91F1EF3217F2D4FD00B5017E /* ITPullToRefreshScrollViewTests-Info.plist */, 210 | 91F1EF3317F2D4FD00B5017E /* InfoPlist.strings */, 211 | ); 212 | name = "Supporting Files"; 213 | sourceTree = ""; 214 | }; 215 | 91F1EF5317F2F32A00B5017E /* Classes */ = { 216 | isa = PBXGroup; 217 | children = ( 218 | 91F1EF5F17F2FD0C00B5017E /* ITPullToRefreshScrollView.h */, 219 | 91F1EF6017F2FD0C00B5017E /* ITPullToRefreshScrollView.m */, 220 | 91F1EF5B17F2FD0C00B5017E /* ITPullToRefreshClipView.h */, 221 | 91F1EF5C17F2FD0C00B5017E /* ITPullToRefreshClipView.m */, 222 | 91F1EF5D17F2FD0C00B5017E /* ITPullToRefreshEdgeView.h */, 223 | 91F1EF5E17F2FD0C00B5017E /* ITPullToRefreshEdgeView.m */, 224 | ); 225 | name = Classes; 226 | sourceTree = ""; 227 | }; 228 | /* End PBXGroup section */ 229 | 230 | /* Begin PBXNativeTarget section */ 231 | 91F1EF0817F2D4FD00B5017E /* ITPullToRefreshScrollView */ = { 232 | isa = PBXNativeTarget; 233 | buildConfigurationList = 91F1EF3A17F2D4FD00B5017E /* Build configuration list for PBXNativeTarget "ITPullToRefreshScrollView" */; 234 | buildPhases = ( 235 | 91F1EF0517F2D4FD00B5017E /* Sources */, 236 | 91F1EF0617F2D4FD00B5017E /* Frameworks */, 237 | 91F1EF0717F2D4FD00B5017E /* Resources */, 238 | ); 239 | buildRules = ( 240 | ); 241 | dependencies = ( 242 | ); 243 | name = ITPullToRefreshScrollView; 244 | productName = ITPullToRefreshScrollView; 245 | productReference = 91F1EF0917F2D4FD00B5017E /* ITPullToRefreshScrollView.app */; 246 | productType = "com.apple.product-type.application"; 247 | }; 248 | 91F1EF2917F2D4FD00B5017E /* ITPullToRefreshScrollViewTests */ = { 249 | isa = PBXNativeTarget; 250 | buildConfigurationList = 91F1EF3D17F2D4FD00B5017E /* Build configuration list for PBXNativeTarget "ITPullToRefreshScrollViewTests" */; 251 | buildPhases = ( 252 | 91F1EF2617F2D4FD00B5017E /* Sources */, 253 | 91F1EF2717F2D4FD00B5017E /* Frameworks */, 254 | 91F1EF2817F2D4FD00B5017E /* Resources */, 255 | ); 256 | buildRules = ( 257 | ); 258 | dependencies = ( 259 | 91F1EF2F17F2D4FD00B5017E /* PBXTargetDependency */, 260 | ); 261 | name = ITPullToRefreshScrollViewTests; 262 | productName = ITPullToRefreshScrollViewTests; 263 | productReference = 91F1EF2A17F2D4FD00B5017E /* ITPullToRefreshScrollViewTests.xctest */; 264 | productType = "com.apple.product-type.bundle.unit-test"; 265 | }; 266 | /* End PBXNativeTarget section */ 267 | 268 | /* Begin PBXProject section */ 269 | 91F1EF0117F2D4FD00B5017E /* Project object */ = { 270 | isa = PBXProject; 271 | attributes = { 272 | LastUpgradeCheck = 0820; 273 | ORGANIZATIONNAME = "Ilija Tovilo"; 274 | TargetAttributes = { 275 | 91F1EF2917F2D4FD00B5017E = { 276 | TestTargetID = 91F1EF0817F2D4FD00B5017E; 277 | }; 278 | }; 279 | }; 280 | buildConfigurationList = 91F1EF0417F2D4FD00B5017E /* Build configuration list for PBXProject "ITPullToRefreshScrollView" */; 281 | compatibilityVersion = "Xcode 3.2"; 282 | developmentRegion = English; 283 | hasScannedForEncodings = 0; 284 | knownRegions = ( 285 | en, 286 | Base, 287 | ); 288 | mainGroup = 91F1EF0017F2D4FD00B5017E; 289 | productRefGroup = 91F1EF0A17F2D4FD00B5017E /* Products */; 290 | projectDirPath = ""; 291 | projectRoot = ""; 292 | targets = ( 293 | 91F1EF0817F2D4FD00B5017E /* ITPullToRefreshScrollView */, 294 | 91F1EF2917F2D4FD00B5017E /* ITPullToRefreshScrollViewTests */, 295 | ); 296 | }; 297 | /* End PBXProject section */ 298 | 299 | /* Begin PBXResourcesBuildPhase section */ 300 | 91F1EF0717F2D4FD00B5017E /* Resources */ = { 301 | isa = PBXResourcesBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | 91F1EF1717F2D4FD00B5017E /* InfoPlist.strings in Resources */, 305 | 91F1EF2517F2D4FD00B5017E /* Images.xcassets in Resources */, 306 | 91F1EF1D17F2D4FD00B5017E /* Credits.rtf in Resources */, 307 | 91F1EF2317F2D4FD00B5017E /* MainMenu.xib in Resources */, 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | }; 311 | 91F1EF2817F2D4FD00B5017E /* Resources */ = { 312 | isa = PBXResourcesBuildPhase; 313 | buildActionMask = 2147483647; 314 | files = ( 315 | 91F1EF3517F2D4FD00B5017E /* InfoPlist.strings in Resources */, 316 | ); 317 | runOnlyForDeploymentPostprocessing = 0; 318 | }; 319 | /* End PBXResourcesBuildPhase section */ 320 | 321 | /* Begin PBXSourcesBuildPhase section */ 322 | 91F1EF0517F2D4FD00B5017E /* Sources */ = { 323 | isa = PBXSourcesBuildPhase; 324 | buildActionMask = 2147483647; 325 | files = ( 326 | 91915E3917F98453002031B0 /* NSBKeyframeAnimation.m in Sources */, 327 | 91915E3A17F98453002031B0 /* NSBKeyframeAnimationFunctions.c in Sources */, 328 | 91F1EF6217F2FD0C00B5017E /* ITPullToRefreshEdgeView.m in Sources */, 329 | 91F1EF2017F2D4FD00B5017E /* AppDelegate.m in Sources */, 330 | 91F1EF1917F2D4FD00B5017E /* main.m in Sources */, 331 | 91915E3F17F984A1002031B0 /* ITProgressIndicator.m in Sources */, 332 | 91F1EF6317F2FD0C00B5017E /* ITPullToRefreshScrollView.m in Sources */, 333 | 91EE6BFC17FAC2F9004A307A /* DuxScrollViewAnimation.m in Sources */, 334 | 91F1EF6117F2FD0C00B5017E /* ITPullToRefreshClipView.m in Sources */, 335 | ); 336 | runOnlyForDeploymentPostprocessing = 0; 337 | }; 338 | 91F1EF2617F2D4FD00B5017E /* Sources */ = { 339 | isa = PBXSourcesBuildPhase; 340 | buildActionMask = 2147483647; 341 | files = ( 342 | 91F1EF3717F2D4FD00B5017E /* ITPullToRefreshScrollViewTests.m in Sources */, 343 | ); 344 | runOnlyForDeploymentPostprocessing = 0; 345 | }; 346 | /* End PBXSourcesBuildPhase section */ 347 | 348 | /* Begin PBXTargetDependency section */ 349 | 91F1EF2F17F2D4FD00B5017E /* PBXTargetDependency */ = { 350 | isa = PBXTargetDependency; 351 | target = 91F1EF0817F2D4FD00B5017E /* ITPullToRefreshScrollView */; 352 | targetProxy = 91F1EF2E17F2D4FD00B5017E /* PBXContainerItemProxy */; 353 | }; 354 | /* End PBXTargetDependency section */ 355 | 356 | /* Begin PBXVariantGroup section */ 357 | 91F1EF1517F2D4FD00B5017E /* InfoPlist.strings */ = { 358 | isa = PBXVariantGroup; 359 | children = ( 360 | 91F1EF1617F2D4FD00B5017E /* en */, 361 | ); 362 | name = InfoPlist.strings; 363 | sourceTree = ""; 364 | }; 365 | 91F1EF1B17F2D4FD00B5017E /* Credits.rtf */ = { 366 | isa = PBXVariantGroup; 367 | children = ( 368 | 91F1EF1C17F2D4FD00B5017E /* en */, 369 | ); 370 | name = Credits.rtf; 371 | sourceTree = ""; 372 | }; 373 | 91F1EF2117F2D4FD00B5017E /* MainMenu.xib */ = { 374 | isa = PBXVariantGroup; 375 | children = ( 376 | 91F1EF2217F2D4FD00B5017E /* Base */, 377 | ); 378 | name = MainMenu.xib; 379 | sourceTree = ""; 380 | }; 381 | 91F1EF3317F2D4FD00B5017E /* InfoPlist.strings */ = { 382 | isa = PBXVariantGroup; 383 | children = ( 384 | 91F1EF3417F2D4FD00B5017E /* en */, 385 | ); 386 | name = InfoPlist.strings; 387 | sourceTree = ""; 388 | }; 389 | /* End PBXVariantGroup section */ 390 | 391 | /* Begin XCBuildConfiguration section */ 392 | 91F1EF3817F2D4FD00B5017E /* Debug */ = { 393 | isa = XCBuildConfiguration; 394 | buildSettings = { 395 | ALWAYS_SEARCH_USER_PATHS = NO; 396 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 397 | CLANG_CXX_LIBRARY = "libc++"; 398 | CLANG_ENABLE_OBJC_ARC = YES; 399 | CLANG_WARN_BOOL_CONVERSION = YES; 400 | CLANG_WARN_CONSTANT_CONVERSION = YES; 401 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 402 | CLANG_WARN_EMPTY_BODY = YES; 403 | CLANG_WARN_ENUM_CONVERSION = YES; 404 | CLANG_WARN_INFINITE_RECURSION = YES; 405 | CLANG_WARN_INT_CONVERSION = YES; 406 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 407 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 408 | CLANG_WARN_UNREACHABLE_CODE = YES; 409 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 410 | COPY_PHASE_STRIP = NO; 411 | ENABLE_STRICT_OBJC_MSGSEND = YES; 412 | ENABLE_TESTABILITY = YES; 413 | GCC_C_LANGUAGE_STANDARD = gnu99; 414 | GCC_DYNAMIC_NO_PIC = NO; 415 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 416 | GCC_NO_COMMON_BLOCKS = YES; 417 | GCC_OPTIMIZATION_LEVEL = 0; 418 | GCC_PREPROCESSOR_DEFINITIONS = ( 419 | "DEBUG=1", 420 | "$(inherited)", 421 | ); 422 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 423 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 424 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 425 | GCC_WARN_UNDECLARED_SELECTOR = YES; 426 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 427 | GCC_WARN_UNUSED_FUNCTION = YES; 428 | GCC_WARN_UNUSED_VARIABLE = YES; 429 | MACOSX_DEPLOYMENT_TARGET = 10.8; 430 | ONLY_ACTIVE_ARCH = YES; 431 | SDKROOT = macosx; 432 | }; 433 | name = Debug; 434 | }; 435 | 91F1EF3917F2D4FD00B5017E /* Release */ = { 436 | isa = XCBuildConfiguration; 437 | buildSettings = { 438 | ALWAYS_SEARCH_USER_PATHS = NO; 439 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 440 | CLANG_CXX_LIBRARY = "libc++"; 441 | CLANG_ENABLE_OBJC_ARC = YES; 442 | CLANG_WARN_BOOL_CONVERSION = YES; 443 | CLANG_WARN_CONSTANT_CONVERSION = YES; 444 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 445 | CLANG_WARN_EMPTY_BODY = YES; 446 | CLANG_WARN_ENUM_CONVERSION = YES; 447 | CLANG_WARN_INFINITE_RECURSION = YES; 448 | CLANG_WARN_INT_CONVERSION = YES; 449 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 450 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 451 | CLANG_WARN_UNREACHABLE_CODE = YES; 452 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 453 | COPY_PHASE_STRIP = YES; 454 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 455 | ENABLE_NS_ASSERTIONS = NO; 456 | ENABLE_STRICT_OBJC_MSGSEND = YES; 457 | GCC_C_LANGUAGE_STANDARD = gnu99; 458 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 459 | GCC_NO_COMMON_BLOCKS = YES; 460 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 461 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 462 | GCC_WARN_UNDECLARED_SELECTOR = YES; 463 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 464 | GCC_WARN_UNUSED_FUNCTION = YES; 465 | GCC_WARN_UNUSED_VARIABLE = YES; 466 | MACOSX_DEPLOYMENT_TARGET = 10.8; 467 | SDKROOT = macosx; 468 | }; 469 | name = Release; 470 | }; 471 | 91F1EF3B17F2D4FD00B5017E /* Debug */ = { 472 | isa = XCBuildConfiguration; 473 | buildSettings = { 474 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 475 | COMBINE_HIDPI_IMAGES = YES; 476 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 477 | GCC_PREFIX_HEADER = "ITPullToRefreshScrollView/ITPullToRefreshScrollView-Prefix.pch"; 478 | INFOPLIST_FILE = "ITPullToRefreshScrollView/ITPullToRefreshScrollView-Info.plist"; 479 | PRODUCT_BUNDLE_IDENTIFIER = "ch.ilijatovilo.${PRODUCT_NAME:rfc1034identifier}"; 480 | PRODUCT_NAME = "$(TARGET_NAME)"; 481 | WRAPPER_EXTENSION = app; 482 | }; 483 | name = Debug; 484 | }; 485 | 91F1EF3C17F2D4FD00B5017E /* Release */ = { 486 | isa = XCBuildConfiguration; 487 | buildSettings = { 488 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 489 | COMBINE_HIDPI_IMAGES = YES; 490 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 491 | GCC_PREFIX_HEADER = "ITPullToRefreshScrollView/ITPullToRefreshScrollView-Prefix.pch"; 492 | INFOPLIST_FILE = "ITPullToRefreshScrollView/ITPullToRefreshScrollView-Info.plist"; 493 | PRODUCT_BUNDLE_IDENTIFIER = "ch.ilijatovilo.${PRODUCT_NAME:rfc1034identifier}"; 494 | PRODUCT_NAME = "$(TARGET_NAME)"; 495 | WRAPPER_EXTENSION = app; 496 | }; 497 | name = Release; 498 | }; 499 | 91F1EF3E17F2D4FD00B5017E /* Debug */ = { 500 | isa = XCBuildConfiguration; 501 | buildSettings = { 502 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ITPullToRefreshScrollView.app/Contents/MacOS/ITPullToRefreshScrollView"; 503 | COMBINE_HIDPI_IMAGES = YES; 504 | FRAMEWORK_SEARCH_PATHS = ( 505 | "$(DEVELOPER_FRAMEWORKS_DIR)", 506 | "$(inherited)", 507 | ); 508 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 509 | GCC_PREFIX_HEADER = "ITPullToRefreshScrollView/ITPullToRefreshScrollView-Prefix.pch"; 510 | GCC_PREPROCESSOR_DEFINITIONS = ( 511 | "DEBUG=1", 512 | "$(inherited)", 513 | ); 514 | INFOPLIST_FILE = "ITPullToRefreshScrollViewTests/ITPullToRefreshScrollViewTests-Info.plist"; 515 | PRODUCT_BUNDLE_IDENTIFIER = "ch.ilijatovilo.${PRODUCT_NAME:rfc1034identifier}"; 516 | PRODUCT_NAME = "$(TARGET_NAME)"; 517 | TEST_HOST = "$(BUNDLE_LOADER)"; 518 | WRAPPER_EXTENSION = xctest; 519 | }; 520 | name = Debug; 521 | }; 522 | 91F1EF3F17F2D4FD00B5017E /* Release */ = { 523 | isa = XCBuildConfiguration; 524 | buildSettings = { 525 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ITPullToRefreshScrollView.app/Contents/MacOS/ITPullToRefreshScrollView"; 526 | COMBINE_HIDPI_IMAGES = YES; 527 | FRAMEWORK_SEARCH_PATHS = ( 528 | "$(DEVELOPER_FRAMEWORKS_DIR)", 529 | "$(inherited)", 530 | ); 531 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 532 | GCC_PREFIX_HEADER = "ITPullToRefreshScrollView/ITPullToRefreshScrollView-Prefix.pch"; 533 | INFOPLIST_FILE = "ITPullToRefreshScrollViewTests/ITPullToRefreshScrollViewTests-Info.plist"; 534 | PRODUCT_BUNDLE_IDENTIFIER = "ch.ilijatovilo.${PRODUCT_NAME:rfc1034identifier}"; 535 | PRODUCT_NAME = "$(TARGET_NAME)"; 536 | TEST_HOST = "$(BUNDLE_LOADER)"; 537 | WRAPPER_EXTENSION = xctest; 538 | }; 539 | name = Release; 540 | }; 541 | /* End XCBuildConfiguration section */ 542 | 543 | /* Begin XCConfigurationList section */ 544 | 91F1EF0417F2D4FD00B5017E /* Build configuration list for PBXProject "ITPullToRefreshScrollView" */ = { 545 | isa = XCConfigurationList; 546 | buildConfigurations = ( 547 | 91F1EF3817F2D4FD00B5017E /* Debug */, 548 | 91F1EF3917F2D4FD00B5017E /* Release */, 549 | ); 550 | defaultConfigurationIsVisible = 0; 551 | defaultConfigurationName = Release; 552 | }; 553 | 91F1EF3A17F2D4FD00B5017E /* Build configuration list for PBXNativeTarget "ITPullToRefreshScrollView" */ = { 554 | isa = XCConfigurationList; 555 | buildConfigurations = ( 556 | 91F1EF3B17F2D4FD00B5017E /* Debug */, 557 | 91F1EF3C17F2D4FD00B5017E /* Release */, 558 | ); 559 | defaultConfigurationIsVisible = 0; 560 | defaultConfigurationName = Release; 561 | }; 562 | 91F1EF3D17F2D4FD00B5017E /* Build configuration list for PBXNativeTarget "ITPullToRefreshScrollViewTests" */ = { 563 | isa = XCConfigurationList; 564 | buildConfigurations = ( 565 | 91F1EF3E17F2D4FD00B5017E /* Debug */, 566 | 91F1EF3F17F2D4FD00B5017E /* Release */, 567 | ); 568 | defaultConfigurationIsVisible = 0; 569 | defaultConfigurationName = Release; 570 | }; 571 | /* End XCConfigurationList section */ 572 | }; 573 | rootObject = 91F1EF0117F2D4FD00B5017E /* Project object */; 574 | } 575 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/ITPullToRefreshScrollView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ITPullToRefreshScrollView 4 | // 5 | // Created by Ilija Tovilo on 9/25/13. 6 | // Copyright (c) 2013 Ilija Tovilo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ITPullToRefreshScrollView.h" 11 | 12 | @interface AppDelegate : NSObject 16 | 17 | @property (assign) IBOutlet NSWindow *window; 18 | @property (assign) IBOutlet NSTableView *tableView; 19 | @property (assign) IBOutlet ITPullToRefreshScrollView *scrollView; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/ITPullToRefreshScrollView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ITPullToRefreshScrollView 4 | // 5 | // Created by Ilija Tovilo on 9/25/13. 6 | // Copyright (c) 2013 Ilija Tovilo. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ITPullToRefreshScrollView.h" 11 | 12 | 13 | @interface AppDelegate () { 14 | NSMutableArray *data; 15 | } 16 | @end 17 | 18 | 19 | @implementation AppDelegate 20 | 21 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 22 | { 23 | data = [@[ ] mutableCopy]; 24 | self.scrollView.refreshableEdges = ITPullToRefreshEdgeTop | ITPullToRefreshEdgeBottom; 25 | [self.tableView reloadData]; 26 | } 27 | 28 | - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { 29 | return data.count; 30 | } 31 | 32 | - (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { 33 | NSTableCellView *cellView = [tableView makeViewWithIdentifier:@"default" owner:self]; 34 | cellView.textField.stringValue = data[row]; 35 | 36 | return cellView; 37 | } 38 | 39 | - (void)pullToRefreshView:(ITPullToRefreshScrollView *)scrollView didStartRefreshingEdge:(ITPullToRefreshEdge)edge { 40 | double delayInSeconds = ((arc4random() % 10) / 10.0) * 5.0; 41 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 42 | dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^(void){ 43 | dispatch_sync(dispatch_get_main_queue(), ^{ 44 | if (edge & ITPullToRefreshEdgeTop) { 45 | [data insertObject:@"Test 1" atIndex:0]; 46 | [data insertObject:@"Test 2" atIndex:0]; 47 | } else if (edge & ITPullToRefreshEdgeBottom) { 48 | for (int i = 0; i < 50; i++) { 49 | [data addObject:[NSString stringWithFormat:@"Test %d", i]]; 50 | } 51 | } 52 | 53 | [scrollView stopRefreshingEdge:edge]; 54 | }); 55 | }); 56 | } 57 | 58 | - (void)pullToRefreshView:(ITPullToRefreshScrollView *)scrollView didStopRefreshingEdge:(ITPullToRefreshEdge)edge { 59 | NSRange range; 60 | 61 | if (edge & ITPullToRefreshEdgeTop) { 62 | range = NSMakeRange(0, 2); 63 | } else { 64 | range = NSMakeRange(data.count - 50, 50); 65 | } 66 | 67 | [self.tableView beginUpdates]; 68 | { 69 | [self.tableView insertRowsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:range] 70 | withAnimation:NSTableViewAnimationSlideDown]; 71 | } 72 | [self.tableView endUpdates]; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/ITPullToRefreshScrollView/ITPullToRefreshScrollView-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | ${MACOSX_DEPLOYMENT_TARGET} 27 | NSHumanReadableCopyright 28 | Copyright © 2013 Ilija Tovilo. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/ITPullToRefreshScrollView/ITPullToRefreshScrollView-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/ITPullToRefreshScrollView/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/ITPullToRefreshScrollView/en.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} 2 | {\colortbl;\red255\green255\blue255;} 3 | \paperw9840\paperh8400 4 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural 5 | 6 | \f0\b\fs24 \cf0 Engineering: 7 | \b0 \ 8 | Some people\ 9 | \ 10 | 11 | \b Human Interface Design: 12 | \b0 \ 13 | Some other people\ 14 | \ 15 | 16 | \b Testing: 17 | \b0 \ 18 | Hopefully not nobody\ 19 | \ 20 | 21 | \b Documentation: 22 | \b0 \ 23 | Whoever\ 24 | \ 25 | 26 | \b With special thanks to: 27 | \b0 \ 28 | Mom\ 29 | } 30 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/ITPullToRefreshScrollView/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/ITPullToRefreshScrollView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ITPullToRefreshScrollView 4 | // 5 | // Created by Ilija Tovilo on 9/25/13. 6 | // Copyright (c) 2013 Ilija Tovilo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) 12 | { 13 | return NSApplicationMain(argc, argv); 14 | } 15 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/ITPullToRefreshScrollViewTests/ITPullToRefreshScrollViewTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/ITPullToRefreshScrollViewTests/ITPullToRefreshScrollViewTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ITPullToRefreshScrollViewTests.m 3 | // ITPullToRefreshScrollViewTests 4 | // 5 | // Created by Ilija Tovilo on 9/25/13. 6 | // Copyright (c) 2013 Ilija Tovilo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ITPullToRefreshScrollViewTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation ITPullToRefreshScrollViewTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /ITPullToRefreshScrollView/ITPullToRefreshScrollViewTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ITPullToRefreshScrollView 2 | ========================= 3 | 4 | `ITPullToRefreshScrollView` is a subclass of `NSScrollView` which allows iOS 7 style refreshing by pulling. 5 | `ITPullToRefreshScrollView` was created for [Play by Play](http://playbyplayapp.com), development funded by [David Keegan](http://davidkeegan.com). 6 | 7 | ![](./demo.gif) 8 | 9 | 10 | Thanks to 11 | --------- 12 | 13 | - [Sasmito Adibowo](https://github.com/adib), a lot of code is based on [RefreshableScrollView](https://github.com/adib/RefreshableScrollView) 14 | - [Abhi Beckert](https://github.com/abhibeckert) for the [DuxScrollViewAnimation class](https://github.com/abhibeckert/Dux/blob/master/Dux/DuxScrollViewAnimation.m). 15 | - [NachoSoto](https://github.com/NachoSoto) for the [NSBKeyframeAnimation class](https://github.com/NachoSoto/NSBKeyframeAnimation) 16 | 17 | 18 | Usage 19 | ----- 20 | 21 | ### Include files in project 22 | 23 | `ITPullToRefreshScrollView` has two submodules. 24 | You need to copy the following files: 25 | 26 | *Files under /ITPullToRefreshScrollView/Classes/* 27 | 28 | * `ITPullToRefreshScrollView.h` 29 | * `ITPullToRefreshScrollView.m` 30 | * `ITPullToRefreshClipView.h` 31 | * `ITPullToRefreshClipView.m` 32 | * `ITPullToRefreshEdgeView.h` 33 | * `ITPullToRefreshEdgeView.m` 34 | * `DuxScrollViewAnimation.h` 35 | * `DuxScrollViewAnimation.m` 36 | 37 | ----------- 38 | 39 | *Files under /Modules/ITProgressIndicator/ITProgressIndicator/Classes/* 40 | 41 | * `ITProgressIndicator.h` 42 | * `ITProgressIndicator.m` 43 | * `NSBezierPath+Geometry.h` 44 | * `NSBezierPath+Geometry.m` 45 | 46 | ----------- 47 | 48 | *Files under /Modules/NSBKeyframeAnimation/NSBKeyframeAnimation/Classes/NSBKeyframeAnimation/* 49 | 50 | * `NSBKeyframeAnimation.h` 51 | * `NSBKeyframeAnimation.m` 52 | * `NSBKeyframeAnimationFunctions.h` 53 | * `NSBKeyframeAnimationFunctions.c` 54 | 55 | ----------- 56 | 57 | Make sure to copy them to the project, and to add them to the target. 58 | 59 | 60 | ### Use in a project 61 | 62 | **Make sure to check out the sample project.** 63 | 64 | Set the custom class of your scroll view in Interface Builder to `ITPullToRefreshScrollView`. 65 | Next you need to connect a `IBOutlet` to your scroll view. 66 | ``` objc 67 | @property (assign) IBOutlet ITPullToRefreshScrollView *scrollView; 68 | ``` 69 | 70 | Then you can configure in code, which edges of the scroll view should be refreshable. 71 | ``` objc 72 | // Make top & bottom refreshable 73 | self.scrollView.refreshableEdges = ITPullToRefreshEdgeTop | ITPullToRefreshEdgeBottom; 74 | ``` 75 | 76 | To receive notifications from the scroll view, you need to create a delegate. 77 | To do this, your delegate class must implement the `ITPullToRefreshScrollViewDelegate` protocol. 78 | You can then implement the following methods. 79 | ``` objc 80 | /** 81 | * This method get's invoked when the scroll view started refreshing 82 | * 83 | * @param scrollView - The scroll view that started refreshing 84 | * @param edge - The edge that started refreshing 85 | */ 86 | - (void)pullToRefreshView:(ITPullToRefreshScrollView *)scrollView 87 | didStartRefreshingEdge:(ITPullToRefreshEdge)edge { 88 | 89 | // Do stuff that takes very long 90 | 91 | // Don't forget to call this line! 92 | [scrollView stopRefreshingEdge:edge]; 93 | } 94 | 95 | /** 96 | * This method get's invoked when the scroll view stopped refreshing 97 | * 98 | * @param scrollView - The scroll view that stopped refreshing 99 | * @param edge - The edge that stopped refreshing 100 | */ 101 | - (void)pullToRefreshView:(ITPullToRefreshScrollView *)scrollView 102 | didStopRefreshingEdge:(ITPullToRefreshEdge)edge { 103 | 104 | // Do UI stuff 105 | } 106 | ``` 107 | 108 | To receive notifications, you finally have to set the delegate of the scroll view. 109 | ``` objc 110 | self.scrollView.delegate = self; 111 | ``` 112 | 113 | ### Customisation 114 | 115 | To create custom `ITPullToRefreshEdgeView` subclasses, override the following methods: 116 | 117 | ``` objc 118 | // -------------------------- 119 | // ------------------- Events 120 | // -------------------------- 121 | 122 | /** 123 | * This method is called when the edge is triggered 124 | * 125 | * @param scrollView - The sender scroll view 126 | */ 127 | - (void)pullToRefreshScrollViewDidTriggerRefresh:(ITPullToRefreshScrollView *)scrollView; 128 | 129 | /** 130 | * This method is called when the edge is untriggered 131 | * 132 | * @param scrollView - The sender scroll view 133 | */ 134 | - (void)pullToRefreshScrollViewDidUntriggerRefresh:(ITPullToRefreshScrollView *)scrollView; 135 | 136 | /** 137 | * This method is called when the edge starts refreshing 138 | * 139 | * @param scrollView - The sender scroll view 140 | */ 141 | - (void)pullToRefreshScrollViewDidStartRefreshing:(ITPullToRefreshScrollView *)scrollView; 142 | 143 | /** 144 | * This method is called when the edge stops refreshing 145 | * 146 | * @param scrollView - The sender scroll view 147 | */ 148 | - (void)pullToRefreshScrollViewDidStopRefreshing:(ITPullToRefreshScrollView *)scrollView; 149 | 150 | /** 151 | * This method is called when part of the edge view becomes visible 152 | * 153 | * @param scrollView - The sender scroll view 154 | * @param progress - The amount of the edge view that is visible (from 0.0 to 1.0) 155 | */ 156 | - (void)pullToRefreshScrollView:(ITPullToRefreshScrollView *)scrollView didScrollWithProgress:(CGFloat)progress; 157 | 158 | 159 | 160 | // -------------------------- 161 | // ------------ Customisation 162 | // -------------------------- 163 | 164 | /** 165 | * Override this to remove the progress indicator and create other components 166 | */ 167 | - (void)installComponents; 168 | 169 | /** 170 | * The height of the edge view. 171 | * Override this method to achieve a custom edge view height. 172 | * 173 | * @return The height of the edge view 174 | */ 175 | - (CGFloat)edgeViewHeight; 176 | 177 | /** 178 | * Override this method to draw a custom background. 179 | * You can also just override `drawRect:` and to all the drawing on your own. 180 | * 181 | * @param The rect which should be drawn on 182 | */ 183 | - (void)drawBackgroundInRect:(NSRect)dirtyRect; 184 | ``` 185 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iluuu1994/ITPullToRefreshScrollView/70ffb846b5ca868634a5ff0dfcfc5a7452785022/demo.gif --------------------------------------------------------------------------------