AloeStackViewで簡単リストレイアウト

## AloeStackViewとは

[AloeStackView](https://github.com/airbnb/AloeStackView)はAirbnbが作ったUIを速くつるるためのViewで、UITableViewみたいな縦のリストレイアウトがUITableViewのようにdelegateやdataSourceを用意せずに簡単に実装できます。

これまでだとこういった用途の場合は、UIStackViewで縦に並べて実装したりしていましたが、タップ時の挙動やセパレーターをつくったりなど細かいところをいちいち自前で実装しなければなりませんでした。AloeStackViewも中身はUIStackViewですが、こういった面倒なところがぱぱっとできるようにinterfaceが用意されているので自前でUIStackViewを使うより便利になっています。

ただ、AloeStackViewは速くシンプルに作ることを目的としているので、UITableViewのようにViewの再利用はしません。大きい画面だとメモリを食いますし、最初の描画に時間がかかってしまうというデメリットもあります。なので完全にUITableViewを置き換えるものではありません。

コードが500行以下と小さく（UIScrollViewにUIStackViewを配置して縦に並べているだけ）、外部のライブラリも使ってないので導入のハードルは低めですので、気軽に試しやすいライブラリではないかなと思います。

[Airbnbのblog](https://medium.com/airbnb-engineering/introducing-aloestackview-for-ios-a676d253c6ba)でも紹介されているので興味ある方はこちらも御覧ください。

## AloeStackViewの使い方

### 基本

viewを作って

```swift
    import AloeStackView
    
    let stackView = AloeStackView()
```

表示するviewをaddRowするだけです

```swift
    let label = UILabel()
    label.text = "Label"
    stackView.addRow(label)
```

そうするとこのようにUITableViewでcellを追加したかのような表示になります。

![](https://d2mxuefqeaa7sj.cloudfront.net/s_FB66CAC8E445D0E88E9AA19E5DF747480CA2ACBCF8467DFDFF882BC81A57FC1B_1544797344887_image.png)


Viewの再利用はないので、このLabelのテキストを更新したい場合は

```swift
    label.text = "Text"
```

単純にviewを更新するだけです。（UITableViewのようにcellをreloadする必要はありません）

画面全体でAloeStackViewを使いたい場合は[AloeStackViewController](https://github.com/airbnb/AloeStackView/blob/master/Sources/AloeStackView/AloeStackViewController.swift)が用意されているのでこれを使ってもよいです。現在(v1.0.1)時点ではStoryboardに対応していないのでコードでAloeStackViewControllerを生成しなればなりません。（Storyboard上でAloeStackViewControllerを設定するのは☓）

```swift
    import AloeStackView
    
    class ViewController: AloeStackViewController {
      override func viewDidLoad() {
        super.viewDidLoad()
        stackView.addRow(...)
      }
    }
```

### Rowの追加、挿入、削除
Row(UITableViewでいうところのcell)の追加、削除には、それぞれ一つのRowを追加、削除するメソッドと複数のRowをまとめて追加、削除するメソッドの両方が用意されています。

#### 末尾に追加
- `func addRow(_ row: UIView, animated: Bool = false)`
- `func addRows(_ rows: [UIView], animated: Bool = false)`

#### 先頭に挿入
- `func prependRow(_ row: UIView, animated: Bool = false)`
- `func prependRows(_ rows: [UIView], animated: Bool = false)`

#### 指定したRow(View)の前に挿入
- `func insertRow(_ row: UIView, before beforeRow: UIView, animated: Bool = false)` 
- `func insertRows(_ rows: [UIView], before beforeRow: UIView, animated: Bool = false)`

#### 指定したRow(View)の後に挿入
- `func insertRow(_ row: UIView, after afterRow: UIView, animated: Bool = false)`
- `func insertRows(_ rows: [UIView], after afterRow: UIView, animated: Bool = false)`

#### 指定したRowの削除
- `func removeRow(_ row: UIView, animated: Bool = false)`
- `func removeRows(_ rows: [UIView], animated: Bool = false)`

#### 全Row削除
- `func removeAllRows(animated: Bool = false)`

### タップ
Rowをタップしたときに処理を行うには2通りの方法があります。

#### TapHandlerを使うパターン
一つはTapHandlerを使う法法です。

- `func setTapHandler<RowView: UIView>(forRow row: RowView, handler: ((RowView) -> Void)?)`

```swift
    stackView.setTapHandler(
      forRow: label,
      handler: { [weak self] label in
        self?.showAlert(title: "Row Tapped", message: "Tapped on: \(label.text ?? "")")
      })
    
    label.isUserInteractionEnabled = true
```

気をつけることは、`isUserInteractionEnable`がtrueのときにしか反応しないようになっている点です。

#### Tappableを使うパターン
もう一つの方法はRowにaddするViewを[Tappable](https://github.com/airbnb/AloeStackView/blob/master/Sources/AloeStackView/Protocols/Tappable.swift)に適用させてdidTapView()を実装することです。
そうするとタップするとdidTapView()が呼ばれます。

```swift
    class ToggleLabel: UILabel, Tappable {
      func didTapView() {
        textColor = textColor == .red ? .black : .red
      }
    }
```

```swift
    let label = ToggleLabel()
    label.text = "Label"
    label.isUserInteractionEnabled = true
    stackView.addRow(label)
```

こちらの場合も、`isUserInteractionEnable`がtrueのときにしか反応しないようになっています。

#### タップしたときにハイライトする
[Highlightable](https://github.com/airbnb/AloeStackView/blob/master/Sources/AloeStackView/Protocols/Highlightable.swift)プロトコルを適用してやるだけです。

```swift
    class ToggleLabel: UILabel, Highlightable {
    }
```

### Rowの表示非表示

#### 非表示
- `func hideRow(_ row: UIView, animated: Bool = false)`
- `func hideRows(_ rows: [UIView], animated: Bool = false)`

#### 表示
- `func showRow(_ row: UIView, animated: Bool = false)`
- `func showRows(_ rows: [UIView], animated: Bool = false)`

#### 表示・非表示
上の`showRow` `hideRow`との違いはisHiddenで表示非表示をセットするかの違いです。

- `func setRowHidden(_ row: UIView, isHidden: Bool, animated: Bool = false)`
- `func setRowsHidden(_ rows: [UIView], isHidden: Bool, animated: Bool = false)`

#### 表示非表示状態の取得
- `func isRowHidden(_ row: UIView) -> Bool`

### スタイル
Rowの余白の設定と、背景色、選択色、セパレータのデフォルトの色や個別のRowの色の設定を行うことができます。

#### RowのInset（Padding）
Rowの上下左右の余白を変更したい場合に使います。

- `var rowInset`
  - addしたあとに変更しても変わりません
    
- `func setInset(forRow row: UIView, inset: UIEdgeInsets)`
- `func setInset(forRows rows: [UIView], inset: UIEdgeInsets)`
  - addした後変更したい場合はこちらを使います。

#### 背景色
Rowの背景色を変更したい場合に使います。

- `var rowBackgroundColor`
  - デフォルトは透明。
  - addしたあとにrowBackgroundColorを変更しても変わりません

- `func setBackgroundColor(forRow row: UIView, color: UIColor)`
- `func setBackgroundColor(forRows rows: [UIView], color: UIColor)`
  - addした後変更したい場合はこちらを使います。

#### 選択色
Rowを選択したときの色を変更したい場合に使います。
※StackViewCellのメソッドなのでcellForRow()でStackViewCellを取得して呼ぶ必要があります。

- `var rowHighlightColor`

#### セパレータ関連

##### セパレータの色
- `var separatorColor`

##### セパレータの高さ
- `var separatorHeight`

##### セパレータのInset
- `var separatorInset: UIEdgeInsets`
  - addしたあとに変更しても変わりません

- `func setSeperatorInset(forRow row: UIView, inset: UIEdgeInsets)`
- `func setSeperatorInset(forRows rows: [UIView], inset: UIEdgeInsets)`
  - addした後変更したい場合はこちらを使います。

##### セパレータの表示非表示
- `var hidesSeparatorsByDefault`
  - デフォルト値：false
  - デフォルトでセパレータを非表示にする設定。addしたあとに変更しても変わりません

- `func hideSeparator(forRow row: UIView)`
- `func hideSeparators(forRows rows: [UIView])`
- `func showSeparator(forRow row: UIView)`
- `func showSeparators(forRows rows: [UIView])`
  - addした後変更したい場合はこちらを使います。

- ` var automaticallyHidesLastSeparator`
  - デフォルト値：`false`
  - 一番下のRowのセパレータを自動で隠す設定。
 
-  SeparatorHidingプロトコル
  - [SeparatorHiding](https://github.com/airbnb/AloeStackView/blob/master/Sources/AloeStackView/Protocols/SeparatorHiding.swift)プロトコルを適用してやるとそのRowだけセパレータが表示されません。

```swift
    class ToggleLabel: UILabel, SeparatorHiding {
    }
```

### その他
#### 全Row取得
- `func getAllRows() -> [UIView]`

#### Rowがあるか
- `func containsRow(_ row: UIView) -> Bool`

#### Rowを見える位置にスクロール 
- `func scrollRowToVisible(_ row: UIView, animated: Bool = true)`

#### AloeStackViewを継承して使う用

- `func cellForRow(_ row: UIView) -> StackViewCell`
  - cellを追加したりしたときの呼ばれてrowに対応するStackViewCellを返す。

- `func configureCell(_ cell: StackViewCell)`
  - 中身は空。生成時(rowのadd, insert)に呼ばれる。

## まとめ
一通り実装を見ていくと、move系以外はUITableViewでできること主なことはだいたいできるようでした。

一番下の行のセパレータを自動で隠す機能についてはUITableViewよりもAloeStackViewのほうが便利だなと思いました。