LoginSignup
4
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-07-18

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

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

  1. InterfaceBuilder(IB)を利用してStoryBoard上で設定する
  2. Visual Format Language(VFL)を利用してコードで記述する
  3. NSLayoutConstraint(item: 〜)を利用してコードで記述する
  4. NSLayoutAnchorを利用してコードで記述する (17/7/19追加。@rika-tawashiさんに感謝!)
  5. 他の開発者に丸投げする

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

サンプルソースについて

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

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

実行結果

AboutAutoLayoutMovie.gif

記述例

StoryBoard上の全体構成

Storyboard2.png

1. UseIBViewController

UseIB2.png

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

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

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

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.3

ソースコード

4
3
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3