Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/aschuch/StatefulViewController

Placeholder views based on content, loading, error or empty states
https://github.com/aschuch/StatefulViewController

empty error loading placeholder state state-management swift uiviewcontroller

Last synced: 3 months ago
JSON representation

Placeholder views based on content, loading, error or empty states

Awesome Lists containing this project

README

        

# StatefulViewController

[![Build Status](https://travis-ci.org/aschuch/StatefulViewController.svg)](https://travis-ci.org/aschuch/StatefulViewController)
![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)
![Swift 3.0](https://img.shields.io/badge/Swift-3.0-orange.svg)
![Platform](https://img.shields.io/badge/platform-iOS%20%7C%20tvOS-lightgrey.svg)

A protocol to enable `UIViewController`s or `UIView`s to present placeholder views based on content, loading, error or empty states.

![StatefulViewController Example](Resources/example.gif)

## Overview

In a networked application a view controller or custom view typically has the following states that need to be communicated to the user:

* **Loading**: The content is currently loaded over the network.
* **Content**: The content is available and presented to the user.
* **Empty**: There is currently no content available to display.
* **Error**: An error occurred whilst downloading content.

As trivial as this flow may sound, there are a lot of cases that result in a rather large decision tree.

![Decision Tree](Resources/decision_tree.png)

`StatefulViewController` is a concrete implementation of this particular decision tree. (If you want to create your own modified version, you might be interested in the [state machine](#viewstatemachine) that is used to show and hide views.)

## Version Compatibility

Current Swift compatibility breakdown:

| Swift Version | Framework Version |
| ------------- | ----------------- |
| 3.0 | 3.x |
| 2.3 | 2.x |
| 2.2 | 1.x |

[all releases]: https://github.com/aschuch/StatefulViewController/releases

## Usage

> This guide describes the use of the `StatefulViewController` protocol on `UIViewController`. However, you can also adopt the `StatefulViewController` protocol on any `UIViewController` subclass, such as `UITableViewController` or `UICollectionViewController`, as well as your custom `UIView` subclasses.

First, make sure your view controller adopts to the `StatefulViewController` protocol.

```swift
class MyViewController: UIViewController, StatefulViewController {
// ...
}
```

Then, configure the `loadingView`, `emptyView` and `errorView` properties (provided by the `StatefulViewController` protocol) in `viewDidLoad`.

```swift
override func viewDidLoad() {
super.viewDidLoad()

// Setup placeholder views
loadingView = // UIView
emptyView = // UIView
errorView = // UIView
}
```

In addition, call the `setupInitialViewState()` method in `viewWillAppear:` in order to setup the initial state of the controller.

```swift
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)

setupInitialViewState()
}
```

After that, simply tell the view controller whenever content is loading and `StatefulViewController` will take care of showing and hiding the correct loading, error and empty view for you.

```swift
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)

loadDeliciousWines()
}

func loadDeliciousWines() {
startLoading()

let url = NSURL(string: "http://example.com/api")
let session = NSURLSession.sharedSession()
session.dataTaskWithURL(url) { (let data, let response, let error) in
endLoading(error: error)
}.resume()
}
```

### Life cycle

StatefulViewController calls the `hasContent` method to check if there is any content to display. If you do not override this method in your own class, `StatefulViewController` will always assume that there is content to display.

```swift
func hasContent() -> Bool {
return datasourceArray.count > 0
}
```

Optionally, you might also be interested to respond to an error even if content is already shown. `StatefulViewController` will not show its `errorView` in this case, because there is already content that can be shown.

To e.g. show a custom alert or other unobtrusive error message, use `handleErrorWhenContentAvailable:` to manually present the error to the user.

```swift
func handleErrorWhenContentAvailable(error: ErrorType) {
let alertController = UIAlertController(title: "Ooops", message: "Something went wrong.", preferredStyle: .Alert)
alertController.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
presentViewController(alertController, animated: true, completion: nil)
}
```

### Custom Placeholder View insets

Per default, StatefulViewController presents all configured placeholder views fullscreen (i.e. with 0 insets from top, bottom, left & right from the superview). In case a placeholder view should have custom insets the configured placeholderview may conform to the `StatefulPlaceholderView` protocol and override the `placeholderViewInsets` method to return custom edge insets.

```swift
class MyPlaceholderView: UIView, StatefulPlaceholderView {
func placeholderViewInsets() -> UIEdgeInsets {
return UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
}
}
```

### View State Machine

> Note: The following section is only intended for those, who want to create a stateful controller that differs from the flow described above.

You can also use the underlying view state machine to create a similar implementation for your custom flow of showing/hiding views.

```swift
let stateMachine = ViewStateMachine(view: view)

// Add states
stateMachine["loading"] = loadingView
stateMachine["other"] = otherView

// Transition to state
stateMachine.transitionToState(.View("loading"), animated: true) {
println("finished switching to loading view")
}

// Hide all views
stateMachine.transitionToState(.None, animated: true) {
println("all views hidden now")
}
```

## Installation

#### Carthage

Add the following line to your [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile).

```
github "aschuch/StatefulViewController" ~> 3.0
```

Then run `carthage update`.

#### CocoaPods

Add the following line to your Podfile.

```
pod "StatefulViewController", "~> 3.0"
```

Then run `pod install` with CocoaPods 0.36 or newer.

#### Manually

Just drag and drop the two `.swift` files in the `StatefulViewController` folder into your project.

## Tests

Open the Xcode project and press `⌘-U` to run the tests.

Alternatively, all tests can be run from the terminal using [xctool](https://github.com/facebook/xctool).

```bash
xctool -scheme StatefulViewControllerTests -sdk iphonesimulator test
```

## Todo

* Default loading, error, empty views
* Protocol on views that notifies them of removal and add
* Views can provide delays in order to tell the state machine to show/remove them only after a specific delay (e.g. for hide and show animations)

## Contributing

* Create something awesome, make the code better, add some functionality,
whatever (this is the hardest part).
* [Fork it](http://help.github.com/forking/)
* Create new branch to make your changes
* Commit all your changes to your branch
* Submit a [pull request](http://help.github.com/pull-requests/)

## Contact

Feel free to get in touch.

* Website:
* Twitter: [@schuchalexander](http://twitter.com/schuchalexander)