Objective-C
iPhone
iOS
AutoLayout
Swift

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

More than 1 year has passed since last update.

まず、本記事はもともと

「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


ソースコード

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