{"id":26869504,"url":"https://github.com/jakemarsh/jmstatefultableviewcontroller","last_synced_at":"2025-05-07T04:22:58.161Z","repository":{"id":3188558,"uuid":"4221095","full_name":"jakemarsh/JMStatefulTableViewController","owner":"jakemarsh","description":"A subclassable table view controller with empty, loading and error states, also supports infinite scrolling and pull to refresh.","archived":false,"fork":false,"pushed_at":"2013-06-19T22:41:38.000Z","size":438,"stargazers_count":103,"open_issues_count":1,"forks_count":18,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-05-02T19:09:12.510Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://deallocatedobjects.com","language":"Objective-C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jakemarsh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-05-04T02:59:02.000Z","updated_at":"2023-08-27T13:54:45.000Z","dependencies_parsed_at":"2022-09-12T00:50:56.767Z","dependency_job_id":null,"html_url":"https://github.com/jakemarsh/JMStatefulTableViewController","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakemarsh%2FJMStatefulTableViewController","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakemarsh%2FJMStatefulTableViewController/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakemarsh%2FJMStatefulTableViewController/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakemarsh%2FJMStatefulTableViewController/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jakemarsh","download_url":"https://codeload.github.com/jakemarsh/JMStatefulTableViewController/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252811189,"owners_count":21807905,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-31T06:17:35.166Z","updated_at":"2025-05-07T04:22:58.139Z","avatar_url":"https://github.com/jakemarsh.png","language":"Objective-C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JMStatefulTableViewController\n\nThis is the class I use whenever I need to implement a \"stateful\" table view in an iOS app. In this context, when I say \"stateful\" I mean a table view controller that has the following \"states\":\n\n* Initially loading for the first time since instantiating. (Usually displaying a \"loading\" view covering the table view entirely).\n* An \"idle\" state, where the user can scroll around and consume content, no special activity happening.\n* Loading from a \"pull to refresh\" gesture.\n* Loading the next \"page\" in a scenario where I need to scroll \"infinitely.\"\n* Empty (Usually displaying a nice looking \"empty\" view covering the table view entirely).\n* Error (This is useful when the \"initial\" load fails or I need to communicate that some other horrible thing has happened).\n\nIf you're using `JMStatefulTableViewController` in your application, add it to [the list](https://github.com/jakemarsh/JMStatefulTableViewController/wiki/Applications).\n\n## Screenshots\n\n\u003ccenter\u003e\n\u003cimg src=\"http://cl.ly/IHVb/iOS%20Simulator%20Screen%20shot%20Jul%2023,%202012%2012.42.19%20PM.png\" width=\"320\" title=\"Pull to Refresh\" /\u003e\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u003cimg src=\"http://cl.ly/IGtN/iOS%20Simulator%20Screen%20shot%20Jul%2023,%202012%2012.42.22%20PM.png\" width=\"320\" title=\"Infinte Scrolling\" /\u003e\n\u003c/center\u003e\n\n## Example Usage\n\nThe demo project hosted in this repo is the first place you should look for how to implement `JMStatefulTableViewController` in your app, but basically you just need to subclass `JMStatefulTableViewController` and implement the required delegate methods on that subclass.\n\nThe next section shows an example of how you might implement the required delegate methods.\n\n### First Time Loading\n\n`JMStatefulTableViewController` will call it's `statefulDelegate` with this method, passing it in two blocks, a `success` and `failure` block, when the table view needs to load it's \"initial\" bit of content. It will also transparently handle changing the state to `JMStatefulTableViewControllerStateInitialLoading` for you. \n\nYou should write or call your code to load your initial set of content inside this method, and then call the correct block for the outcome. If your data loaded successfully, call the `success`, if it failed for some reason call the `failure` block, optionally passing in an `NSError` object, or `nil`.\n\n\n``` objective-c\n- (void) statefulTableViewControllerWillBeginInitialLoading:(JMStatefulTableViewController *)vc completionBlock:(void (^)())success failure:(void (^)(NSError *error))failure {\n\t// Always do any sort of heavy loading work on a background queue:\n    dispatch_async(dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{\n        self.catPhotos = [self _loadHilariousCatPhotosFromTheInternet];\n                                                    \n\t\t// Always call success() on the main queue:\n        dispatch_async(dispatch_get_main_queue(), ^{\n            success();\n        });\n    });\n}\n```\n\n### Loading From Pull To Refresh\n\n`JMStatefulTableViewController` will call it's `statefulDelegate` with this method, passing it in two blocks, a `success` and `failure` block when the user finishes a \"pull to refresh\" gesture. Note that the `success` block in this case is asking for an array of `NSIndexPath` objects.\n\nI've implemented it this way so I can easily achieve what I call \"proper\" pull to refresh style loading. In \"proper\" pull to refresh loading, the existing content stays in place and the new content appears above it, without offsetting the table view at all. This is how [Loren Brichter](http://twitter.com/lorenb) (original inventor of the concept) originally invented and intended it to work. In my opinion it also makes more logical sense. However, if you'd like, you can simple pass `nil` in for the array of `NSIndexPaths` or an empty `NSArray` object, and `JMStatefulTableViewController` will degrade gracefully, replacing the content in your tableview with the latest content. \n\nYou should write or call your code to load any newer content than the current first item (or optionally just reload everything, like many apps do these days), and then call the correct block for the outcome. If your data loaded successfully, call the `success` block, if it failed for some reason call the `failure` block, optionally passing in an `NSError` object, or `nil`.\n\n``` objective-c\n- (void) statefulTableViewControllerWillBeginLoadingFromPullToRefresh:(JMStatefulTableViewController *)vc completionBlock:(void (^)(NSArray *indexPathsToInsert))success failure:(void (^)(NSError *error))failure {\n\t// Always do any sort of heavy loading work on a background queue:\n    dispatch_async(dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{\n\t\t// Grab what is currently our first photo\n\t\tCatPhoto *photo = [self.catPhotos objectAtIndex:0];\n\t\t\n\t\t// Load any newer photos that might have been added on our server\n        NSArray *catPhotos = [self _loadHilariousCatPhotosFromTheInternetNewerThanPhoto:photo];\n\n\t\t// Prepend our self.catPhotos array with these new photos we loaded\n        self.catPhotos = [catPhotos arrayByAddingObjectsFromArray:self.catPhotos];\n\n\t\t// Put together an array of NSIndexPath objects representing\n\t\t// what the index paths will be of the new rows that will be created\n        NSMutableArray *a = [NSMutableArray array];\n\n        for(NSInteger i = 0; i \u003c loadedBeerStrings.count; i++) {\n            [a addObject:[NSIndexPath indexPathForRow:i inSection:0]];\n        }\n\n\t\t// Always call success() on the main queue:\n        dispatch_async(dispatch_get_main_queue(), ^{\n\t\t\t// If we didn't want to achieve \"proper\" pull to refresh behavior, we could just pass `nil` in here:\n            success([NSArray arrayWithArray:a]);\n        });\n    });\n}\n```\n\n### Loading The Next \"Page\"\n\n`JMStatefulTableViewController` will call it's `statefulDelegate` with this method, passing it in two blocks, a `success` and `failure` block, when the users scrolls to the bottom of your table view.\n\nYou should write or call your code to load the next set of content, and then call the correct block for the outcome. If your data loaded successfully, call the `success` block, if it failed for some reason call the `failure` block, optionally passing in an `NSError` object, or `nil`.\n\n``` objective-c                                                                 \n- (void) statefulTableViewControllerWillBeginLoadingNextPage:(JMStatefulTableViewController *)vc completionBlock:(void (^)())success failure:(void (^)(NSError *))failure {\n\t// Always do any sort of heavy loading work on a background queue:\n    dispatch_async(dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{\n\t\t// Grab what is currently our last photo\n\t\tCatPhoto *photo = [self.catPhotos lastObject];\n\t\t\n\t\t// Load any older cat photos from our server\n        NSArray *catPhotos = [self _loadHilariousCatPhotosFromTheInternetOlderThanPhoto:photo];\n\n\t\t// Append the new photos we've loaded to the end of your self.catPhotos array\n\t\tself.catPhotos = [self.catPhotos arrayByAddingObjectsFromArray:catPhotos];\n\n\t\t// Always call success() on the main queue:\n        dispatch_async(dispatch_get_main_queue(), ^{\n            success();\n        });\n    });    \n}   \n```\n\n### Loading The Next \"Page\"\n\n`JMStatefulTableViewController` will call it's `statefulDelegate` with this method to determine if it can load any more content.\n\nYou should return a value indicating whether or not any more content exists to be loaded. This will control whether or not the user is shown a \"Loading more\" visual state.\n\n``` objective-c\n- (BOOL) statefulTableViewControllerShouldBeginLoadingNextPage:(JMStatefulTableViewController *)vc {\n    return [self _areThereAnyMoreHilariousCatPhotosOnTheServer];\n}\n```\n\n## Pull To Refresh Customization\n\n`JMStatefulTableViewController` uses [@samvermette](https://github.com/samvermette)'s excellent [`SVPullToRefresh`](https://github.com/samvermette/SVPullToRefresh) library to accomplish both pull to refresh and infinite scrolling. It is very customizable, [you can read all about how in `SVPullToRefresh`'s documentation](https://github.com/samvermette/SVPullToRefresh#readme).\n\n## Empty, Loading and Error Views\n\nThe demo app in this repo uses the built-in implementations of these views. Right now, they are simply full width and height solid color views, to give you something to look at when building your app.\n\nYou can subclass `JMStatefulTableViewLoadingView`, `JMStatefulTableViewEmptyView` and `JMStatefulTableViewErrorView` respectively. Currently, they do not offer any special functionality or look and feel, but in the future they will emulate a \"system\" look and feel for these states. Feel free to take them or leave them.\n\n`JMStatefulTableViewController` has three properties:\n\n``` objective-c\n@property (strong, nonatomic) UIView *emptyView;\n@property (strong, nonatomic) UIView *loadingView;\n@property (strong, nonatomic) UIView *errorView;\n```\n\nYou can set these to any `UIView` you'd like, to indicate any of these states. Like I said, right now, by default, they're not anything useful, just solid colored views.\n\n## Adding To Your Project\n\n### With CocoaPods\n\nIf you are using [CocoaPods](http://cocoapods.org) then just add this line to your `Podfile`:\n\n``` ruby\npod 'JMStatefulTableViewController'\n```\n\nNow run `pod install` to install the dependency.\n\n### Without CocoaPods\n\n[Download](https://github.com/jakemarsh/JMStatefulTableViewController/zipball/master) the source files or add it as a [git submodule](http://schacon.github.com/git/user-manual.html#submodules). Here's how to add it as a submodule:\n\n    $ cd YourProject\n    $ git submodule add https://github.com/jakemarsh/JMStatefulTableViewController.git Vendor/JMStatefulTableViewController\n\nAdd all of the Objective-C files to your project.\n\nIf you're installing this way, (instead of using CocoaPods) you'll also need to separately install [SVPullToRefresh](https://github.com/samvermette/SVPullToRefresh) on your own, as described in the [SVPullToRefresh README](https://github.com/samvermette/SVPullToRefresh#readme). (For this reason, and because it's an awesome system, I strongly reccomend using CocoaPods).\n\n`JMStatefulTableViewController` uses [Automatic Reference Counting (ARC)](http://clang.llvm.org/docs/AutomaticReferenceCounting.html). If your project doesn't use ARC, you will need to set the `-fobjc-arc` compiler flag on all of the `JMStatefulTableViewController` source files. To do this in Xcode, go to your active target and select the \"Build Phases\" tab. In the \"Compiler Flags\" column, set `-fobjc-arc` for each of the `JMStatefulTableViewController` source files.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakemarsh%2Fjmstatefultableviewcontroller","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjakemarsh%2Fjmstatefultableviewcontroller","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakemarsh%2Fjmstatefultableviewcontroller/lists"}