Auto Layout をちょっと楽に記述できる AutolayoutHelper を作った

# <i class="fa fa-cubes" aria-hidden="true"></i> はじめに

最近 [NSLayoutAnchor](https://developer.apple.com/documentation/uikit/nslayoutanchor) を利用していますが、`isActive = true` や `translatesAutoresizingMaskIntoConstraints = false` をよく忘れます :sob: 

また以前は [SnapKit](https://github.com/SnapKit/SnapKit) を利用していて、制約が壊れた際のログが以下のように出力されるので、どのファイルのどの行当たりを見ればいいかが一目瞭然でとても助かっていました。

```
(
    "<SnapKit.LayoutConstraint:0x6100000a32a0@ViewController.swift#90 UIView:0x7fee58409540.left == UIView:0x7fee58400c60.left + 22.0>",
    "<SnapKit.LayoutConstraint:0x6100000a3540@ViewController.swift#92 UIView:0x7fee58409540.right == UIView:0x7fee58400c60.right - 22.0>",
    "<SnapKit.LayoutConstraint:0x6100000a35a0@ViewController.swift#93 UIView:0x7fee58409540.width == 300.0>",
    "<NSLayoutConstraint:0x618000097bb0 'UIView-Encapsulated-Layout-Width' UIView:0x7fee58400c60.width == 768   (active)>"
)
```

# <i class="fa fa-github" aria-hidden="true"></i> [AutolayoutHelper](https://github.com/sora0077/AutolayoutHelper)


ということで `NSLayoutAnchor` を薄くラップしつつ、デバッグしやすいライブラリを作ってみました。

### <i class="fa fa-terminal" aria-hidden="true"></i> インストール

```Cartfile
github "sora0077/AutolayoutHelper" "master"
```

※ 今のところ [Carthage](https://github.com/Carthage/Carthage) のみの対応です。


### <i class="fa fa-rocket" aria-hidden="true"></i> 使い方

- import すると `UIView`, `UILayoutGuide` など `NSLayoutAnchor` を持っている型に `autolayout`, (`autoresizing`) プロパティが生えます。
- 基本的には `NSLayoutAnchor` とほぼ同じインターフェースです。
    - 追加として、よく使うサイズ指定や親ビューにフィットする指定のために `size`, `edges` などが追加されています。

例）

```swift
import UIKit
import AutolayoutHelper

class ViewController: UIViewController {
    class View: UIView {}

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        let orangeBox = UIView()
        orangeBox.backgroundColor = .orange
        view.addSubview(orangeBox)
        orangeBox.autolayout.edges.equal(to: view.autoresizing.edges, insets: (x: 50, y: 100))
        
        let redBox = UIView()
        redBox.backgroundColor = .red
        view.addSubview(redBox)
        redBox.autolayout.top.equal(to: orangeBox.autolayout.top, constant: 50)
        redBox.autolayout.left.equal(to: orangeBox.autolayout.left)
        let sizeConstraint =
            redBox.autolayout.size.equal(to: orangeBox.autolayout.size, insets: (x: 50, y: 0), priority: .defaultHigh)
        redBox.autolayout.bottom.equal(to: orangeBox.autolayout.bottom)
        
        print(sizeConstraint.width, sizeConstraint.height)  // NSLayoutConstraint, NSLayoutConstraint
    }
}
```

<img width="414" alt="Simulator Screen Shot 2017.06.16 20.11.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/8508/bc834fcc-6513-4c93-7801-1023f9e1fe5c.png">


# <i class="fa fa-bug" aria-hidden="true"></I> デバッグ

もし制約が満たせていない場合、以下のようなログ表示になります。

```
(
    "<NSLayoutConstraint:0x600000098e70 '@ViewController.swift#22' H:|-(50)-[UIView:0x7f981ec05270](LTR)   (active, names: '|':UIView:0x7f981ec05670 )>",
    "<NSLayoutConstraint:0x600000098f10 '@ViewController.swift#22' UIView:0x7f981ec05270.trailing == UIView:0x7f981ec05670.trailing - 50   (active)>",
    "<NSLayoutConstraint:0x6000000990a0 '@ViewController.swift#23' UIView:0x7f981ec05270.width == 30   (active)>",
    "<NSLayoutConstraint:0x608000095db0 'UIView-Encapsulated-Layout-Width' UIView:0x7f981ec05670.width == 414   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x6000000990a0 '@ViewController.swift#23' UIView:0x7f981ec05270.width == 30   (active)>
```

# <i class="fa fa-pencil" aria-hidden="true"></i> 実装

登場するのは基本的に `Layout<Anchors>` だけです。

```swift
public struct Layout<Anchors> {
    let anchors: Anchors
}
```

この型に対して制約付きの `extension` を必要な分だけ定義しています。
例えば `size` に対する実装は以下になります。

```swift
// UIView.autolayout
var size: Layout<(width: NSLayoutDimension, height: NSLayoutDimension)> { get }
```

```swift
public extension Layout where Anchors == (width: NSLayoutDimension, height: NSLayoutDimension) {
    @discardableResult
    func equal(to other: Layout,
               multiplier: CGFloat = 1,
               constant: CGFloat = 0,
               priority: LayoutPriority = .required,
               file: StaticString = #file,
               line: UInt = #line
        ) -> (width: NSLayoutConstraint, height: NSLayoutConstraint) {
        return (
            width: anchors.width.constraint(equalTo: other.width, ...),
            height: anchors.height.constraint(equalTo: other.height, ...),
        )
    }
}
```

`Anchors` が２つの `NSLayoutDimension` であるときに同じ型を引数に持つ `equal` メソッドを定義しています。

全体は [こちら](https://github.com/sora0077/AutolayoutHelper) です。

# <i class="fa fa-info-circle" aria-hidden="true"></i> 補足

使い方で出てきた `autoresizing` プロパティですが、実態は以下のように `translatesAutoresizingMaskIntoConstraints` を変更しているかどうかの違いです。
これについては、あまり良いインターフェースではない気がするので今後無くすかもしれません。

```swift
public extension AutolayoutExtension where Self: UIView {
    var autolayout: Extension<Self> {
        if translatesAutoresizingMaskIntoConstraints {
            translatesAutoresizingMaskIntoConstraints = false
        }
        return Extension(base: self)
    }

    var autoresizing: Extension<Self> {
        return Extension(base: self)
    }
}
```