Contents 非表示
はじめに
SwiftUIがかなりメジャーになってきた昨今ですが、現場ではまだUIKitをゴリゴリ使う日々。
基本的にUI構築は、StoryboardとXIBを使ってやっている、そして一部にNSLayoutConstraitとかSnapkitという感じ。
この前のiOSDC2025に参加して、いろんな企業の開発の裏側を聞いているとやはりまだUIKitも現役で稼働してるところも多く、そして何よりUI構築もほぼコードベースで管理しているという企業もちらほらあった。
なので今一度GUIベースの構築だけじゃなくてコードベースでのUIKit実装も学び直してみようと思ったというきっかけがこの記事になります。
対象読者:
- UIKit開発してて、普段はStoryboardやXib使ってる
- Auto LayoutはGUIで組む派。コードはあんまり触ってない
- SnapKitって名前は聞いたことあるけど、触ったことはない or 軽く触っただけ
- UIKitなのにUIをコードベース実装、、、?って思ってる人
つまりUIKit初心者 or 普段GUIベースだけどコードベースも知っておくかって人が対象。
最終的に得られること:
- SnapKitの基本と使いどころが完全に理解できてる
- コードでUI組む=難しい」の感覚は消える (はず)
- ここはGUI、ここはコードベースにするかって柔軟に実装目標を考えられるようになる
それでは一個ずつ復習していきましょう。
UIKit開発のスタイル比較
UIKitで画面を作成するとなった時に、考えられるのは以下の2つ。
GUIベース(Storyboard / Xib)or コードベース(NSLayoutConstraint / SnapKit)
良し悪しというよりかは、両方の特性を理解してその状況に応じてどっちを使うか柔軟に考えられるようになれればOK。
GUIベース(Storyboard / Xib)
メリット:
- 視覚的に作れる:UIを直感的に配置できる。初心者でもとっつきやすい。
- レイアウト確認が早い:シミュレーターで確認せずとも、Xcode上でだいたいの形が見える。
- 学習コストが低い:Auto Layoutの基礎がわかればすぐ使える。
デメリット:
- Gitコンフリクトが地獄:Storyboards/XibsはXML形式だから、複数人が編集すると高確率でコンフリクト。(XIBは割と控えめな気がする)
- 保守しにくい:ファイルが分断されてて、どの画面がどのコードと紐づいてるか分かりづらい。
- 動的なレイアウト変更が面倒:サイズ変更やアニメーションに弱い。結局コード書く羽目になる。
“あれ、Storyboardコンフリクトしてね?”, “誰かここConstraintいじった?”とかになりがちな面もある。
つまり手軽ではあるが、大規模 & 複数人開発では破綻する可能性も秘めているということを理解しておくとGOOD。
コードベース(NSLayoutConstraint / SnapKit)
メリット:
- 変更・保守がしやすい:コードだから、Git管理も楽だし誰が何を変えたかも明確。
- 共通View・部品の再利用がしやすい:Viewごとにロジックを分けて、綺麗に管理できる。
- 動的レイアウトが得意:画面サイズに応じたConstraintの切り替え、アニメーションが簡単。
デメリット:
- 学習コスト高め(特にNSLayoutConstraint):Anchorを1個1個書くのがだるい、読みにくい。
- 初見では見た目がわかりにくい:GUIみたいに「見たままレイアウト」できないので想像力必要。
- ⇧に関してはUIKitでもPreview使えるようになってるのでそこまで問題ではなくわなっている
学習コスト高いとは言われつつ、AutoLayoutの概念をある程度理解していればそこまで苦労せずに慣れるものなので、そこまで恐れずにまずは色々と試してみるのがGOODだと感じる。
※ UIKItでのPreview実装方法は以下を参考に

