コードでAutoLayoutを実装するには?
実装するためには下記2つの知識が必要そうだ。
- NSLayoutConstraint
- NSLayoutAnchor
NSLayoutConstraintとは?
公式には下記のように記載があった。
The relationship between two user interface objects that must be satisfied by the constraint-based layout system.
NSLayoutConstraintを使用すると、オブジェクト間の配置やサイズを設定することができる。
使い方については、下記記事がとてもわかりやすかった!
特に"制約"と"制約を考える上で"はNSLayoutConstraintを理解しやすくしてくれる!
NSLayoutAnchorとは?
公式には下記のように記載があった。
Use these constraints to programatically define your layout using Auto Layout. Instead of creating NSLayoutConstraint objects directly, start with a UIView, NSView, or UILayoutGuide object you wish to constrain, and select one of that object’s anchor properties. These properties correspond to the main NSLayoutConstraint.Attribute values used in Auto Layout, and provide an appropriate NSLayoutAnchor subclass for creating constraints to that attribute. Use the anchor’s methods to construct your constraint.
NSLayoutAnchorを使用することで、より簡潔にオブジェクト間の制約を作成することができる。
使い方については、下記記事がとてもわかりやすかった!
"NSLayoutAnchorの使い方、制約の作り方"の部分でNSLayoutConstraintと比較して書かれているので違いがわかりやすい!
NSLayoutConstraintとNSLayoutAnchorの違いとは?
ChatGPTに違いを聞いてみた際の返答がとてもわかりやすかったので記録しておく!
NSLayoutConstraintとNSLayoutAnchorは、どちらもiOSやMacアプリケーション開発において制約ベースのレイアウトを実現するためのツールですが、使用方法や表現方法においていくつかの違いがあります。
NSLayoutConstraintは、制約を作成するためのクラスです。制約を作成する際には、NSLayoutConstraintクラスのインスタンスを生成し、制約を指定します。制約は、ビューやコンポーネントの属性(位置、サイズなど)を他のビューやコンポーネントに関連付けることで定義されます。
一方、NSLayoutAnchorは、制約を作成するための便利なメソッドを提供するクラスです。NSLayoutAnchorを使用すると、より簡潔かつ直感的なコードで制約を指定することができます。NSLayoutAnchorは、ビューやコンポーネントの特定の属性(たとえば、topAnchor、leadingAnchor、widthAnchorなど)を表すアンカープロパティを提供します。これらのアンカープロパティを使用して、ビュー間の関係を記述する制約を簡単に作成できます。
NSLayoutConstraintとNSLayoutAnchorは、同じ目的を達成するための異なるアプローチです。NSLayoutConstraintはより柔軟で広範な制約を作成するために使用されますが、コードがやや冗長になる場合があります。一方、NSLayoutAnchorは、より簡潔で読みやすいコードを実現するために使用されますが、いくつかの制約の種類に制約されることがあります。
どちらを使用するかは、個々の開発者やプロジェクトの好みや要件によって異なります。NSLayoutConstraintはより広範な制約を作成するための柔軟性がありますが、NSLayoutAnchorはコードの可読性を向上させることができます。
実際にコードを書いていく
使い方を知れたところで実際に使って慣らしていく!
準備
新規プロジェクトを作成した場合、Storyboardが初期画面になっているので、作成したViewControllerを初期画面に設定する必要がある。設定手順は下記になる。
1. Storyboardを削除する
2. Main Interfaceを空白にする
3. Info.plistのStoryboard Nameの項目を削除する
4. SceneDelegate.swiftを編集する
手順1から手順4については、こちらを参考にする。
同じ題名の箇所をそのまま追っていけば設定が完了する。写真もついているのでわかりやすい!
手順4について、ViewControllerを新規作成した場合は、下記のコードを適宜変更する。
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
// 新規作成の場合
window.rootViewController = SampleViewControlle()
self.window = window
window.makeKeyAndVisible()
}
UIWindowScene、UIWindow、Sceneとは?
何をしているのかが知りたい場合は、こちらを参考にすると良い!
難しく感じてしまうけど、知っておいた方が良い情報がたくさん詰まっている!
5. ViewControllerのViewに背景色をつける
ViewControllerを継承すると、Viewの背景色がUIColor.clear
に設定されているので、起動時に画面が黒くなってしまう。(xibファイルを使用している場合はUIColor.systemBackground
に設定してくれてたみたい。)
それを回避するためにView.backgroundColor
を下記のように設定する。
// rootViewControllerに設定したクラス
class SampleViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// ここで背景色を設定
self.view.backgroundColor = UIColor.systemBackground
}
}
UIViewControllerを継承すると、なぜUIViewがデフォルトで設定されるのかについて知りたい場合は、こちらを参考にすると良い!特に図1-1 ビュー コントローラーとそのビューの関係
をみるとイメージがつきやすい!
6. ビルドしてViewControllerが表示されているか確認する
実際にビルドして確認してみる。指定した背景色で画面が表示されていれば初期画面の変更が成功している。
実践1
画面上部に引っ付いている、横幅いっぱいのredView(参考はこちら)
import UIKit
class Sample1ViewController: UIViewController {
private var redView: UIView = UIView()
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
setUp()
}
// MARK: - Action
private func setUp() {
self.view.backgroundColor = UIColor.systemBackground
setUpRedView()
}
private func setUpRedView() {
// ①
redView.translatesAutoresizingMaskIntoConstraints = false
redView.backgroundColor = UIColor.red
// ②
self.view.addSubview(redView)
self.view.addConstraints([
// self.viewの上から0pxの位置に配置
NSLayoutConstraint(item: redView,
attribute: .top,
relatedBy: .equal,
toItem: self.view,
attribute: .top,
multiplier: 1,
constant: 0
),
// self.viewの横幅いっぱいにする
NSLayoutConstraint(item: redView,
attribute: .width,
relatedBy: .equal,
toItem: self.view,
attribute: .width,
multiplier: 1,
constant: 0
),
// self.viewのレイアウトに関わらず高さは64px
NSLayoutConstraint(item: redView,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .height,
multiplier: 1,
constant: 64
)
])
}
}
制約をつける処理については、説明箇所で紹介した記事を参考にしてもらえれば問題ないと思う。他の大事な箇所について番号をつけたので、順番に説明していく。
①は、AutoLayoutを設定する場合に必須となる。下記は公式に記載されている内容である。
translatesAutoresizingMaskIntoConstraints
If you want to use Auto Layout to dynamically calculate the size and position of your view, you must set this property to false, and then provide a non ambiguous, nonconflicting set of constraints for the view.
プログラムで作成したViewのtranslatesAutoresizingMaskIntoConstraints
プロパティはtrue
に設定される。 Interface BuilderでViewを追加すると、システムはこのプロパティを自動的にfalse
に設定する。つまり、今回はコードで作成したViewになるので、translatesAutoresizingMaskIntoConstraints
はtrue
の状態である。true
のままAutoLayoutを設定すると、デフォルトで設定されるAutoResizingMask
の
制約とコンフリクトしてしまい、レイアウトに関して問題が発生する可能性がある。そのため、false
に設定し、AutoResizingMask
の
制約を無効化する必要がある。
AutoresizingMask
については、公式に下記のように記載されている。
autoresizingMask
When a view’s bounds change, that view automatically resizes its subviews according to each subview’s autoresizing mask. You specify the value of this mask by combining the constants described in UIView.AutoresizingMask using the C bitwise OR operator. Combining these constants lets you specify which dimensions of the view should grow or shrink relative to the superview. The default value of this property is none, which indicates that the view should not be resized at all.
For example, suppose this property includes the flexibleWidth and flexibleRightMargin constants but does not include the flexibleLeftMargin constant, thus indicating that the width of the view’s left margin is fixed but that the view’s width and right margin may change. Thus, the view appears anchored to the left side of its superview while both the view width and the gap to the right of the view increase.
つまり、コードを下記のように記述することによって、親Viewに合わせてViewの幅や高さを変更することができるということ。下記のコードでは、高さと幅を親Viewに合わせて変更できるようにしている。
view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
もっと詳しく動きを知りたい場合は、下記記事を読んでみると良い!
https://www.advancedswift.com/autolayout-vs-autoresizing-masks/
https://developer.apple.com/documentation/uikit/uiview/1622559-autoresizingmask
②は、Viewに対してredViewを追加している。必ずAutoLayoutの制約をつける前に、この処理を行うこと!
前後が逆になってしまうとエラーが出てしまうので注意!
実践2
redViewの上から10px左から10pxの位置にあり、縦横44pxのblueButton(参考はこちら)
class Sample1ViewController: UIViewController {
// 省略
private var greenView: UIView = UIView()
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
setUp()
}
// MARK: - Action
private func setUp() {
// 省略
setUpBlueButton()
}
private func setUpBlueButton() {
blueButon.translatesAutoresizingMaskIntoConstraints = false
blueButon.backgroundColor = UIColor.blue
redView.addSubview(blueButon)
redView.addConstraints([
// 上から10px
NSLayoutConstraint(item: blueButon,
attribute: .top,
relatedBy: .equal,
toItem: redView,
attribute: .top,
multiplier: 1,
constant: 10
),
// 左から10px
NSLayoutConstraint(item: blueButon,
attribute: .leading,
relatedBy: .equal,
toItem: redView,
attribute: .leading,
multiplier: 1,
constant: 10
),
// 幅44px
NSLayoutConstraint(item: blueButon,
attribute: .width,
relatedBy: .equal,
toItem: nil,
attribute: .width,
multiplier: 1.0,
constant: 44
),
// 高さ44px
NSLayoutConstraint(item: blueButon,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .height,
multiplier: 1.0,
constant: 44)
])
}
}
実践1までの内容がわかっていれば問題なくできる!
実践3
redViewの中央にある、横幅がredViewの横幅によって動的に変化し、高さが44pxのgreenView(参考はこちら)
redView.width:320px、greenView.width:100pxの比率を保ような横幅
class Sample1ViewController: UIViewController {
// 省略
private var greenView: UIView = UIView()
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
setUp()
}
// MARK: - Action
private func setUp() {
// 省略
setUpGreenView()
}
private func setUpGreenView() {
greenView.translatesAutoresizingMaskIntoConstraints = false
greenView.backgroundColor = UIColor.green
redView.addSubview(greenView)
redView.addConstraints([
// CenterXが同じ
NSLayoutConstraint(item: greenView,
attribute: .centerX,
relatedBy: .equal,
toItem: redView,
attribute: .centerX,
multiplier: 1,
constant: 0
),
// CenterYが同じ
NSLayoutConstraint(item: greenView,
attribute: .centerY,
relatedBy: .equal,
toItem: redView,
attribute: .centerY,
multiplier: 1,
constant: 0
),
// redView.width:320のときgreenView:100の比率
NSLayoutConstraint(item: greenView,
attribute: .width,
relatedBy: .equal,
toItem: redView,
attribute: .width,
multiplier: 100.0 / 320.0,
constant: 0
),
// 高さ44px
NSLayoutConstraint(item: greenView,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .height,
multiplier: 1,
constant: 44
)
])
}
}
実践1までの内容がわかっていれば問題なくできる!
実践4
import UIKit
/// NSLayoutAnchorを学ぶためのクラス
class Sample2ViewController: UIViewController {
private var greenView = UIView()
private var baseStackView = UIStackView()
private var buttonStackView = UIStackView()
private var blueButton = UIButton()
private var redButton = UIButton()
private var purpleLabel = UILabel()
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
setUp()
}
// MARK: - Action
private func setUp() {
self.view.backgroundColor = UIColor.systemBackground
setUpBaseStackView()
setUpButtonStackView()
setUpBlueButton()
setUpRedButton()
setUpGreenView()
setUpPurpleLabel()
}
private func setUpBaseStackView() {
baseStackView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(baseStackView)
// ①
baseStackView.addArrangedSubview(greenView)
baseStackView.addArrangedSubview(buttonStackView)
// ②
baseStackView.spacing = 30
baseStackView.distribution = .fill
baseStackView.alignment = .fill
baseStackView.axis = .vertical
baseStackView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100).isActive = true
baseStackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20).isActive = true
baseStackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20).isActive = true
baseStackView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -100).isActive = true
}
private func setUpButtonStackView() {
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
buttonStackView.addArrangedSubview(blueButton)
buttonStackView.addArrangedSubview(redButton)
buttonStackView.spacing = 10
buttonStackView.distribution = .fillEqually
buttonStackView.alignment = .fill
buttonStackView.heightAnchor.constraint(equalToConstant: 44).isActive = true
buttonStackView.leadingAnchor.constraint(equalTo: baseStackView.leadingAnchor).isActive = true
buttonStackView.trailingAnchor.constraint(equalTo: baseStackView.trailingAnchor).isActive = true
buttonStackView.bottomAnchor.constraint(equalTo: baseStackView.bottomAnchor).isActive = true
}
private func setUpBlueButton() {
blueButton.translatesAutoresizingMaskIntoConstraints = false
blueButton.backgroundColor = UIColor.blue
blueButton.tintColor = UIColor.white
blueButton.setTitle("BlueButton", for: .normal)
}
private func setUpRedButton() {
redButton.translatesAutoresizingMaskIntoConstraints = false
redButton.backgroundColor = UIColor.red
redButton.tintColor = UIColor.white
redButton.setTitle("RedButton", for: .normal)
}
private func setUpGreenView() {
greenView.translatesAutoresizingMaskIntoConstraints = false
greenView.backgroundColor = UIColor.green
greenView.topAnchor.constraint(equalTo: baseStackView.topAnchor).isActive = true
greenView.leadingAnchor.constraint(equalTo: baseStackView.leadingAnchor).isActive = true
greenView.trailingAnchor.constraint(equalTo: baseStackView.trailingAnchor).isActive = true
}
private func setUpPurpleLabel() {
purpleLabel.translatesAutoresizingMaskIntoConstraints = false
purpleLabel.backgroundColor = UIColor.purple
purpleLabel.text = "PurpleLable"
purpleLabel.textAlignment = .center
purpleLabel.textColor = UIColor.white
greenView.addSubview(purpleLabel)
purpleLabel.topAnchor.constraint(equalTo: greenView.topAnchor, constant: 20).isActive = true
purpleLabel.leadingAnchor.constraint(equalTo: greenView.leadingAnchor, constant: 100).isActive = true
purpleLabel.trailingAnchor.constraint(equalTo: greenView.trailingAnchor, constant: -100).isActive = true
purpleLabel.heightAnchor.constraint(equalToConstant: 30).isActive = true
}
}
①について、StackViewにViewを追加する場合は、addSubview
ではなく、addArrangedSubview
で追加をする。②について、StackViewの詳細な設定をここで行なっている。垂直にしたい場合や、大きさを同じにしたいなどの希望を叶えてくれる!
まとめ
実践はまだやっていこうと思うので、完成したら都度追加していきます!
ここまでご覧いただきありがとうございました!
また、優れた知識と経験を共有してくださる皆様に感謝です!本当にありがとうございました!