├── Assets
└── .gitkeep
├── test.txt
├── Classes
├── ios
│ └── .gitkeep
└── osx
│ ├── .gitkeep
│ ├── BSRefreshableClipView.h
│ ├── BSRefreshableScrollView_Private.h
│ ├── BSRefreshableScrollView.h
│ ├── BSRefreshableClipView.m
│ └── BSRefreshableScrollView.m
├── Example
├── Podfile
├── RefreshableScrollView
│ ├── en.lproj
│ │ ├── InfoPlist.strings
│ │ ├── Credits.rtf
│ │ └── MainMenu.xib
│ ├── RefreshableScrollView-Prefix.pch
│ ├── RefreshableScrollView-Info.plist
│ ├── main.m
│ ├── BSAppDelegate.h
│ └── BSAppDelegate.m
└── RefreshableScrollView.xcodeproj
│ ├── xcuserdata
│ └── adib.xcuserdatad
│ │ ├── xcdebugger
│ │ └── Breakpoints.xcbkptlist
│ │ └── xcschemes
│ │ ├── xcschememanagement.plist
│ │ ├── RefreshableScrollView 2.xcscheme
│ │ └── RefreshableScrollView.xcscheme
│ ├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── adib.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
│ └── project.pbxproj
├── CHANGELOG.md
├── .gitignore
├── LICENSE
├── BSRefreshableScrollView.podspec
├── README.md
└── Rakefile
/Assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test.txt:
--------------------------------------------------------------------------------
1 | test
2 |
--------------------------------------------------------------------------------
/Classes/ios/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Classes/osx/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | pod "BSRefreshableScrollView", '~> 0.1'
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # BSRefreshableScrollView CHANGELOG
2 |
3 | ## 0.1.0
4 |
5 | Initial release.
6 |
--------------------------------------------------------------------------------
/Example/RefreshableScrollView/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/Example/RefreshableScrollView.xcodeproj/xcuserdata/adib.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/Example/RefreshableScrollView/RefreshableScrollView-Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header for all source files of the 'RefreshableScrollView' target in the 'RefreshableScrollView' project
3 | //
4 |
5 | #ifdef __OBJC__
6 | #import
7 | #endif
8 |
--------------------------------------------------------------------------------
/Example/RefreshableScrollView.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/RefreshableScrollView.xcodeproj/project.xcworkspace/xcuserdata/adib.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adib/RefreshableScrollView/HEAD/Example/RefreshableScrollView.xcodeproj/project.xcworkspace/xcuserdata/adib.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata
15 | *.xccheckout
16 | profile
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 |
22 |
--------------------------------------------------------------------------------
/Example/RefreshableScrollView/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 |
--------------------------------------------------------------------------------
/Example/RefreshableScrollView.xcodeproj/xcuserdata/adib.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | RefreshableScrollView 2.xcscheme
8 |
9 | orderHint
10 | 2
11 |
12 | RefreshableScrollView.xcscheme
13 |
14 | orderHint
15 | 0
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 | C00D9550165CC61B009F59FF
21 |
22 | primary
23 |
24 |
25 | C0573439165A23AF00087E78
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Example/RefreshableScrollView/RefreshableScrollView-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | com.basilsalad.${PRODUCT_NAME:rfc1034identifier}
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 © 2012 Basil Salad Software. All rights reserved.
29 | NSMainNibFile
30 | MainMenu
31 | NSPrincipalClass
32 | NSApplication
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Example/RefreshableScrollView/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // RefreshableScrollView
4 | //
5 | // Created by Sasmito Adibowo on 19-11-12.
6 | // Copyright (c) 2012 Basil Salad Software. All rights reserved.
7 | // http://basilsalad.com
8 | //
9 | // Licensed under the BSD License
10 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
11 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
12 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
13 | // SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
15 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
16 | // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
17 | // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
18 | // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19 | //
20 |
21 |
22 |
23 | #import
24 |
25 | int main(int argc, char *argv[])
26 | {
27 | return NSApplicationMain(argc, (const char **)argv);
28 | }
29 |
--------------------------------------------------------------------------------
/Classes/osx/BSRefreshableClipView.h:
--------------------------------------------------------------------------------
1 | //
2 | // BSRefreshableClipView.h
3 | // RefreshableScrollView
4 | //
5 | // Created by Sasmito Adibowo on 19-11-12.
6 | // Copyright (c) 2012 Basil Salad Software. All rights reserved.
7 | // http://basilsalad.com
8 | //
9 | // Licensed under the BSD License
10 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
11 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
12 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
13 | // SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
15 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
16 | // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
17 | // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
18 | // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19 | //
20 |
21 | #import
22 |
23 | @interface BSRefreshableClipView : NSClipView
24 |
25 | -(instancetype) initWithOriginalClipView:(NSClipView*) clipView;
26 |
27 | @end
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013-2014, Sasmito Adibowo
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 |
8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 |
10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11 |
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/Example/RefreshableScrollView/BSAppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // BSAppDelegate.h
3 | // RefreshableScrollView
4 | //
5 | // Created by Sasmito Adibowo on 19-11-12.
6 | // Copyright (c) 2012 Basil Salad Software. All rights reserved.
7 | // http://basilsalad.com
8 | //
9 | // Licensed under the BSD License
10 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
11 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
12 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
13 | // SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
15 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
16 | // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
17 | // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
18 | // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19 | //
20 |
21 |
22 | #import
23 |
24 | #import "BSRefreshableScrollView.h"
25 |
26 | @interface BSAppDelegate : NSObject
27 |
28 | @property (assign) IBOutlet NSWindow *window;
29 |
30 | @property (nonatomic,weak) IBOutlet BSRefreshableScrollView* refreshableScrollView;
31 |
32 | - (IBAction)stopRefreshTop:(id)sender;
33 | - (IBAction)stopRefreshBottom:(id)sender;
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/Classes/osx/BSRefreshableScrollView_Private.h:
--------------------------------------------------------------------------------
1 | //
2 | // BSRefreshableScrollView_Private.h
3 | // RefreshableScrollView
4 | //
5 | // Created by Sasmito Adibowo on 19-11-12.
6 | // Copyright (c) 2012 Basil Salad Software. All rights reserved.
7 | // http://basilsalad.com
8 | //
9 | // Licensed under the BSD License
10 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
11 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
12 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
13 | // SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
15 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
16 | // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
17 | // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
18 | // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19 | //
20 |
21 |
22 |
23 | #import "BSRefreshableScrollView.h"
24 |
25 | @interface BSRefreshableScrollView ()
26 |
27 | @property (nonatomic,strong) NSProgressIndicator* topProgressIndicator;
28 | @property (nonatomic,strong) NSProgressIndicator* bottomProgressIndicator;
29 |
30 | @property (nonatomic,readonly,strong) NSView* headerView;
31 | @property (nonatomic,readonly,strong) NSView* footerView;
32 |
33 | @property (nonatomic,readwrite) BSRefreshableScrollViewSide refreshingSides;
34 | @property (nonatomic,readwrite) BSRefreshableScrollViewSide triggeredRefreshingSides;
35 |
36 | @property (nonatomic,readwrite) BSRefreshableScrollViewSide activatedRefreshingSides;
37 |
38 |
39 | @end
40 |
--------------------------------------------------------------------------------
/BSRefreshableScrollView.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint NAME.podspec' to ensure this is a
3 | # valid spec and remove all comments before submitting the spec.
4 | #
5 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
6 | #
7 | Pod::Spec.new do |s|
8 | s.name = "BSRefreshableScrollView"
9 | s.version = "1.0.1"
10 | s.summary = "An `NSScrollView` subclass that supports pull-to-refresh both in the top and bottom edges."
11 | s.description = <<-DESC
12 | The intended usage is for hosting a timeline-like view that shows a list of messages in reverse-chronological order.
13 |
14 | * Get iOS like pull-to-refresh experience on OS X complete with pull indicator.
15 | * The bottom pulling upwards to reveal the bottom side will be good for loading older data.
16 | DESC
17 | s.homepage = "http://cutecoder.org/tag/bsrefreshablescrollview/"
18 | s.screenshots = "http://i0.wp.com/cutecoder.org/wp-content/uploads/2012/11/Bi-Directional-Refreshable-Scroll-View.png"
19 | s.license = 'BSD'
20 | s.author = { "Sasmito Adibowo" => "adib@basil-salad.com" }
21 | s.source = { :git => "https://github.com/adib/RefreshableScrollView.git", :tag => 'release-' + s.version.to_s }
22 | s.social_media_url = 'https://www.facebook.com/cutecoder'
23 |
24 | # s.platform = :osx, '10.8'
25 | # s.ios.deployment_target = '5.0'
26 | s.osx.deployment_target = '10.8'
27 | s.requires_arc = true
28 |
29 | s.source_files = 'Classes/osx/*'
30 | # s.resources = 'Assets/*.png'
31 |
32 | # s.ios.exclude_files = 'Classes/osx'
33 | s.osx.exclude_files = 'Classes/ios'
34 | s.public_header_files = 'Classes/**/BSRefreshableScrollView.h'
35 | # s.frameworks = 'SomeFramework', 'AnotherFramework'
36 | # s.dependency 'JSONKit', '~> 1.4'
37 | end
38 |
--------------------------------------------------------------------------------
/Example/RefreshableScrollView/BSAppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // BSAppDelegate.m
3 | // RefreshableScrollView
4 | //
5 | // Created by Sasmito Adibowo on 19-11-12.
6 | // Copyright (c) 2012 Basil Salad Software. All rights reserved.
7 | // http://basilsalad.com
8 | //
9 | // Licensed under the BSD License
10 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
11 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
12 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
13 | // SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
15 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
16 | // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
17 | // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
18 | // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19 | //
20 |
21 |
22 | #import "BSAppDelegate.h"
23 |
24 |
25 | @implementation BSAppDelegate
26 |
27 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
28 | {
29 | // Insert code here to initialize your application
30 | [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"];
31 | }
32 |
33 |
34 | -(void)awakeFromNib
35 | {
36 | [super awakeFromNib];
37 | self.refreshableScrollView.refreshableSides = BSRefreshableScrollViewSideTop | BSRefreshableScrollViewSideBottom;
38 | }
39 |
40 |
41 | #pragma mark BSRefreshableScrollViewDelegate
42 |
43 | -(BOOL) scrollView:(BSRefreshableScrollView*) aScrollView startRefreshSide:(BSRefreshableScrollViewSide) refreshableSide
44 | {
45 | return YES;
46 | }
47 |
48 | #pragma mark Action Handlers
49 |
50 | - (IBAction)stopRefreshTop:(id)sender
51 | {
52 | [self.refreshableScrollView stopRefreshingSide:BSRefreshableScrollViewSideTop];
53 | }
54 |
55 |
56 | - (IBAction)stopRefreshBottom:(id)sender
57 | {
58 | [self.refreshableScrollView stopRefreshingSide:BSRefreshableScrollViewSideBottom];
59 | }
60 |
61 | @end
62 |
--------------------------------------------------------------------------------
/Example/RefreshableScrollView.xcodeproj/xcuserdata/adib.xcuserdatad/xcschemes/RefreshableScrollView 2.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
42 |
43 |
44 |
45 |
51 |
52 |
54 |
55 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/Example/RefreshableScrollView.xcodeproj/xcuserdata/adib.xcuserdatad/xcschemes/RefreshableScrollView.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
51 |
52 |
58 |
59 |
60 |
61 |
62 |
63 |
69 |
70 |
76 |
77 |
78 |
79 |
81 |
82 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RefreshableScrollView
2 |
3 | An `NSScrollView` subclass that supports pull-to-refresh both in the top and bottom edges. The intended usage is for hosting a timeline-like view that shows a list of messages in reverse-chronological order.
4 |
5 |
6 |
7 | ## Getting Started
8 |
9 | Attach `RefreshableScrollView.xcodeproj` into your OS X project and add `libRefreshableScrollView.a` as a dependency in your project. Also include the public header file `BSRefreshableScrollView.h` into your project's user header search paths.
10 |
11 | Play around with the included example application to see the class in action and how it is being used. The video demo above shows a recording of this sample application.
12 |
13 | ## How to use
14 |
15 | Use `BSRefreshableScrollView` as a replacement of `NSScrollView`. If you use Xcode's interface builder to add instances of `NSTableView` or `NSOutlineView` (among others), by default it will enclose those objects inside an `NSScrollView`. Highlight this `NSScrollView` container, go to the object' identity inspector and type in `BSRefreshableScrollView` as the scroll view's class name.
16 |
17 | Then you'll need to write a delegate that conforms to `BSRefreshableScrollViewDelegate` protocol. Most likely this will be the `xib`'s file owner – a view controller or a window controller. Attach this delegate class to the `refreshableDelegate` outlet of the scroll view.
18 |
19 | In `awakeFromNib` method of the delegate, setup which sides that supports pull-to-refresh. This is important – without enabling these flags, `BSRefreshableScrollView` will behave just like a plain old `NSScrollView`. If you don't use `xib` files then you'll need to find an appropriate place to set these flags. Note that you can clear any of these flags to disable refresh for their respective sides even when the view is already visible.
20 |
21 |
22 | -(void)awakeFromNib
23 | {
24 | [super awakeFromNib];
25 | self.refreshableScrollView.refreshableSides = BSRefreshableScrollViewSideTop | BSRefreshableScrollViewSideBottom;
26 | }
27 |
28 | Start the refresh process in your delegate by implementing method `scrollView: startRefreshSide:` and return `YES` if the process was successfully started. The scroll view will then display an indeterminate progress indicator at the appropriate edge. If for some reason you couldn't initiate refresh for that side, simply return `NO` and the scroll view will behave as if nothing happened.
29 |
30 | #pragma mark BSRefreshableScrollViewDelegate
31 |
32 | -(BOOL) scrollView:(BSRefreshableScrollView*) aScrollView startRefreshSide:(BSRefreshableScrollViewSide) refreshableSide
33 | {
34 | if (refreshableSide == BSRefreshableScrollViewSideTop) {
35 | // initiate refresh process for the top edge of the scroll view
36 | // ...
37 | return YES; // tell the scroll view to display the progress indicator
38 | } else if (refreshableSide == BSRefreshableScrollViewSideBottom) {
39 | // initiate refresh process for the bottom edge of the scroll view
40 | // ...
41 | return YES;
42 | }
43 | return NO;
44 | }
45 |
46 | When the refresh process have completed (i.e. the network service have returned a result or timed out), call `stopRefreshingSide:` and give it the appropriate edge to stop. If there was no refresh currently in progress at that side this calling this method will have no effect. For example:
47 |
48 | - (IBAction)stopRefreshTop:(id)sender
49 | {
50 | [self.refreshableScrollView stopRefreshingSide:BSRefreshableScrollViewSideTop];
51 | }
52 |
53 | ## License
54 |
55 | This project is licensed under the BSD license. Please let me know () if you use it for something interesting.
56 |
57 |
58 |
59 | Sasmito Adibowo
60 | http://cutecoder.org
61 |
62 |
63 |
--------------------------------------------------------------------------------
/Classes/osx/BSRefreshableScrollView.h:
--------------------------------------------------------------------------------
1 | //
2 | // BSRefreshableScrollView.h
3 | // RefreshableScrollView
4 | //
5 | // Created by Sasmito Adibowo on 19-11-12.
6 | // Copyright (c) 2012 Basil Salad Software. All rights reserved.
7 | // http://basilsalad.com
8 | //
9 | // Licensed under the BSD License
10 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
11 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
12 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
13 | // SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
15 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
16 | // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
17 | // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
18 | // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19 | //
20 |
21 |
22 | #import
23 |
24 | /**
25 | Indicates which sides that are refreshable.
26 | */
27 | enum {
28 | BSRefreshableScrollViewSideNone = 0,
29 | /**
30 | Pulling downwards will reveal a refresh indicator and when sufficiently pulled should trigger a data load for newer items.
31 | */
32 | BSRefreshableScrollViewSideTop = 1,
33 | /**
34 | Pulling upwards will reveal a refresh indicator and when sufficiently pulled should trigger a data load for older items.
35 | */
36 | BSRefreshableScrollViewSideBottom = 1 << 1,
37 | // left & right edges are for future expansion but not currently implemented
38 | /**
39 | Currently unimplemented.
40 | */
41 | BSRefreshableScrollViewSideLeft = 1 << 2,
42 | /**
43 | Currently unimplemented.
44 | */
45 | BSRefreshableScrollViewSideRight = 1 << 3
46 | };
47 |
48 | typedef NSUInteger BSRefreshableScrollViewSide;
49 |
50 | // ---
51 |
52 | @protocol BSRefreshableScrollViewDelegate;
53 | @protocol BSRefreshableScrollViewDataSource;
54 |
55 | // ---
56 |
57 | /**
58 | A scroll view that can be pulled downwards or upwards for the user to trigger refreshing of newer data or loading historical data.
59 |
60 | */
61 | @interface BSRefreshableScrollView : NSScrollView
62 |
63 | /**
64 | Which sides are refreshable, of type BSRefreshableScrollViewSide
65 | */
66 | @property (nonatomic) NSUInteger refreshableSides;
67 |
68 | /**
69 | Which sides are currently refreshing, of type BSRefreshableScrollViewSide
70 | */
71 | @property (nonatomic,readonly) NSUInteger refreshingSides;
72 |
73 | /**
74 | The object that will provide the refreshable data.
75 | */
76 | @property (nonatomic,weak) IBOutlet id refreshableDataSource;
77 | @property (nonatomic,weak) IBOutlet id refreshableDelegate;
78 |
79 | /**
80 | Call this when you have loaded the data to dismiss the refresh progress indicator.
81 | */
82 | -(void) stopRefreshingSide:(BSRefreshableScrollViewSide) refreshableSide;
83 |
84 |
85 | @end
86 |
87 | // ---
88 |
89 | /**
90 | The object that will provide the refreshable data.
91 | This protocol is present for future implementation and currently empty 😏
92 | */
93 | @protocol BSRefreshableScrollViewDataSource
94 |
95 |
96 | @end
97 |
98 |
99 | // ---
100 | /**
101 | The object that will provide the refreshable data.
102 | This protocol is present for future implementation and currently empty 😏
103 | */
104 | @protocol BSRefreshableScrollViewDelegate
105 |
106 | @optional
107 |
108 | /**
109 | Called by the scroll view to indicate that refresh should start.
110 | */
111 | -(BOOL) scrollView:(BSRefreshableScrollView*) aScrollView startRefreshSide:(BSRefreshableScrollViewSide) refreshableSide;
112 |
113 |
114 | @end
115 |
--------------------------------------------------------------------------------
/Classes/osx/BSRefreshableClipView.m:
--------------------------------------------------------------------------------
1 | //
2 | // BSRefreshableClipView.m
3 | // RefreshableScrollView
4 | //
5 | // Created by Sasmito Adibowo on 19-11-12.
6 | // Copyright (c) 2012 Basil Salad Software. All rights reserved.
7 | // http://basilsalad.com
8 | //
9 | // Licensed under the BSD License
10 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
11 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
12 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
13 | // SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
15 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
16 | // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
17 | // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
18 | // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19 | //
20 |
21 |
22 | #if !__has_feature(objc_arc)
23 | #error Need automatic reference counting to compile this.
24 | #endif
25 |
26 | #import "BSRefreshableScrollView_Private.h"
27 | #import "BSRefreshableClipView.h"
28 |
29 | @implementation BSRefreshableClipView
30 |
31 | -(instancetype) initWithOriginalClipView:(NSClipView*) clipView
32 | {
33 | if (self = [super initWithFrame:clipView.frame]) {
34 | [self setAutoresizingMask:[clipView autoresizingMask]];
35 | [self setAutoresizesSubviews:[clipView autoresizesSubviews]];
36 | [self setBackgroundColor:[clipView backgroundColor]];
37 | [self setTranslatesAutoresizingMaskIntoConstraints:[clipView translatesAutoresizingMaskIntoConstraints]];
38 | [self setCopiesOnScroll:[clipView copiesOnScroll]];
39 |
40 | // 10.9 only
41 | if ([clipView respondsToSelector:@selector(canDrawSubviewsIntoLayer)] && [self respondsToSelector:@selector(setCanDrawSubviewsIntoLayer:)]) {
42 | [self setCanDrawSubviewsIntoLayer:[clipView canDrawSubviewsIntoLayer]];
43 | }
44 | }
45 | return self;
46 | }
47 |
48 | -(NSView*) headerView
49 | {
50 | return [(BSRefreshableScrollView*) self.superview headerView];
51 | }
52 |
53 | -(NSView*) footerView
54 | {
55 | return [(BSRefreshableScrollView*) self.superview footerView];
56 | }
57 |
58 |
59 | -(BSRefreshableScrollViewSide) refreshingSides
60 | {
61 | return [(BSRefreshableScrollView*) self.superview refreshingSides];
62 | }
63 |
64 |
65 | #pragma mark NSClipView
66 |
67 | - (NSPoint)constrainScrollPoint:(NSPoint)proposedNewOrigin
68 | {
69 | NSPoint constrained = [super constrainScrollPoint:proposedNewOrigin];
70 | const NSRect clipViewBounds = self.bounds;
71 | NSView* const documentView = self.documentView;
72 | const NSRect documentFrame = documentView.frame;
73 |
74 | const BSRefreshableScrollViewSide refreshingSides = [self refreshingSides];
75 |
76 | if ((refreshingSides & BSRefreshableScrollViewSideTop) && proposedNewOrigin.y <= 0) {
77 | const NSRect headerFrame = [self headerView].frame;
78 | constrained.y = MAX(-headerFrame.size.height, proposedNewOrigin.y);
79 | }
80 |
81 | if((refreshingSides & BSRefreshableScrollViewSideBottom) ) {
82 | const NSRect footerFrame = [self footerView].frame;
83 | if (proposedNewOrigin.y > documentFrame.size.height - clipViewBounds.size.height) {
84 | const CGFloat maxHeight = documentFrame.size.height - clipViewBounds.size.height + footerFrame.size.height + 1;
85 | constrained.y = MIN(maxHeight, proposedNewOrigin.y);
86 | }
87 | }
88 |
89 | return constrained;
90 | }
91 |
92 |
93 | -(NSRect)documentRect
94 | {
95 | NSRect documentRect = [super documentRect];
96 | const BSRefreshableScrollViewSide refreshingSides = [self refreshingSides];
97 | if (refreshingSides & BSRefreshableScrollViewSideTop) {
98 | const NSRect headerFrame = [self headerView].frame;
99 | documentRect.size.height += headerFrame.size.height;
100 | documentRect.origin.y -= headerFrame.size.height;
101 | }
102 |
103 | if(refreshingSides & BSRefreshableScrollViewSideBottom) {
104 | const NSRect footerFrame = [self footerView].frame;
105 | documentRect.size.height += footerFrame.size.height ;
106 | }
107 |
108 | return documentRect;
109 | }
110 |
111 |
112 | #pragma mark NSView
113 |
114 | -(BOOL)isFlipped
115 | {
116 | return YES;
117 | }
118 |
119 |
120 | @end
121 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | desc "Runs the specs [EMPTY]"
2 | task :spec do
3 | # Provide your own implementation
4 | end
5 |
6 | task :version do
7 | git_remotes = `git remote`.strip.split("\n")
8 |
9 | if git_remotes.count > 0
10 | puts "-- fetching version number from github"
11 | sh 'git fetch'
12 |
13 | remote_version = remote_spec_version
14 | end
15 |
16 | if remote_version.nil?
17 | puts "There is no current released version. You're about to release a new Pod."
18 | version = "0.0.1"
19 | else
20 | puts "The current released version of your pod is " +
21 | remote_spec_version.to_s()
22 | version = suggested_version_number
23 | end
24 |
25 | puts "Enter the version you want to release (" + version + ") "
26 | new_version_number = $stdin.gets.strip
27 | if new_version_number == ""
28 | new_version_number = version
29 | end
30 |
31 | replace_version_number(new_version_number)
32 | end
33 |
34 | desc "Release a new version of the Pod (append repo=name to push to a private spec repo)"
35 | task :release do
36 | # Allow override of spec repo name using `repo=private` after task name
37 | repo = ENV["repo"] || "master"
38 |
39 | puts "* Running version"
40 | sh "rake version"
41 |
42 | unless ENV['SKIP_CHECKS']
43 | if `git symbolic-ref HEAD 2>/dev/null`.strip.split('/').last != 'master'
44 | $stderr.puts "[!] You need to be on the `master' branch in order to be able to do a release."
45 | exit 1
46 | end
47 |
48 | if `git tag`.strip.split("\n").include?(spec_version)
49 | $stderr.puts "[!] A tag for version `#{spec_version}' already exists. Change the version in the podspec"
50 | exit 1
51 | end
52 |
53 | puts "You are about to release `#{spec_version}`, is that correct? [y/n]"
54 | exit if $stdin.gets.strip.downcase != 'y'
55 | end
56 |
57 | puts "* Running specs"
58 | sh "rake spec"
59 |
60 | puts "* Linting the podspec"
61 | sh "pod lib lint"
62 |
63 | # Then release
64 | sh "git commit #{podspec_path} CHANGELOG.md -m 'Release #{spec_version}' --allow-empty"
65 | sh "git tag -a #{spec_version} -m 'Release #{spec_version}'"
66 | sh "git push origin master"
67 | sh "git push origin --tags"
68 | sh "pod push #{repo} #{podspec_path}"
69 | end
70 |
71 | # @return [Pod::Version] The version as reported by the Podspec.
72 | #
73 | def spec_version
74 | require 'cocoapods'
75 | spec = Pod::Specification.from_file(podspec_path)
76 | spec.version
77 | end
78 |
79 | # @return [Pod::Version] The version as reported by the Podspec from remote.
80 | #
81 | def remote_spec_version
82 | require 'cocoapods-core'
83 |
84 | if spec_file_exist_on_remote?
85 | remote_spec = eval(`git show origin/master:#{podspec_path}`)
86 | remote_spec.version
87 | else
88 | nil
89 | end
90 | end
91 |
92 | # @return [Bool] If the remote repository has a copy of the podpesc file or not.
93 | #
94 | def spec_file_exist_on_remote?
95 | test_condition = `if git rev-parse --verify --quiet origin/master:#{podspec_path} >/dev/null;
96 | then
97 | echo 'true'
98 | else
99 | echo 'false'
100 | fi`
101 |
102 | 'true' == test_condition.strip
103 | end
104 |
105 | # @return [String] The relative path of the Podspec.
106 | #
107 | def podspec_path
108 | podspecs = Dir.glob('*.podspec')
109 | if podspecs.count == 1
110 | podspecs.first
111 | else
112 | raise "Could not select a podspec"
113 | end
114 | end
115 |
116 | # @return [String] The suggested version number based on the local and remote
117 | # version numbers.
118 | #
119 | def suggested_version_number
120 | if spec_version != remote_spec_version
121 | spec_version.to_s()
122 | else
123 | next_version(spec_version).to_s()
124 | end
125 | end
126 |
127 | # @param [Pod::Version] version
128 | # the version for which you need the next version
129 | #
130 | # @note It is computed by bumping the last component of
131 | # the version string by 1.
132 | #
133 | # @return [Pod::Version] The version that comes next after
134 | # the version supplied.
135 | #
136 | def next_version(version)
137 | version_components = version.to_s().split(".");
138 | last = (version_components.last.to_i() + 1).to_s
139 | version_components[-1] = last
140 | Pod::Version.new(version_components.join("."))
141 | end
142 |
143 | # @param [String] new_version_number
144 | # the new version number
145 | #
146 | # @note This methods replaces the version number in the podspec file
147 | # with a new version number.
148 | #
149 | # @return void
150 | #
151 | def replace_version_number(new_version_number)
152 | text = File.read(podspec_path)
153 | text.gsub!(/(s.version( )*= ")#{spec_version}(")/,
154 | "\\1#{new_version_number}\\3")
155 | File.open(podspec_path, "w") { |file| file.puts text }
156 | end
157 |
--------------------------------------------------------------------------------
/Classes/osx/BSRefreshableScrollView.m:
--------------------------------------------------------------------------------
1 | //
2 | // BSRefreshableScrollView.m
3 | // RefreshableScrollView
4 | //
5 | // Created by Sasmito Adibowo on 19-11-12.
6 | // Copyright (c) 2012 Basil Salad Software. All rights reserved.
7 | // http://basilsalad.com
8 | //
9 | // Licensed under the BSD License
10 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
11 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
12 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
13 | // SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
15 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
16 | // BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
17 | // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
18 | // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19 | //
20 |
21 |
22 | #if !__has_feature(objc_arc)
23 | #error Need automatic reference counting to compile this.
24 | #endif
25 |
26 | #import "BSRefreshableClipView.h"
27 | #import "BSRefreshableScrollView_Private.h"
28 |
29 |
30 | @implementation BSRefreshableScrollView
31 |
32 | @synthesize refreshingSides = _refreshingSides;
33 |
34 | -(void) stopRefreshingSide:(BSRefreshableScrollViewSide) refreshableSides
35 | {
36 | NSClipView* const clipView = self.contentView;
37 | const NSRect clipViewBounds = clipView.bounds;
38 |
39 | void (^stopRefresh)(BSRefreshableScrollViewSide side, NSProgressIndicator* progressIndicator, BOOL (^shouldScroll)()) = ^(BSRefreshableScrollViewSide side, NSProgressIndicator* progressIndicator, BOOL (^shouldScroll)()) {
40 |
41 | if ( !(self.refreshingSides & side) ) {
42 | return;
43 | }
44 |
45 | self.refreshingSides &= ~side;
46 | [progressIndicator stopAnimation:self];
47 | [progressIndicator setDisplayedWhenStopped:NO];
48 | if (shouldScroll()) {
49 | // fake scrolling
50 | [[NSOperationQueue mainQueue] addOperationWithBlock:^{
51 | int scrollAmount = 0;
52 | if (side & BSRefreshableScrollViewSideTop) {
53 | scrollAmount = 1;
54 | } else if(side & BSRefreshableScrollViewSideBottom) {
55 | scrollAmount = -1;
56 | }
57 | CGEventRef cgEvent = CGEventCreateScrollWheelEvent(NULL,
58 | kCGScrollEventUnitLine,
59 | 1,
60 | scrollAmount,
61 | 0);
62 |
63 | NSEvent *scrollEvent = [NSEvent eventWithCGEvent:cgEvent];
64 | [self scrollWheel:scrollEvent];
65 | CFRelease(cgEvent);
66 | }];
67 | }
68 | };
69 |
70 | if (refreshableSides & BSRefreshableScrollViewSideTop) {
71 | stopRefresh(BSRefreshableScrollViewSideTop,self.topProgressIndicator,^{
72 | return (BOOL) (clipViewBounds.origin.y < 0);
73 | });
74 | }
75 |
76 | if (refreshableSides & BSRefreshableScrollViewSideBottom) {
77 | stopRefresh(BSRefreshableScrollViewSideBottom,self.bottomProgressIndicator,^{
78 | return (BOOL) (clipViewBounds.origin.y > 0);
79 | });
80 | }
81 |
82 | }
83 |
84 |
85 | -(NSView*) newEdgeViewForSide:(BSRefreshableScrollViewSide) edgeSide progressIndicator:(NSProgressIndicator*) indicatorView
86 | {
87 | NSView* const contentView = self.contentView;
88 | NSView* const documentView = self.documentView;
89 | const NSRect indicatorViewBounds = indicatorView.bounds;
90 |
91 |
92 | NSView* edgeView = [[NSView alloc] initWithFrame:NSZeroRect];
93 | [edgeView setTranslatesAutoresizingMaskIntoConstraints:NO];
94 | [edgeView setWantsLayer:YES];
95 |
96 | [edgeView addSubview:indicatorView];
97 |
98 | // vertically centered
99 | [edgeView addConstraint:[NSLayoutConstraint constraintWithItem:indicatorView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:edgeView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
100 |
101 | // horizontally centered
102 | [edgeView addConstraint:[NSLayoutConstraint constraintWithItem:indicatorView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:edgeView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
103 |
104 | [contentView addSubview:edgeView];
105 |
106 |
107 | if (edgeSide & (BSRefreshableScrollViewSideTop | BSRefreshableScrollViewSideBottom) ) {
108 | // span horizontally
109 | [contentView addConstraint:[NSLayoutConstraint constraintWithItem:edgeView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeLeft multiplier:1 constant:0]];
110 | [contentView addConstraint:[NSLayoutConstraint constraintWithItem:edgeView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeRight multiplier:1 constant:0]];
111 |
112 |
113 | // set height
114 | [contentView addConstraint:[NSLayoutConstraint constraintWithItem:edgeView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:indicatorViewBounds.size.height]];
115 |
116 | if (edgeSide & BSRefreshableScrollViewSideTop) {
117 | // above the content view top
118 | [contentView addConstraint:[NSLayoutConstraint constraintWithItem:edgeView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:documentView attribute:NSLayoutAttributeTop multiplier:1 constant:0]];
119 | } else if(edgeSide & BSRefreshableScrollViewSideBottom) {
120 | [contentView addConstraint:[NSLayoutConstraint constraintWithItem:edgeView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:documentView attribute:NSLayoutAttributeBottom multiplier:1 constant:0]];
121 | }
122 | }
123 |
124 | return edgeView;
125 | }
126 |
127 |
128 | #pragma mark NSObject
129 |
130 | -(void)dealloc
131 | {
132 |
133 | }
134 |
135 |
136 | #pragma mark NSResponder
137 |
138 | -(void)scrollWheel:(NSEvent *)theEvent
139 | {
140 | const NSEventPhase eventPhase = theEvent.phase;
141 |
142 | if (eventPhase & NSEventPhaseChanged) {
143 | NSClipView* const clipView = self.contentView;
144 | const NSRect clipViewBounds = clipView.bounds;
145 | NSView* const documentView = self.documentView;
146 | const NSRect headerFrame = self.headerView.frame;
147 | const NSRect footerFrame = self.footerView.frame;
148 | const NSRect documentFrame = documentView.frame;
149 |
150 | void (^startScrollPhase)(BSRefreshableScrollViewSide refreshSide,NSProgressIndicator* progressIndicator,float progressMaxValue,float progressCurrentValue, BOOL (^shouldTriggerRefresh)(void)) = ^(BSRefreshableScrollViewSide refreshSide,NSProgressIndicator* progressIndicator,float progressMaxValue,float progressCurrentValue, BOOL (^shouldTriggerRefresh)(void)) {
151 |
152 | if(!(self.refreshingSides & refreshSide) && (self.refreshableSides & refreshSide) ) {
153 | // not refreshing top
154 |
155 | if (progressIndicator.isIndeterminate) {
156 | [progressIndicator setIndeterminate:NO];
157 | [progressIndicator setDisplayedWhenStopped:YES];
158 | }
159 | [progressIndicator setAlphaValue:1];
160 |
161 | progressIndicator.minValue = 0;
162 | progressIndicator.maxValue = progressMaxValue;
163 | progressIndicator.doubleValue = progressCurrentValue;
164 |
165 | self.activatedRefreshingSides |= refreshSide;
166 |
167 | if (shouldTriggerRefresh()) {
168 | self.triggeredRefreshingSides |= refreshSide;
169 | }
170 | }
171 | };
172 |
173 |
174 | if (clipViewBounds.origin.y < 0 ) {
175 | // showing top area
176 |
177 | startScrollPhase(BSRefreshableScrollViewSideTop,self.topProgressIndicator,headerFrame.size.height,-clipViewBounds.origin.y, ^{
178 | return (BOOL) (clipViewBounds.origin.y < -headerFrame.size.height);
179 | });
180 |
181 | } else if (clipViewBounds.origin.y > documentFrame.size.height - clipViewBounds.size.height) {
182 | // scrolling to bottom
183 | CGFloat gapHeight = clipViewBounds.origin.y + clipViewBounds.size.height - documentFrame.size.height;
184 | startScrollPhase(BSRefreshableScrollViewSideBottom,self.bottomProgressIndicator,footerFrame.size.height,gapHeight, ^{
185 | return (BOOL) (gapHeight > footerFrame.size.height);
186 | });
187 | }
188 | } else if(eventPhase & NSEventPhaseEnded) {
189 | NSClipView* const clipView = self.contentView;
190 | const NSRect clipViewBounds = clipView.bounds;
191 |
192 |
193 | void (^completeScrollPhase)(BSRefreshableScrollViewSide refreshSide,NSProgressIndicator* progressIndicator) = ^(BSRefreshableScrollViewSide refreshSide,NSProgressIndicator* progressIndicator) {
194 | if(!(self.refreshingSides & refreshSide) && (self.activatedRefreshingSides & refreshSide)) {
195 | // not refreshing and OK to refresh
196 |
197 | self.activatedRefreshingSides &= ~refreshSide;
198 |
199 | if (self.triggeredRefreshingSides & refreshSide) {
200 | self.triggeredRefreshingSides &= ~refreshSide;
201 |
202 | [[NSOperationQueue mainQueue ] addOperationWithBlock:^{
203 | BOOL refreshStarted = NO;
204 | id delegate = self.refreshableDelegate;
205 | if ([delegate respondsToSelector:@selector(scrollView:startRefreshSide:)]) {
206 | refreshStarted = [delegate scrollView:self startRefreshSide:refreshSide];
207 | }
208 |
209 | if (refreshStarted) {
210 | [progressIndicator setIndeterminate:YES];
211 | [progressIndicator startAnimation:self];
212 | self.refreshingSides |= refreshSide;
213 | }
214 | }];
215 | } else {
216 | // un-triggered
217 | [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
218 | [progressIndicator.animator setAlphaValue:0];
219 | } completionHandler:^{
220 | [progressIndicator stopAnimation:self];
221 | [progressIndicator setIndeterminate:NO];
222 |
223 | }];
224 |
225 | }
226 | }
227 | };
228 |
229 | if (clipViewBounds.origin.y < 0) {
230 | // showing top area
231 | completeScrollPhase(BSRefreshableScrollViewSideTop,self.topProgressIndicator);
232 | } else if(clipViewBounds.origin.y > 0) {
233 | // showing bottom area
234 | completeScrollPhase(BSRefreshableScrollViewSideBottom,self.bottomProgressIndicator);
235 | }
236 | }
237 | [super scrollWheel:theEvent];
238 | }
239 |
240 |
241 | #pragma mark NSView
242 |
243 | // Place NSView overrides here – currently empty 😊
244 |
245 |
246 | #pragma mark NSScrollView
247 |
248 | -(NSClipView *)contentView
249 | {
250 | NSClipView* superClipView = [super contentView];
251 | if (![superClipView isKindOfClass:[BSRefreshableClipView class]]) {
252 | NSView* documentView = superClipView.documentView;
253 |
254 | BSRefreshableClipView* clipView = [[BSRefreshableClipView alloc] initWithOriginalClipView:superClipView];
255 | NSDictionary* bindings = NSDictionaryOfVariableBindings(documentView);
256 | clipView.documentView = documentView;
257 | [clipView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[documentView]|" options:0 metrics:nil views:bindings]];
258 | [clipView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[documentView]|" options:0 metrics:nil views:bindings]];
259 | [self setContentView:clipView];
260 |
261 | superClipView = clipView;
262 | }
263 | return superClipView;
264 | }
265 |
266 |
267 | #pragma mark Property Access
268 |
269 | @synthesize topProgressIndicator = _topProgressIndicator;
270 |
271 | -(NSProgressIndicator *)topProgressIndicator
272 | {
273 | if (!_topProgressIndicator && (self.refreshableSides & BSRefreshableScrollViewSideTop)) {
274 | _topProgressIndicator = [NSProgressIndicator new];
275 | [_topProgressIndicator setTranslatesAutoresizingMaskIntoConstraints:NO];
276 | [_topProgressIndicator setIndeterminate:YES];
277 | [_topProgressIndicator setStyle:NSProgressIndicatorSpinningStyle];
278 | [_topProgressIndicator setControlSize: NSRegularControlSize];
279 | [_topProgressIndicator setDisplayedWhenStopped:YES];
280 | [_topProgressIndicator setAlphaValue:0];
281 | [_topProgressIndicator sizeToFit];
282 | }
283 | return _topProgressIndicator;
284 | }
285 |
286 |
287 | @synthesize bottomProgressIndicator = _bottomProgressIndicator;
288 |
289 | -(NSProgressIndicator *)bottomProgressIndicator
290 | {
291 | if (!_bottomProgressIndicator && (self.refreshableSides & BSRefreshableScrollViewSideBottom)) {
292 | _bottomProgressIndicator = [NSProgressIndicator new];
293 | [_bottomProgressIndicator setTranslatesAutoresizingMaskIntoConstraints:NO];
294 | [_bottomProgressIndicator setIndeterminate:YES];
295 | [_bottomProgressIndicator setStyle:NSProgressIndicatorSpinningStyle];
296 | [_bottomProgressIndicator setControlSize: NSRegularControlSize];
297 | [_bottomProgressIndicator setDisplayedWhenStopped:YES];
298 | [_bottomProgressIndicator setAlphaValue:0];
299 | [_bottomProgressIndicator sizeToFit];
300 | }
301 | return _bottomProgressIndicator;
302 | }
303 |
304 |
305 | @synthesize headerView = _headerView;
306 |
307 | -(NSView *)headerView
308 | {
309 | if (!_headerView && (self.refreshableSides & BSRefreshableScrollViewSideTop)) {
310 | _headerView = [self newEdgeViewForSide:BSRefreshableScrollViewSideTop progressIndicator:self.topProgressIndicator];
311 | }
312 | return _headerView;
313 | }
314 |
315 |
316 | @synthesize footerView = _footerView;
317 |
318 | -(NSView *)footerView
319 | {
320 | if (!_footerView && (self.refreshableSides & BSRefreshableScrollViewSideBottom)) {
321 | _footerView = [self newEdgeViewForSide:BSRefreshableScrollViewSideBottom progressIndicator:self.bottomProgressIndicator];
322 | }
323 | return _footerView;
324 | }
325 |
326 |
327 | @end
328 |
--------------------------------------------------------------------------------
/Example/RefreshableScrollView.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | C00D9552165CC61B009F59FF /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C057343D165A23B000087E78 /* Cocoa.framework */; };
11 | C00D9561165CC663009F59FF /* libRefreshableScrollView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C00D9551165CC61B009F59FF /* libRefreshableScrollView.a */; };
12 | C02FC955191BBFFD0014FFB8 /* BSRefreshableClipView.m in Sources */ = {isa = PBXBuildFile; fileRef = C02FC94F191BBFC70014FFB8 /* BSRefreshableClipView.m */; };
13 | C02FC956191BBFFD0014FFB8 /* BSRefreshableScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = C02FC952191BBFC70014FFB8 /* BSRefreshableScrollView.m */; };
14 | C057343E165A23B000087E78 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C057343D165A23B000087E78 /* Cocoa.framework */; };
15 | C0573448165A23B000087E78 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C0573446165A23B000087E78 /* InfoPlist.strings */; };
16 | C057344A165A23B000087E78 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C0573449165A23B000087E78 /* main.m */; };
17 | C057344E165A23B000087E78 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = C057344C165A23B000087E78 /* Credits.rtf */; };
18 | C0573451165A23B000087E78 /* BSAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C0573450165A23B000087E78 /* BSAppDelegate.m */; };
19 | C0573454165A23B000087E78 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0573452165A23B000087E78 /* MainMenu.xib */; };
20 | /* End PBXBuildFile section */
21 |
22 | /* Begin PBXContainerItemProxy section */
23 | C00D9562165CC68F009F59FF /* PBXContainerItemProxy */ = {
24 | isa = PBXContainerItemProxy;
25 | containerPortal = C0573432165A23AF00087E78 /* Project object */;
26 | proxyType = 1;
27 | remoteGlobalIDString = C00D9550165CC61B009F59FF;
28 | remoteInfo = RefreshableScrollView;
29 | };
30 | /* End PBXContainerItemProxy section */
31 |
32 | /* Begin PBXFileReference section */
33 | C00D9551165CC61B009F59FF /* libRefreshableScrollView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRefreshableScrollView.a; sourceTree = BUILT_PRODUCTS_DIR; };
34 | C00D9555165CC61B009F59FF /* RefreshableScrollView-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RefreshableScrollView-Prefix.pch"; sourceTree = ""; };
35 | C02FC94E191BBFC70014FFB8 /* BSRefreshableClipView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BSRefreshableClipView.h; sourceTree = ""; };
36 | C02FC94F191BBFC70014FFB8 /* BSRefreshableClipView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BSRefreshableClipView.m; sourceTree = ""; };
37 | C02FC950191BBFC70014FFB8 /* BSRefreshableScrollView_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BSRefreshableScrollView_Private.h; sourceTree = ""; };
38 | C02FC951191BBFC70014FFB8 /* BSRefreshableScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BSRefreshableScrollView.h; sourceTree = ""; };
39 | C02FC952191BBFC70014FFB8 /* BSRefreshableScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BSRefreshableScrollView.m; sourceTree = ""; };
40 | C057343A165A23AF00087E78 /* RefreshableScrollViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RefreshableScrollViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
41 | C057343D165A23B000087E78 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
42 | C0573440165A23B000087E78 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
43 | C0573441165A23B000087E78 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
44 | C0573442165A23B000087E78 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
45 | C0573445165A23B000087E78 /* RefreshableScrollView-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "RefreshableScrollView-Info.plist"; sourceTree = ""; };
46 | C0573447165A23B000087E78 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; };
47 | C0573449165A23B000087E78 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
48 | C057344B165A23B000087E78 /* RefreshableScrollView-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RefreshableScrollView-Prefix.pch"; sourceTree = ""; };
49 | C057344D165A23B000087E78 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; };
50 | C057344F165A23B000087E78 /* BSAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BSAppDelegate.h; sourceTree = ""; };
51 | C0573450165A23B000087E78 /* BSAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSAppDelegate.m; sourceTree = ""; };
52 | C0573453165A23B000087E78 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; };
53 | /* End PBXFileReference section */
54 |
55 | /* Begin PBXFrameworksBuildPhase section */
56 | C00D954E165CC61B009F59FF /* Frameworks */ = {
57 | isa = PBXFrameworksBuildPhase;
58 | buildActionMask = 2147483647;
59 | files = (
60 | C00D9552165CC61B009F59FF /* Cocoa.framework in Frameworks */,
61 | );
62 | runOnlyForDeploymentPostprocessing = 0;
63 | };
64 | C0573437165A23AF00087E78 /* Frameworks */ = {
65 | isa = PBXFrameworksBuildPhase;
66 | buildActionMask = 2147483647;
67 | files = (
68 | C00D9561165CC663009F59FF /* libRefreshableScrollView.a in Frameworks */,
69 | C057343E165A23B000087E78 /* Cocoa.framework in Frameworks */,
70 | );
71 | runOnlyForDeploymentPostprocessing = 0;
72 | };
73 | /* End PBXFrameworksBuildPhase section */
74 |
75 | /* Begin PBXGroup section */
76 | C00D9553165CC61B009F59FF /* RefreshableScrollView */ = {
77 | isa = PBXGroup;
78 | children = (
79 | C00D9554165CC61B009F59FF /* Supporting Files */,
80 | );
81 | path = RefreshableScrollView;
82 | sourceTree = "";
83 | };
84 | C00D9554165CC61B009F59FF /* Supporting Files */ = {
85 | isa = PBXGroup;
86 | children = (
87 | C00D9555165CC61B009F59FF /* RefreshableScrollView-Prefix.pch */,
88 | );
89 | name = "Supporting Files";
90 | sourceTree = "";
91 | };
92 | C0573431165A23AF00087E78 = {
93 | isa = PBXGroup;
94 | children = (
95 | C057345A165A23C900087E78 /* Views */,
96 | C0573443165A23B000087E78 /* RefreshableScrollView */,
97 | C00D9553165CC61B009F59FF /* RefreshableScrollView */,
98 | C057343C165A23AF00087E78 /* Frameworks */,
99 | C057343B165A23AF00087E78 /* Products */,
100 | );
101 | sourceTree = "";
102 | };
103 | C057343B165A23AF00087E78 /* Products */ = {
104 | isa = PBXGroup;
105 | children = (
106 | C057343A165A23AF00087E78 /* RefreshableScrollViewDemo.app */,
107 | C00D9551165CC61B009F59FF /* libRefreshableScrollView.a */,
108 | );
109 | name = Products;
110 | sourceTree = "";
111 | };
112 | C057343C165A23AF00087E78 /* Frameworks */ = {
113 | isa = PBXGroup;
114 | children = (
115 | C057343D165A23B000087E78 /* Cocoa.framework */,
116 | C057343F165A23B000087E78 /* Other Frameworks */,
117 | );
118 | name = Frameworks;
119 | sourceTree = "";
120 | };
121 | C057343F165A23B000087E78 /* Other Frameworks */ = {
122 | isa = PBXGroup;
123 | children = (
124 | C0573440165A23B000087E78 /* AppKit.framework */,
125 | C0573441165A23B000087E78 /* CoreData.framework */,
126 | C0573442165A23B000087E78 /* Foundation.framework */,
127 | );
128 | name = "Other Frameworks";
129 | sourceTree = "";
130 | };
131 | C0573443165A23B000087E78 /* RefreshableScrollView */ = {
132 | isa = PBXGroup;
133 | children = (
134 | C057344F165A23B000087E78 /* BSAppDelegate.h */,
135 | C0573450165A23B000087E78 /* BSAppDelegate.m */,
136 | C0573452165A23B000087E78 /* MainMenu.xib */,
137 | C0573444165A23B000087E78 /* Supporting Files */,
138 | );
139 | path = RefreshableScrollView;
140 | sourceTree = "";
141 | };
142 | C0573444165A23B000087E78 /* Supporting Files */ = {
143 | isa = PBXGroup;
144 | children = (
145 | C0573445165A23B000087E78 /* RefreshableScrollView-Info.plist */,
146 | C0573446165A23B000087E78 /* InfoPlist.strings */,
147 | C0573449165A23B000087E78 /* main.m */,
148 | C057344B165A23B000087E78 /* RefreshableScrollView-Prefix.pch */,
149 | C057344C165A23B000087E78 /* Credits.rtf */,
150 | );
151 | name = "Supporting Files";
152 | sourceTree = "";
153 | };
154 | C057345A165A23C900087E78 /* Views */ = {
155 | isa = PBXGroup;
156 | children = (
157 | C02FC94E191BBFC70014FFB8 /* BSRefreshableClipView.h */,
158 | C02FC94F191BBFC70014FFB8 /* BSRefreshableClipView.m */,
159 | C02FC950191BBFC70014FFB8 /* BSRefreshableScrollView_Private.h */,
160 | C02FC951191BBFC70014FFB8 /* BSRefreshableScrollView.h */,
161 | C02FC952191BBFC70014FFB8 /* BSRefreshableScrollView.m */,
162 | );
163 | name = Views;
164 | path = ../Classes/osx;
165 | sourceTree = "";
166 | };
167 | /* End PBXGroup section */
168 |
169 | /* Begin PBXHeadersBuildPhase section */
170 | C00D954F165CC61B009F59FF /* Headers */ = {
171 | isa = PBXHeadersBuildPhase;
172 | buildActionMask = 2147483647;
173 | files = (
174 | );
175 | runOnlyForDeploymentPostprocessing = 0;
176 | };
177 | /* End PBXHeadersBuildPhase section */
178 |
179 | /* Begin PBXNativeTarget section */
180 | C00D9550165CC61B009F59FF /* RefreshableScrollView */ = {
181 | isa = PBXNativeTarget;
182 | buildConfigurationList = C00D9559165CC61B009F59FF /* Build configuration list for PBXNativeTarget "RefreshableScrollView" */;
183 | buildPhases = (
184 | C00D954D165CC61B009F59FF /* Sources */,
185 | C00D954E165CC61B009F59FF /* Frameworks */,
186 | C00D954F165CC61B009F59FF /* Headers */,
187 | );
188 | buildRules = (
189 | );
190 | dependencies = (
191 | );
192 | name = RefreshableScrollView;
193 | productName = RefreshableScrollView;
194 | productReference = C00D9551165CC61B009F59FF /* libRefreshableScrollView.a */;
195 | productType = "com.apple.product-type.library.static";
196 | };
197 | C0573439165A23AF00087E78 /* RefreshableScrollViewDemo */ = {
198 | isa = PBXNativeTarget;
199 | buildConfigurationList = C0573457165A23B000087E78 /* Build configuration list for PBXNativeTarget "RefreshableScrollViewDemo" */;
200 | buildPhases = (
201 | C0573436165A23AF00087E78 /* Sources */,
202 | C0573437165A23AF00087E78 /* Frameworks */,
203 | C0573438165A23AF00087E78 /* Resources */,
204 | );
205 | buildRules = (
206 | );
207 | dependencies = (
208 | C00D9563165CC68F009F59FF /* PBXTargetDependency */,
209 | );
210 | name = RefreshableScrollViewDemo;
211 | productName = RefreshableScrollView;
212 | productReference = C057343A165A23AF00087E78 /* RefreshableScrollViewDemo.app */;
213 | productType = "com.apple.product-type.application";
214 | };
215 | /* End PBXNativeTarget section */
216 |
217 | /* Begin PBXProject section */
218 | C0573432165A23AF00087E78 /* Project object */ = {
219 | isa = PBXProject;
220 | attributes = {
221 | CLASSPREFIX = BS;
222 | LastUpgradeCheck = 0510;
223 | ORGANIZATIONNAME = "Basil Salad Software";
224 | };
225 | buildConfigurationList = C0573435165A23AF00087E78 /* Build configuration list for PBXProject "RefreshableScrollView" */;
226 | compatibilityVersion = "Xcode 3.2";
227 | developmentRegion = English;
228 | hasScannedForEncodings = 0;
229 | knownRegions = (
230 | en,
231 | );
232 | mainGroup = C0573431165A23AF00087E78;
233 | productRefGroup = C057343B165A23AF00087E78 /* Products */;
234 | projectDirPath = "";
235 | projectRoot = "";
236 | targets = (
237 | C0573439165A23AF00087E78 /* RefreshableScrollViewDemo */,
238 | C00D9550165CC61B009F59FF /* RefreshableScrollView */,
239 | );
240 | };
241 | /* End PBXProject section */
242 |
243 | /* Begin PBXResourcesBuildPhase section */
244 | C0573438165A23AF00087E78 /* Resources */ = {
245 | isa = PBXResourcesBuildPhase;
246 | buildActionMask = 2147483647;
247 | files = (
248 | C0573448165A23B000087E78 /* InfoPlist.strings in Resources */,
249 | C057344E165A23B000087E78 /* Credits.rtf in Resources */,
250 | C0573454165A23B000087E78 /* MainMenu.xib in Resources */,
251 | );
252 | runOnlyForDeploymentPostprocessing = 0;
253 | };
254 | /* End PBXResourcesBuildPhase section */
255 |
256 | /* Begin PBXSourcesBuildPhase section */
257 | C00D954D165CC61B009F59FF /* Sources */ = {
258 | isa = PBXSourcesBuildPhase;
259 | buildActionMask = 2147483647;
260 | files = (
261 | C02FC956191BBFFD0014FFB8 /* BSRefreshableScrollView.m in Sources */,
262 | C02FC955191BBFFD0014FFB8 /* BSRefreshableClipView.m in Sources */,
263 | );
264 | runOnlyForDeploymentPostprocessing = 0;
265 | };
266 | C0573436165A23AF00087E78 /* Sources */ = {
267 | isa = PBXSourcesBuildPhase;
268 | buildActionMask = 2147483647;
269 | files = (
270 | C057344A165A23B000087E78 /* main.m in Sources */,
271 | C0573451165A23B000087E78 /* BSAppDelegate.m in Sources */,
272 | );
273 | runOnlyForDeploymentPostprocessing = 0;
274 | };
275 | /* End PBXSourcesBuildPhase section */
276 |
277 | /* Begin PBXTargetDependency section */
278 | C00D9563165CC68F009F59FF /* PBXTargetDependency */ = {
279 | isa = PBXTargetDependency;
280 | target = C00D9550165CC61B009F59FF /* RefreshableScrollView */;
281 | targetProxy = C00D9562165CC68F009F59FF /* PBXContainerItemProxy */;
282 | };
283 | /* End PBXTargetDependency section */
284 |
285 | /* Begin PBXVariantGroup section */
286 | C0573446165A23B000087E78 /* InfoPlist.strings */ = {
287 | isa = PBXVariantGroup;
288 | children = (
289 | C0573447165A23B000087E78 /* en */,
290 | );
291 | name = InfoPlist.strings;
292 | sourceTree = "";
293 | };
294 | C057344C165A23B000087E78 /* Credits.rtf */ = {
295 | isa = PBXVariantGroup;
296 | children = (
297 | C057344D165A23B000087E78 /* en */,
298 | );
299 | name = Credits.rtf;
300 | sourceTree = "";
301 | };
302 | C0573452165A23B000087E78 /* MainMenu.xib */ = {
303 | isa = PBXVariantGroup;
304 | children = (
305 | C0573453165A23B000087E78 /* en */,
306 | );
307 | name = MainMenu.xib;
308 | sourceTree = "";
309 | };
310 | /* End PBXVariantGroup section */
311 |
312 | /* Begin XCBuildConfiguration section */
313 | C00D955A165CC61B009F59FF /* Debug */ = {
314 | isa = XCBuildConfiguration;
315 | buildSettings = {
316 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
317 | GCC_PREFIX_HEADER = "RefreshableScrollView/RefreshableScrollView-Prefix.pch";
318 | OTHER_LDFLAGS = "-ObjC";
319 | PRODUCT_NAME = "$(TARGET_NAME)";
320 | };
321 | name = Debug;
322 | };
323 | C00D955B165CC61B009F59FF /* Release */ = {
324 | isa = XCBuildConfiguration;
325 | buildSettings = {
326 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
327 | GCC_PREFIX_HEADER = "RefreshableScrollView/RefreshableScrollView-Prefix.pch";
328 | OTHER_LDFLAGS = "-ObjC";
329 | PRODUCT_NAME = "$(TARGET_NAME)";
330 | };
331 | name = Release;
332 | };
333 | C0573455165A23B000087E78 /* Debug */ = {
334 | isa = XCBuildConfiguration;
335 | buildSettings = {
336 | ALWAYS_SEARCH_USER_PATHS = NO;
337 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
338 | CLANG_CXX_LIBRARY = "libc++";
339 | CLANG_ENABLE_OBJC_ARC = YES;
340 | CLANG_WARN_CONSTANT_CONVERSION = YES;
341 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
342 | CLANG_WARN_EMPTY_BODY = YES;
343 | CLANG_WARN_INT_CONVERSION = YES;
344 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
345 | COPY_PHASE_STRIP = NO;
346 | GCC_C_LANGUAGE_STANDARD = gnu99;
347 | GCC_DYNAMIC_NO_PIC = NO;
348 | GCC_ENABLE_OBJC_EXCEPTIONS = YES;
349 | GCC_OPTIMIZATION_LEVEL = 0;
350 | GCC_PREPROCESSOR_DEFINITIONS = (
351 | "DEBUG=1",
352 | "$(inherited)",
353 | );
354 | GCC_STRICT_ALIASING = YES;
355 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
356 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
357 | GCC_WARN_ABOUT_RETURN_TYPE = YES;
358 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
359 | GCC_WARN_UNUSED_VARIABLE = YES;
360 | MACOSX_DEPLOYMENT_TARGET = 10.8;
361 | ONLY_ACTIVE_ARCH = YES;
362 | SDKROOT = macosx;
363 | };
364 | name = Debug;
365 | };
366 | C0573456165A23B000087E78 /* Release */ = {
367 | isa = XCBuildConfiguration;
368 | buildSettings = {
369 | ALWAYS_SEARCH_USER_PATHS = NO;
370 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
371 | CLANG_CXX_LIBRARY = "libc++";
372 | CLANG_ENABLE_OBJC_ARC = YES;
373 | CLANG_WARN_CONSTANT_CONVERSION = YES;
374 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
375 | CLANG_WARN_EMPTY_BODY = YES;
376 | CLANG_WARN_INT_CONVERSION = YES;
377 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
378 | COPY_PHASE_STRIP = YES;
379 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
380 | GCC_C_LANGUAGE_STANDARD = gnu99;
381 | GCC_ENABLE_OBJC_EXCEPTIONS = YES;
382 | GCC_STRICT_ALIASING = YES;
383 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
384 | GCC_WARN_ABOUT_RETURN_TYPE = YES;
385 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
386 | GCC_WARN_UNUSED_VARIABLE = YES;
387 | MACOSX_DEPLOYMENT_TARGET = 10.8;
388 | SDKROOT = macosx;
389 | };
390 | name = Release;
391 | };
392 | C0573458165A23B000087E78 /* Debug */ = {
393 | isa = XCBuildConfiguration;
394 | buildSettings = {
395 | COMBINE_HIDPI_IMAGES = YES;
396 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
397 | GCC_PREFIX_HEADER = "RefreshableScrollView/RefreshableScrollView-Prefix.pch";
398 | INFOPLIST_FILE = "RefreshableScrollView/RefreshableScrollView-Info.plist";
399 | OTHER_LDFLAGS = "-ObjC";
400 | PRODUCT_NAME = "$(TARGET_NAME)";
401 | WRAPPER_EXTENSION = app;
402 | };
403 | name = Debug;
404 | };
405 | C0573459165A23B000087E78 /* Release */ = {
406 | isa = XCBuildConfiguration;
407 | buildSettings = {
408 | COMBINE_HIDPI_IMAGES = YES;
409 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
410 | GCC_PREFIX_HEADER = "RefreshableScrollView/RefreshableScrollView-Prefix.pch";
411 | INFOPLIST_FILE = "RefreshableScrollView/RefreshableScrollView-Info.plist";
412 | OTHER_LDFLAGS = "-ObjC";
413 | PRODUCT_NAME = "$(TARGET_NAME)";
414 | WRAPPER_EXTENSION = app;
415 | };
416 | name = Release;
417 | };
418 | /* End XCBuildConfiguration section */
419 |
420 | /* Begin XCConfigurationList section */
421 | C00D9559165CC61B009F59FF /* Build configuration list for PBXNativeTarget "RefreshableScrollView" */ = {
422 | isa = XCConfigurationList;
423 | buildConfigurations = (
424 | C00D955A165CC61B009F59FF /* Debug */,
425 | C00D955B165CC61B009F59FF /* Release */,
426 | );
427 | defaultConfigurationIsVisible = 0;
428 | defaultConfigurationName = Release;
429 | };
430 | C0573435165A23AF00087E78 /* Build configuration list for PBXProject "RefreshableScrollView" */ = {
431 | isa = XCConfigurationList;
432 | buildConfigurations = (
433 | C0573455165A23B000087E78 /* Debug */,
434 | C0573456165A23B000087E78 /* Release */,
435 | );
436 | defaultConfigurationIsVisible = 0;
437 | defaultConfigurationName = Release;
438 | };
439 | C0573457165A23B000087E78 /* Build configuration list for PBXNativeTarget "RefreshableScrollViewDemo" */ = {
440 | isa = XCConfigurationList;
441 | buildConfigurations = (
442 | C0573458165A23B000087E78 /* Debug */,
443 | C0573459165A23B000087E78 /* Release */,
444 | );
445 | defaultConfigurationIsVisible = 0;
446 | defaultConfigurationName = Release;
447 | };
448 | /* End XCConfigurationList section */
449 | };
450 | rootObject = C0573432165A23AF00087E78 /* Project object */;
451 | }
452 |
--------------------------------------------------------------------------------
/Example/RefreshableScrollView/en.lproj/MainMenu.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
688 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
--------------------------------------------------------------------------------