From fcda3415c1a5787129077a46af938691e59e9fce Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Tue, 20 Dec 2016 18:05:25 +0000 Subject: [PATCH] Update README and gh-pages script --- Classes/UIScrollView+InfiniteScroll.h | 52 ++++- README.md | 277 ++++++++++++-------------- publish-docs.sh | 20 ++ 3 files changed, 193 insertions(+), 156 deletions(-) create mode 100755 publish-docs.sh diff --git a/Classes/UIScrollView+InfiniteScroll.h b/Classes/UIScrollView+InfiniteScroll.h index 2977127..c3d67a2 100644 --- a/Classes/UIScrollView+InfiniteScroll.h +++ b/Classes/UIScrollView+InfiniteScroll.h @@ -11,6 +11,9 @@ NS_ASSUME_NONNULL_BEGIN +/** + UIScrollView infinite scroll category + */ @interface UIScrollView (InfiniteScroll) /** @@ -95,22 +98,65 @@ NS_ASSUME_NONNULL_BEGIN @end -/* - Convenience interface to avoid cast from UIScrollView to common subclasses such as UITableView and UICollectionView. +/** + Convenience interface for UIScrollView+InfiniteScroll category. */ - @interface UITableView (InfiniteScrollConvenienceInterface) +/** + * Setup infinite scroll handler + * + * @param handler a handler block + */ - (void)addInfiniteScrollWithHandler:(void(^)(UITableView *tableView))handler; + +/** + * Set a handler to be called to check if the infinite scroll should be shown + * + * @param handler a handler block + */ - (void)setShouldShowInfiniteScrollHandler:(BOOL(^)(UITableView *tableView))handler; + +/** + * Finish infinite scroll animations + * + * You must call this method from your infinite scroll handler to finish all + * animations properly and reset infinite scroll state + * + * @param handler a completion block handler called when animation finished + */ - (void)finishInfiniteScrollWithCompletion:(nullable void(^)(UITableView *tableView))handler; @end + +/** + Convenience interface for UIScrollView+InfiniteScroll category. + */ @interface UICollectionView (InfiniteScrollConvenienceInterface) +/** + * Setup infinite scroll handler + * + * @param handler a handler block + */ - (void)addInfiniteScrollWithHandler:(void(^)(UICollectionView *collectionView))handler; + +/** + * Set a handler to be called to check if the infinite scroll should be shown + * + * @param handler a handler block + */ - (void)setShouldShowInfiniteScrollHandler:(BOOL(^)(UICollectionView *collectionView))handler; + +/** + * Finish infinite scroll animations + * + * You must call this method from your infinite scroll handler to finish all + * animations properly and reset infinite scroll state + * + * @param handler a completion block handler called when animation finished + */ - (void)finishInfiniteScrollWithCompletion:(nullable void(^)(UICollectionView *collectionView))handler; @end diff --git a/README.md b/README.md index d4203f7..2c18e7b 100644 --- a/README.md +++ b/README.md @@ -2,238 +2,209 @@ Infinite scroll implementation as a category for UIScrollView. -Be aware that this category swizzles `setContentOffset` and `setContentSize` on `UIScrollView`. - \* The content used in demo app is publicly available and provided by hn.algolia.com and Flickr. Both can be inappropriate. +### Swizzling + +Be aware that this category [swizzles](http://nshipster.com/method-swizzling/) `setContentOffset` and `setContentSize` on `UIScrollView`. + ### CocoaPods Just add the following line in your Podfile: ```ruby -pod 'UIScrollView-InfiniteScroll' +pod 'UIScrollView-InfiniteScroll', '~> 1.0.0' ``` -### Basic usage +### Examples -Objective-C: +This component comes with example app written in Swift and Objective-C. -```objc -// Somewhere in your implementation file -#import +If you use CocoaPods you can try it by running: -// ... +```bash +pod try UIScrollView-InfiniteScroll +``` -- (void)viewDidLoad { - [super viewDidLoad]; +### Documentation - // change indicator view style to white - self.tableView.infiniteScrollIndicatorStyle = UIActivityIndicatorViewStyleWhite; +http://pronebird.github.io/UIScrollView-InfiniteScroll/ - // setup infinite scroll - [self.tableView addInfiniteScrollWithHandler:^(UITableView* tableView) { - // - // fetch your data here, can be async operation, - // just make sure to call finishInfiniteScroll in the end - // +### Before using module - NSArray * indexPaths; // index paths of updated rows - - // make sure to update tableView before calling -finishInfiniteScroll - [tableView beginUpdates]; - [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic]; - [tableView endUpdates]; +#### Objective-C - // finish infinite scroll animation - [tableView finishInfiniteScroll]; - }]; -} +Import header file in Objective-C: + +```objc +#import ``` -Swift +#### Swift -Before using InfiniteScroll you have to add the following line in your bridging header file: +Add the following line in your bridging header file: ```objc #import ``` -```swift -override func viewDidLoad() { - super.viewDidLoad() - - // change indicator view style to white - tableView.infiniteScrollIndicatorStyle = .White - - // Add infinite scroll handler - tableView.addInfiniteScrollWithHandler { (tableView) -> Void in - // - // fetch your data here, can be async operation, - // just make sure to call finishInfiniteScroll in the end - // - - let indexPaths = [NSIndexPath]() // index paths of updated rows - - // make sure you update tableView before calling -finishInfiniteScroll - tableView.beginUpdates() - tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .Automatic) - tableView.endUpdates() - - // finish infinite scroll animation - tableView.finishInfiniteScroll() - } -} -``` +### Basics -#### Collection view quirks +In order to enable infinite scroll you have to provide a handler block using `addInfiniteScrollWithHandler`. The block you provide is executed each time infinite scroll component detects that more data needs to be provided. -`UICollectionView#reloadData` causes contentOffset to reset. Please use `UICollectionView#performBatchUpdates` instead when possible. +The purpose of the handler block is to perform asynchronous task, typically networking or database fetch, and update your scroll view or scroll view subclass. -Objective-C: +The block itself is called on main queue, therefore make sure you move any long-running tasks to background queue. Once you receive new data, update table view by adding new rows and sections, then call `finishInfiniteScroll` to complete infinite scroll animations and reset the state of infinite scroll components. -```objc -// Somewhere in your implementation file -#import +`viewDidLoad` is a good place to install handler block. -// ... +Make sure that any interactions with UIKit or methods provided by Infinite Scroll happen on main queue. Use `dispatch_async(dispatch_get_main_queue, { ... })` in Objective-C or `DispatchQueue.main.async { ... }` in Swift to run UI related calls on main queue. -- (void)viewDidLoad { - [super viewDidLoad]; - - __weak typeof(self) weakSelf = self; - - [self.collectionView addInfiniteScrollWithHandler:^(UICollectionView* collectionView) { - // - // fetch your data here, can be async operation, - // just make sure to call finishInfiniteScroll in the end - // - - // suppose this is an array with new data - NSArray *newStories; - - NSMutableArray *indexPaths = [NSMutableArray new]; - NSInteger index = weakSelf.allStories.count; +Many people make mistake by using external reference to table view or collection view within the handler block. Don't do this. This creates a circular retention. Instead use the instance of scroll view or scroll view subclass passed as first argument to handler block. + +#### Objective-C + +```objc +// setup infinite scroll +[tableView addInfiniteScrollWithHandler:^(UITableView* tableView) { + // update table view - // create index paths for affected items - for(Story *story in newStories) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index++ inSection:0]; - - [weakSelf.allStories addObject:story]; - [indexPaths addObject:indexPath]; - } - - // Update collection view - [collectionView performBatchUpdates:^{ - // add new items into collection - [collectionView insertItemsAtIndexPaths:indexPaths]; - } completion:^(BOOL finished) { - // finish infinite scroll animations - [collectionView finishInfiniteScroll]; - }]; - }]; -} + // finish infinite scroll animation + [tableView finishInfiniteScroll]; +}]; ``` -Swift: +#### Swift ```swift -override func viewDidLoad() { - super.viewDidLoad() +tableView.addInfiniteScrollWithHandler { (tableView) -> Void in + // update table view - // Add infinite scroll handler - collectionView?.addInfiniteScrollWithHandler { [weak self] (scrollView) -> Void in - let collectionView = scrollView as! UICollectionView - - // suppose this is an array with new data - let newStories = [Story]() - - var indexPaths = [NSIndexPath]() - let index = self?.allStories.count - - // create index paths for affected items - for story in newStories { - let indexPath = NSIndexPath(forItem: index++, inSection: 0) - - indexPaths.append(indexPath) - self?.allStories.append(story) - } - - // Update collection view - collectionView.performBatchUpdates({ () -> Void in - // add new items into collection - collectionView.insertItemsAtIndexPaths(indexPaths) - }, completion: { (finished) -> Void in - // finish infinite scroll animations - collectionView.finishInfiniteScroll() - }); - - } + // finish infinite scroll animation + tableView.finishInfiniteScroll() } ``` -### Custom indicator +### Collection view quirks -You can use custom indicator instead of default `UIActivityIndicatorView`. +`UICollectionView.reloadData` causes contentOffset to reset. Instead use `UICollectionView.performBatchUpdates` when possible. -Custom indicator must be a subclass of `UIView` and implement the following methods: +#### Objective-C ```objc -- (void)startAnimating; -- (void)stopAnimating; +[self.collectionView addInfiniteScrollWithHandler:^(UICollectionView* collectionView) { + [collectionView performBatchUpdates:^{ + // update collection view + } completion:^(BOOL finished) { + // finish infinite scroll animations + [collectionView finishInfiniteScroll]; + }]; +}]; ``` -Objective-C: -```objc -// optionally you can use custom indicator view -CustomInfiniteIndicator *infiniteIndicator = [[CustomInfiniteIndicator alloc] initWithFrame:CGRectMake(0, 0, 40, 40)]; +#### Swift -self.tableView.infiniteScrollIndicatorView = indicator; -``` - -Swift: ```swift -// optionally you can use custom indicator view -tableView.infiniteScrollIndicatorView = CustomInfiniteIndicator(frame: CGRect(x: 0, y: 0, width: 24, height: 24)) +collectionView.addInfiniteScrollWithHandler { (collectionView) -> Void in + collectionView.performBatchUpdates({ () -> Void in + // update collection view + }, completion: { (finished) -> Void in + // finish infinite scroll animations + collectionView.finishInfiniteScroll() + }); +} ``` -Please see example implementation of indicator view: +### Start infinite scroll programmatically -* Objective-C: [CustomInfiniteIndicator.m](https://github.com/pronebird/UIScrollView-InfiniteScroll/blob/master/InfiniteScrollViewDemo/CustomInfiniteIndicator.m) +You can reuse infinite scroll flow to load initial data or fetch more using `beginInfiniteScroll(forceScroll)`. `viewDidLoad` is a good place for loading initial data, however absolutely up to you to decide. -* Swift: [CustomInfiniteIndicator.swift](https://github.com/pronebird/UIScrollView-InfiniteScroll/blob/master/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift) +When `forceScroll` parameter is positive, Infinite Scroll component will attempt to scroll down to reveal indicator view. Keep in mind that scrolling will not happen if user is interacting with scroll view. -At the moment InfiniteScroll uses indicator's frame directly so make sure you size custom indicator view beforehand. Such views as `UIImageView` or `UIActivityIndicatorView` will automatically resize themselves so no need to setup frame for them. +#### Objective-C + +```objc +[self.tableView beginInfiniteScroll:YES]; +``` + +#### Swift + +```swift +tableView.beginInfiniteScroll(true) +``` ### Prevent infinite scroll Sometimes you need to prevent the infinite scroll from continuing. For example, if your search API has no more results, it does not make sense to keep making the requests or to show the spinner. -Objective-C: +#### Objective-C + ```objc -// Provide a block to be called right before a infinite scroll event is triggered. Return YES to allow or NO to prevent it from triggering. -[self.tableView setShouldShowInfiniteScrollHandler:^BOOL(UIScrollView *scrollView) { +[tableView setShouldShowInfiniteScrollHandler:^BOOL (UITableView *tableView) { // Only show up to 5 pages then prevent the infinite scroll return (weakSelf.currentPage < 5); }]; ``` +#### Swift + +```swift +// Provide a block to be called right before a infinite scroll event is triggered. Return YES to allow or NO to prevent it from triggering. +tableView.setShouldShowInfiniteScrollHandler { _ -> Bool in + // Only show up to 5 pages then prevent the infinite scroll + return currentPage < 5 +} +``` + ### Seamlessly preload content Ideally you want your content to flow seamlessly without ever showing a spinner. Infinite scroll offers an option to specify offset in points that will be used to start preloader before user reaches the bottom of scroll view. The proper balance between the number of results you load each time and large enough offset should give your users a decent experience. Most likely you will have to come up with your own formula for the combination of those based on kind of content and device dimensions. -Objective-C: - ```objc // Preload more data 500pt before reaching the bottom of scroll view. tableView.infiniteScrollTriggerOffset = 500; ``` +### Custom indicator + +You can use custom indicator instead of default `UIActivityIndicatorView`. + +Custom indicator must be a subclass of `UIView` and implement the following methods: + +```objc +- (void)startAnimating; +- (void)stopAnimating; +``` + +#### Objective-C + +```objc +CustomInfiniteIndicator *infiniteIndicator = [[CustomInfiniteIndicator alloc] initWithFrame:CGRectMake(0, 0, 40, 40)]; +self.tableView.infiniteScrollIndicatorView = indicator; +``` + +#### Swift + +```swift +let frame = CGRect(x: 0, y: 0, width: 24, height: 24) +tableView.infiniteScrollIndicatorView = CustomInfiniteIndicator(frame: frame) +``` + +Please see example implementation of custom indicator view: + +* Objective-C: [CustomInfiniteIndicator.m](https://github.com/pronebird/UIScrollView-InfiniteScroll/blob/master/InfiniteScrollViewDemo/CustomInfiniteIndicator.m) + +* Swift: [CustomInfiniteIndicator.swift](https://github.com/pronebird/UIScrollView-InfiniteScroll/blob/master/InfiniteScrollViewDemoSwift/CustomInfiniteIndicator.swift) + +At the moment InfiniteScroll uses indicator's frame directly so make sure you size custom indicator view beforehand. Such views as `UIImageView` or `UIActivityIndicatorView` will automatically resize themselves so no need to setup frame for them. + + ### Contributors * [@GorkaMM](https://github.com/GorkaMM)
diff --git a/publish-docs.sh b/publish-docs.sh new file mode 100755 index 0000000..886197e --- /dev/null +++ b/publish-docs.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +jazzy \ + --clean \ + --objc \ + --sdk iphoneos \ + --theme fullwidth \ + --author 'Andrei Mihailov' \ + --author_url http://codeispoetry.ru/ \ + --github_url https://github.com/pronebird/UIScrollView-InfiniteScroll \ + --github-file-prefix https://github.com/pronebird/UIScrollView-InfiniteScroll/tree/1.0.0 \ + --module-version 1.0.0 \ + --umbrella-header Classes/UIScrollView+InfiniteScroll.h \ + --framework-root Classes \ + --module UIScrollView_InfiniteScroll + +git add docs && git commit -m "Update docs subtree commit" +git subtree push --prefix docs origin gh-pages +git reset --soft HEAD~1 +git rm -rf docs