NSLayoutConstraint vs SnapKit の違い
どちらもコードベースの実装、だけど色々内部の特徴は違ってくる。
NSLayoutConstraint = Apple純正 / SnapKit = サードパーティ製と理解しておけばOK、その上で以下の特徴。
項目 | NSLayoutConstraint | SnapKit |
---|---|---|
書きやすさ | 冗長 | スッキリ |
学習コスト | 高め | 低め |
SwiftUI的な書き味 | 遠い | 近い |
公式サポート | Apple公式 | サードパーティ(でも安定) |
アニメーション対応 | できるけど面倒 | .updateConstraints で簡単 |
SnapKitは、「GUIより柔軟で、ネイティブより書きやすい」っていう、ちょうどいいポジション。
結論:GUIかコードかじゃなくて「どう使い分けるか」
- 画面が複雑でデザインの変更が多い → コードベース(SnapKit)一択
- シンプルな画面で早く形にしたい → StoryboardでもOK
- チーム開発でのメンテを考える → コードベースが圧倒的に楽
- レイアウトの細かい制御が必要 → SnapKit最強
次は実際に同じ画面をStoryboard, NSLayoutConstrait, Snapkitでそれぞれ実装して肌感覚でどう違うかみていきましょう。
同じログインUIを3パターンで作ってみる
一般的なログイン画面をそれぞれのパターンで作って、どんな感じか見てみるというチャプター。
レイアウトパターン:
- 上から順に縦に並べる
- 水平は両端40ptマージン
- 各要素の縦間隔は40pt
- 高さ:TextField = 44、Button = 48
全体はsafeAreaLayoutGuide.top
から80pt下に開始 - 画面色はLightGray
これで作っていきましょう。
Storyboard

