AutoLayout: 同じレイアウトを複数アプローチで実現してみる（4種＋α）

まず、本記事はもともと
「101匹AutoLayoutちゃん大行進」
とする予定だったが、筆者の時間と根気の問題により、解説規模を大幅に縮小させてもらった。なんとなくお詫びしておきたい。

さて、iOS/Macアプリ向けのAutoLayout制約については、筆者の知る限り、少なくとも5通りの設定方法が存在する。

1. [InterfaceBuilder（IB）]( https://developer.apple.com/jp/xcode/interface-builder/ )を利用してStoryBoard上で設定する
2. [Visual Format Language（VFL）]( https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html )を利用してコードで記述する
3. [NSLayoutConstraint(item: 〜)]( https://developer.apple.com/documentation/appkit/nslayoutconstraint/1526954-init )を利用してコードで記述する
4. [NSLayoutAnchor]( https://developer.apple.com/documentation/appkit/nslayoutanchor )を利用してコードで記述する （17/7/19追加。@rika-tawashiさんに感謝！）
5. 他の開発者に丸投げする

AutoLayoutを解説した素晴らしい記事は無数にあるが、
**結局のところ、どれを使えば良いの…？**
うわっ…私のAutoLayoutスキル、低すぎ…？
そんなお悩みに対する解決の一助となれば幸甚と考えまとめたのが、本記事である。

### サンプルソースについて

色違いでほぼ同じレイアウトのパーツを、上記四通りの手法でそれぞれ実装した実装例を以下に紹介する。

なお、お題の性質上やむを得ないが、実行結果があまりにも地味で物足りなかった為、単体でGet Wild可能なボタンパーツを衝動的に取り付けてしまった。本題とズレるので特に気にしない事を推奨する。

### 実行結果

![AboutAutoLayoutMovie.gif](https://qiita-image-store.s3.amazonaws.com/0/187895/40e8d9a1-90b2-ebe8-e449-a1d0295f37e2.gif)

### 記述例

#### StoryBoard上の全体構成

![Storyboard2.png](https://qiita-image-store.s3.amazonaws.com/0/187895/13657d1d-3d60-9a9e-8896-5aa78333e519.png)

#### 1. UseIBViewController

![UseIB2.png](https://qiita-image-store.s3.amazonaws.com/0/187895/6137927c-7759-2a39-2b46-740c81f4ae85.png)

```swift:UseIBViewController.swift
import UIKit

class UseIBViewController: UIViewController {
    
    @IBOutlet weak var textViewLabel: UITextView!
    
    override func viewDidLoad() {

        super.viewDidLoad()

        self.textViewLabel.font = .systemFont(ofSize: 20)
        self.textViewLabel.text = "\(Mirror(reflecting: self).subjectType)"
        self.textViewLabel.textAlignment = NSTextAlignment.center
        
    }
    
}
```

#### 2. UseVFLViewController

```swift:UseVFLViewController.swift
import UIKit

class UseVFLViewController: UseIBViewController {
    
    @IBOutlet weak var middleFrame: UIView?
    @IBOutlet weak var bottomButton: UIButton?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.setUpConstraint()
    }
    
    func setUpConstraint() {
        
        textViewLabel.translatesAutoresizingMaskIntoConstraints = false
        middleFrame?.translatesAutoresizingMaskIntoConstraints = false
        bottomButton?.translatesAutoresizingMaskIntoConstraints = false
        
        let horizontalConstraintsTop = NSLayoutConstraint.constraints(
            withVisualFormat: "H:|-[textViewLabel]-|", // use Standard Space.
            options: NSLayoutFormatOptions(),
            metrics: nil,
            views: ["textViewLabel": textViewLabel])
        NSLayoutConstraint.activate(horizontalConstraintsTop)
        
        let horizontalConstraintsMiddle = NSLayoutConstraint.constraints(
            withVisualFormat: "H:|-25-[middleFrame]-25-|",
            options: NSLayoutFormatOptions(),
            metrics: nil,
            views: ["middleFrame": middleFrame!,
                    "": self.edgesForExtendedLayout])
        NSLayoutConstraint.activate(horizontalConstraintsMiddle)
        
        let horizontalConstraintsBottom = NSLayoutConstraint.constraints(
            withVisualFormat: "H:|-40-[bottomFrame]-40-|",
            options: NSLayoutFormatOptions(),
            metrics: nil,
            views: ["bottomFrame": bottomButton!])
        NSLayoutConstraint.activate(horizontalConstraintsBottom)
        
        let verticalConstraints = NSLayoutConstraint.constraints(
            withVisualFormat: "V:|-20-[textViewLabel(==50)]-20-[middleFrame]-[bottomFrame]-[bottomLayoutGuide]|",
            options: NSLayoutFormatOptions(),
            metrics: nil,
            views: ["textViewLabel": textViewLabel,
                    "middleFrame": middleFrame!,
                    "bottomFrame": bottomButton!,
                    "bottomLayoutGuide": self.bottomLayoutGuide,
                    ])
        
        NSLayoutConstraint.activate(verticalConstraints)
        
    }
    
}
```

#### 3. UseItemViewController

```swift:UseItemViewController.swift
import UIKit

class UseItemViewController: UseIBViewController {
    
    @IBOutlet weak var middleFrame: UIView?
    @IBOutlet weak var bottomButton: UIButton?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.setUpConstraint()
    }
    
    func setUpConstraint() {
        
        textViewLabel.translatesAutoresizingMaskIntoConstraints = false
        middleFrame?.translatesAutoresizingMaskIntoConstraints = false
        bottomButton?.translatesAutoresizingMaskIntoConstraints = false
        
        if let view = self.view {
            
            // for TextViewLabel
            
            let traingSpaceTextViewLabel = NSLayoutConstraint(item: textViewLabel,
                                                         attribute: .trailing,
                                                         relatedBy: .equal,
                                                         toItem: view,
                                                         attribute: .trailingMargin,
                                                         multiplier: 1,
                                                         constant: 0)
            
            let leadingSpaceTextViewLabel = NSLayoutConstraint(item: textViewLabel,
                                                         attribute: .leading,
                                                         relatedBy: .equal,
                                                         toItem: view,
                                                         attribute: .leadingMargin,
                                                         multiplier: 1,
                                                         constant: 0)
            
            let topSpaceTextViewLabel = NSLayoutConstraint(item: textViewLabel,
                                                         attribute: .top,
                                                         relatedBy: .equal,
                                                         toItem: view,
                                                         attribute: .topMargin,
                                                         multiplier: 1,
                                                         constant: 20)
            
            let heightTextViewLabel = NSLayoutConstraint(item: textViewLabel,
                                                         attribute: .height,
                                                         relatedBy: .equal,
                                                         toItem: nil,
                                                         attribute: .height,
                                                         multiplier: 1,
                                                         constant: 50)
            
            // for middleFrame
            
            let trailingSpaceMiddleFrame = NSLayoutConstraint(item: middleFrame!,
                                                         attribute: .trailing,
                                                         relatedBy: .equal,
                                                         toItem: view,
                                                         attribute: .trailing,
                                                         multiplier: 1,
                                                         constant: -25)
            
            let leadingSpaceMiddleFrame = NSLayoutConstraint(item: middleFrame!,
                                                         attribute: .leading,
                                                         relatedBy: .equal,
                                                         toItem: view,
                                                         attribute: .leading,
                                                         multiplier: 1,
                                                         constant: 25)
            
            let topSpaceMiddleFrame = NSLayoutConstraint(item: middleFrame!,
                                                         attribute: .top,
                                                         relatedBy: .equal,
                                                         toItem: textViewLabel,
                                                         attribute: .bottom,
                                                         multiplier: 1,
                                                         constant: 20)
            
            // for bottomButton
            
            let trailingSpaceBottomButton = NSLayoutConstraint(item: bottomButton!,
                                                         attribute: .trailing,
                                                         relatedBy: .equal,
                                                         toItem: view,
                                                         attribute: .trailing,
                                                         multiplier: 1,
                                                         constant: -40)
            
            
            let leadingSpaceBottomButton = NSLayoutConstraint(item: bottomButton!,
                                                         attribute: .leading,
                                                         relatedBy: .equal,
                                                         toItem: view,
                                                         attribute: .leading,
                                                         multiplier: 1,
                                                         constant: 40)
            
            let topSpaceBottomButton = NSLayoutConstraint(item: bottomButton!,
                                                         attribute: .top,
                                                         relatedBy: .equal,
                                                         toItem: middleFrame!,
                                                         attribute: .bottom,
                                                         multiplier: 1,
                                                         constant: 8)
            
            let bottomSpaceBottomButton = NSLayoutConstraint(item: bottomButton!,
                                                         attribute: .bottom,
                                                         relatedBy: .equal,
                                                         toItem: bottomLayoutGuide,
                                                         attribute: .top,
                                                         multiplier: 1,
                                                         constant: -8)
            
            NSLayoutConstraint.activate([
                
                traingSpaceTextViewLabel,
                leadingSpaceTextViewLabel,
                topSpaceTextViewLabel,
                heightTextViewLabel,
                
                trailingSpaceMiddleFrame,
                leadingSpaceMiddleFrame,
                topSpaceMiddleFrame,
                
                trailingSpaceBottomButton,
                leadingSpaceBottomButton,
                topSpaceBottomButton,
                bottomSpaceBottomButton,
                
                ])
            
        }
        
    }
    
}
```

#### 4. UseAnchorViewController

```swift:UseAnchorViewController.swift
import UIKit

class UseAnchorViewController: UseIBViewController {
    
    @IBOutlet weak var middleFrame: UIView?
    @IBOutlet weak var bottomButton: UIButton?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.setUpConstraint()
    }
    
    func setUpConstraint() {
        
        textViewLabel.translatesAutoresizingMaskIntoConstraints = false
        middleFrame?.translatesAutoresizingMaskIntoConstraints = false
        bottomButton?.translatesAutoresizingMaskIntoConstraints = false
        
        if let view = self.view {
            
            // for TextViewLabel
            textViewLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor,
                                              constant: 0).isActive = true
            textViewLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor,
                                              constant: 0).isActive = true
            textViewLabel.topAnchor.constraint(equalTo: view.topAnchor,
                                              constant: 20).isActive = true
            textViewLabel.heightAnchor.constraint(equalToConstant: 50).isActive = true
            
            // for middleFrame
            middleFrame?.trailingAnchor.constraint(equalTo: view.trailingAnchor,
                                              constant: -25).isActive = true
            middleFrame?.leadingAnchor.constraint(equalTo: view.leadingAnchor,
                                              constant: 25).isActive = true
            middleFrame?.topAnchor.constraint(equalTo: textViewLabel.bottomAnchor,
                                              constant: 20).isActive = true
            
            // for bottomButton
            bottomButton?.trailingAnchor.constraint(equalTo: view.trailingAnchor,
                                              constant: -40).isActive = true
            bottomButton?.leadingAnchor.constraint(equalTo: view.leadingAnchor,
                                              constant: 40).isActive = true
            bottomButton?.topAnchor.constraint(equalTo: middleFrame!.bottomAnchor,
                                              constant: 8).isActive = true
            bottomButton?.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor,
                                              constant: -8).isActive = true
        }
        
    }
    
}
```

#### 5. GetWildButton

```GetWildButton.swift
import UIKit

class GetWildButton: UIButton {

    
    init() {
        super.init(frame: .zero)
    }

    required init?(coder aDecoder: NSCoder) {
        
        super.init(coder: aDecoder)
        
        self.setTitle("Get Wild!", for: .normal)
        self.titleLabel?.textAlignment = .center
        self.addTarget(self, action: #selector(self.getWild), for: .touchUpInside)

    }
    
    func getWild() {

        let alert = UIAlertController(title: nil, message: "Get wild and tough!", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        self.window?.rootViewController?.present(alert, animated: true, completion: nil)
        
    }
    
}
```

### まとめ

結局は「ケースバイケース」「適材適所」となると思うが、個人的には短くまとめる事ができ、さりげなくStandard ValueもサポートしているVFLの利用を推したい。
なお、それぞれの細かい解説については大胆に割愛するので、各自、コードと対話して欲しい（一旦丸投げするスタイル）。

### バージョン情報

Swift：3.1
Xcode：8.３.3

### ソースコード

https://github.com/rotors123/AboutAutoLayout.git