ReSwift vol.1

# やること： API → 表示 

<img src="https://j.gifs.com/LgwN04.gif">

Calil のAPI から図書館情報を取得して一覧で表示。
https://calil.jp/doc/api.html

同じ構成のjavascript実装はこちら
http://qiita.com/nakadoribooks/items/a31a85c788bbc90baf56

## 成果物
https://github.com/nakadoribooks/CalilClient-ios/releases/tag/vol1

#準備

### 環境
xcode8.2.1

###ライブラリ取り込み

```:Podfile
use_frameworks!
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'

target 'CalilClient' do
  pod 'ReSwift'
  pod 'Alamofire', '~> 4.4'
end
```

`pod update`

###ファイル構成
![スクリーンショット 2017-03-10 21.54.02.png](https://qiita-image-store.s3.amazonaws.com/0/159266/425bde8e-6330-24a4-6b7d-031d0fa8abc4.png)

# 処理の流れ

0: globalにstoreを作る (Global)

```swift:AppDelegate.swift
import UIKit
import ReSwift

// The global application store, which is responsible for managing the appliction state.
let mainStore = Store<AppState>(
    reducer: CalilReducer(),
    state: nil
)

// ↑ これ

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

```

1: 読み込みイベントを作る (View)

```swift:LibraryListViewController.swift
let loadAction = ActionCreator.loadLibraries() /*← こっち*/
mainStore.dispatch(loadAction)
```

1.5: 読み込む and Actionを作って返す (ActionCreator)

```swift:ActionCreator.swift
static func loadLibraries(prefName:String="秋田県")->Action{
        let parameters: Parameters = ["appkey": Config.API_KEY, "pref":prefName, "format":"json", "callback":""]
        
        Alamofire.request(Config.API_LIBRARY, parameters: parameters).responseJSON { (response) in

                // 〜 略 〜 
                /* 読み込んだら loadedActionをdispatchして 5 へ*/
                let loadedAction = LibraryListLoadedAction(libraryList: results)
                mainStore.dispatch(loadedAction)
        }
        
        // 読み込み開始アクションを返す 2 へ
        return LibraryListLoadAction()

```

2: (from 1) 作ったイベントをdispatch (View)

```swift:LibraryListViewController.swift
let loadAction = ActionCreator.loadLibraries()
mainStore.dispatch(loadAction) /*← こっち*/
```

3: loadActionを受け取ってStateのisLoading を trueに (Reducer)

```swift:CalilReducer.swift
case _ as LibraryListLoadAction:
    state.errorMessage = nil
    state.isLoading = true
```

4: stateの変更を受け取ってViewを更新 (View)

```swift:LibraryListViewController.swift
func newState(state: AppState) {
    
    // 〜 略 〜    

    // loading
    if state.isLoading{
        reloadButton.isEnabled = false
        reloadButton.alpha = 0.5
        statusLabel.text = "Loading..."
        loadingIndicator.startAnimating()
        return;
    }
```

5: (from 1.5) 読み込み完了のイベントを受け取ってStateを更新 (Reducer)

```swift:CalilReducer.swift
    case let action as LibraryListLoadedAction:
        state.errorMessage = nil
        state.libraryList = action.libraryList
        state.isLoading = false
```

6: stateの変更を受け取ってViewを更新 (View)

```swift:LibraryListViewController.swift
func newState(state: AppState) {

    // 〜 略 〜

    // loaded
    reloadButton.isEnabled = true
    reloadButton.alpha = 1.0
    statusLabel.text = ""
    loadingIndicator.stopAnimating()
        
    self.libraryList = state.libraryList
    tableView.reloadData()
```

# 以下、コード全体

##State 3つ
読み込み中フラグ
表示するデータ
エラー有無

```swift:AppState.swift
import ReSwift

struct AppState: StateType {
    var isLoading:Bool = false
    var libraryList:[Library] = []
    var errorMessage:String?
}
```

##Action 3つ
読み込み開始
読み込み完了
エラー

```swift:Actions.swift
import ReSwift

struct LibraryListLoadAction:Action {}
struct LibraryListLoadedAction:Action{
    let libraryList:[Library]
}
struct LibraryListFailLoadAction:Action{}
```

## Reducer: Actionを受け取ってStateを変更
```swift:CalilReducer.swift
import ReSwift

// the reducer is responsible for evolving the application state based
// on the actions it receives
struct CalilReducer: Reducer {
    
    func handleAction(action: Action, state: AppState?) -> AppState {
        
        var state = state ?? AppState()
        
        switch action {
        case _ as LibraryListLoadAction:
            
            state.errorMessage = nil
            state.isLoading = true
            
        case _ as LibraryListFailLoadAction:
            state.errorMessage = "読み込みに失敗しました"
            state.isLoading = false
            
        case let action as LibraryListLoadedAction:
            state.errorMessage = nil
            state.libraryList = action.libraryList
            state.isLoading = false
            
        default:
            break
        }
        
        return state
    }
}

```

## ActionCreator: Action を作って返却 or 通信 → Action作って dispatch
この中でdispatchするのはどうなんだろう。

```swift:ActionCreator.swift
import ReSwift
import Alamofire

struct ActionCreator{
    
    static func loadLibraries(prefName:String="秋田県")->Action{
        let parameters: Parameters = ["appkey": Config.API_KEY, "pref":prefName, "format":"json", "callback":""]
        
        Alamofire.request(Config.API_LIBRARY, parameters: parameters).responseJSON { (response) in
            guard let json = response.result.value as? [NSDictionary] else{
                print("fail json")
                print(response)
                let loadFailAction = LibraryListFailLoadAction()
                mainStore.dispatch(loadFailAction)
                return
            }
            
            print("ok json")
            let results = Library.createList(list: json)
            let loadedAction = LibraryListLoadedAction(libraryList: results)
            
            // mainStore.dispatch(loadedAction)
            
            // 時間かかっているてい
            DispatchQueue.global(qos: .default).async {
                Thread.sleep(forTimeInterval: 1)
                DispatchQueue.main.async {
                    mainStore.dispatch(loadedAction)
                }
            }
        }
        
        
        return LibraryListLoadAction()
    }
    
}
```

## View: Stateの変更を受け取って表示を更新
```swift:LibraryListViewController.swift
import UIKit
import ReSwift

class LibraryListViewController: UIViewController, StoreSubscriber, UITableViewDelegate, UITableViewDataSource {
    
    // 〜 略 〜
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // 〜 略 〜

        // storeの通知を受け取る
        mainStore.subscribe(self)  
        // 読み込みアクションDispatch
        let loadAction = ActionCreator.loadLibraries()
        mainStore.dispatch(loadAction)
    }
    
    // 〜 略 〜
    
    private dynamic func tapReload(){
        // 読み込みアクションDispatch
        let loadAction = ActionCreator.loadLibraries()
        mainStore.dispatch(loadAction)
    }
    
    // MARK: StoreSubscriber
    
    func newState(state: AppState) {
        // Stateの変更を受け取って View の更新 //
        
        // error
        if let errorMessage = state.errorMessage{
            // 〜 略 〜
            return;
        }
        
        statusLabel.textColor = UI.textColor
        
        // loading
        if state.isLoading{
            // 〜 略 〜
            return;
        }
        
        // loaded
        // 〜 略 〜
        
        // 受け取ったデータでView更新
        self.libraryList = state.libraryList
        tableView.reloadData()
    }

    // 〜 略 〜
}

```

# その他

```swift:Config.swift

class Config: NSObject {

    static let API_ROOT = "https://api.calil.jp/"
    static let API_LIBRARY = API_ROOT + "library"
    static let API_KEY = "*****" // 書き換えてね
    
}

```

カーリルに開発者登録してAPI_KEYをゲットして書き換えてね。


# ありがとう Calil APi

https://calil.jp/doc/api.html
> 引用
未来の図書館を作るのはあなただ！
全国6000の図書館に簡単アクセス！ カーリルの図書館APIを使ってみよう
全国の図書館を対象としたリアルタイム蔵書検索を可能にするAPI群を開発者向けに提供します。
法人・個人を問わず無償で利用できます。