import UIKit
final class LoginViewController: UIViewController {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var idTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var loginButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
}
.storyboard
ファイルに、コンポーネントをペタペタと貼り付けて、AutoLayoutを設定するのが基本。
ViewController
との接続は@IBOutlet
、これが正しく接続されていないとスタイルだったりアクションが正常に処理されないという注意点がある。
特徴:
- 見た目で調整しやすいけど、コード量よりもクリック作業多め
- たまにクッソ重たくなる (個人的な恨み)
- レイアウト崩れたとき原因追いにくい
- コンフリクト地獄発生しやすい
NSConstraint
final class ViewController: UIViewController {
private let titleLabel: UILabel = {
let label = UILabel()
label.text = "ログイン"
label.textColor = .black
label.font = .systemFont(ofSize: 24, weight: .bold)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let idTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "ID"
textField.borderStyle = .roundedRect
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
private let passwordTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "パスワード"
textField.isSecureTextEntry = true
textField.borderStyle = .roundedRect
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
private let loginButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("ログイン", for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 20, weight: .bold)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .systemBlue
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .lightGray
setupConstraints()
}
private func setupConstraints() {
[titleLabel, idTextField, passwordTextField, loginButton].forEach {
view.addSubview($0)
}
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 80),
titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40),
titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40),
idTextField.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 40),
idTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40),
idTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40),
idTextField.heightAnchor.constraint(equalToConstant: 44),
passwordTextField.topAnchor.constraint(equalTo: idTextField.bottomAnchor, constant: 40),
passwordTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40),
passwordTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40),
passwordTextField.heightAnchor.constraint(equalToConstant: 44),
loginButton.topAnchor.constraint(equalTo: passwordTextField.bottomAnchor, constant: 40),
loginButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40),
loginButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40),
loginButton.heightAnchor.constraint(equalToConstant: 48)
])
}
}
比較なので、Constraint設定もベタ書きにしていますが、左右のConstraintは共通なら、passwordTextField.leadingAnchor.constraint(equalTo: idTextField.leadingAnchor),
とかにも出来ます、デザイン変更があってもidTextFieldのConstraintを変えるだけで済むので本番はこちらを採用するとGOOD。
特徴:
- 完全コード。自由度高いけど、書く量多い
translatesAutoresizingMaskIntoConstraints = false
を毎回書くのが地味に面倒- 保守性はStoryboardより断然マシ
SnapKit
final class ViewController: UIViewController {
private let titleLabel: UILabel = {
let label = UILabel()
label.text = "ログイン"
label.textColor = .black
label.font = .systemFont(ofSize: 24, weight: .bold)
label.textAlignment = .center
return label
}()
private let idTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "ID"
textField.borderStyle = .roundedRect
return textField
}()
private let passwordTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "パスワード"
textField.isSecureTextEntry = true
textField.borderStyle = .roundedRect
return textField
}()
private let loginButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("ログイン", for: .normal)
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 20, weight: .bold)
button.backgroundColor = .systemBlue
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .lightGray
setupConstraints()
}
private func setupConstraints() {
[titleLabel, idTextField, passwordTextField, loginButton].forEach {
view.addSubview($0)
}
titleLabel.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(80)
make.leading.trailing.equalToSuperview().inset(40)
}
idTextField.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(40)
make.leading.trailing.equalToSuperview().inset(40)
make.height.equalTo(44)
}
passwordTextField.snp.makeConstraints { make in
make.top.equalTo(idTextField.snp.bottom).offset(40)
make.leading.trailing.equalToSuperview().inset(40)
make.height.equalTo(44)
}
loginButton.snp.makeConstraints { make in
make.top.equalTo(passwordTextField.snp.bottom).offset(40)
make.leading.trailing.equalToSuperview().inset(40)
make.height.equalTo(48)
}
}
}
特徴:
- スッキリしていて、一目で構造が読める
translatesAutoresizingMaskIntoConstraints
不要inset
が便利(マイナス考えなくてOK)make.leading.trailing.equalToSuperview().inset(40)
のように、まとめて書ける
比較まとめ
比較項目 | Storyboard | NSLayoutConstraint | SnapKit |
---|---|---|---|
視覚的に作れる | ◎ | ✖️ | ✖️ |
書きやすさ | △(GUI操作) | ✖️(冗長) | ◎(宣言的) |
保守性 | ✖️(複雑化しやすい) | ◯ | ◎ |
Git管理のしやすさ | ✖️ | ◎ | ◎ |
動的レイアウト対応 | △ | ◎ | ◎ |
学習コスト | 低 | 中 | 低〜中 |
個人的に今後コードベースでUIKItやっていきたいとなったのであれば、基本はSnapkitを優先で使う感じになりそうかなと感じました。
せっかくなので最後にSnapkitの便利な特徴をまとめて頭の片隅に入れておきましょう。
SnapKitの便利セット
基本的な概念・使い方
1. snp.makeConstraints
一番基本のやつ。制約を新規で設定する。
view.snp.makeConstraints { make in
make.top.equalToSuperview().offset(16)
make.leading.trailing.equalToSuperview().inset(24)
make.height.equalTo(50)
}
2. equalTo
の使い方
equalToSuperview()
→ superview に対してequalTo(otherView.snp.xxx)
→ 他のViewのAnchorに対してoffset(x)
/inset(x)
→ 距離の調整(左右で符号意識不要)
3. updateConstraints
既存の制約を動的に変更したいとき
myView.snp.updateConstraints { make in
make.height.equalTo(100)
}
4. remakeConstraints
全ての制約を一旦リセットして再設定したいとき
myView.snp.remakeConstraints { make in
make.center.equalToSuperview()
make.size.equalTo(200)
}
便利Tips・パターン集
.inset()
vs .offset()
属性 | .inset(16) | .offset(16) |
---|---|---|
使いどころ | 左右・上下の「内側余白」指定 | Anchorからの「相対距離」指定 |
使用例 | make.leading.trailing.equalToSuperview().inset(20) | make.top.equalTo(label.snp.bottom).offset(16) |
基本は inset
、relativeなときだけoffset
使うって覚えておけばOK。
まとめて書けるやつ
top, bottom, leading, trailing を一括設定。
make.edges.equalToSuperview().inset(16)
レスポンシブなレイアウト
画面サイズに応じてレイアウトできる
make.width.equalToSuperview().multipliedBy(0.8)
サイズ固定
make.size.equalTo(CGSize(width: 100, height: 44))
UIStackViewとの組み合わせ
SnapKit + StackView は名コンビ、使えるところには積極的に使うとGOOD
let stack = UIStackView(arrangedSubviews: [label, textField, button])
stack.axis = .vertical
stack.spacing = 24
view.addSubview(stack)
stack.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview().inset(24)
make.centerY.equalToSuperview()
}
まとめ:SnapKitはなんだかんだ便利
スキル | できるようになると強い |
---|---|
.makeConstraints の基本 | UI組めるようになる |
.update , .remake | アニメーションや状態変化に対応 |
.inset と .offset の違い | レイアウトの意図が伝わるコードが書ける |
StackViewとの合わせ技 | レスポンシブ&柔軟なUIが構築できる |
おわり
なんだかんだ、SnapkitになれるとUIKitもコードベース実装が保守性面でもチーム開発面でもかなり便利って結論。
GUI開発から抜け出したい人、両方学び直したい人にとって、SnapKitは間違いなくGOODな選択。
ぜひこの便利セット持って、次のUI画面から色々と試してみてください。。