Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
iOSのレイアウトサイクルを知ろう
Search
Elvis Shi
May 24, 2018
Programming
3
1.9k
iOSのレイアウトサイクルを知ろう
iOS(UIKit)のレイアウトサイクルについてのお話
Elvis Shi
May 24, 2018
Tweet
Share
More Decks by Elvis Shi
See All by Elvis Shi
@Environment(\.keyPath)那么好我不允许你们不知道! / atEnvironment keyPath is so good and you should know it!
lovee
0
120
ゼロから始めるPreferenceの実装 / Let's implement Preferences from scratch
lovee
0
86
Kotlin エンジニアへ送る:Swift 案件に参加させられる日に備えて~似てるけど色々違う Swift の仕様 / from Kotlin to Swift
lovee
1
300
個人アプリを2年ぶりにアプデしたから褒めて / I just updated my personal app, praise me!
lovee
0
520
How did I build an Open-Source SwiftUI Toast Library
lovee
1
120
SwiftUIで使いやすいToastの作り方 / How to build a Toast system which is easy to use in SwiftUI
lovee
3
1k
SwiftUIで二重スクロール作ってみた / When I tried to make a dual-scroll-ish view in SwiftUI
lovee
1
320
Observation のあれこれ / A brief introduction about Observation
lovee
3
390
ChatGPT 時代の勉強 / Learning under ChatGPT era
lovee
27
8.8k
Other Decks in Programming
See All in Programming
実用的なGOCACHEPROG実装をするために / golang.tokyo #40
mazrean
1
280
開発チーム・開発組織の設計改善スキルの向上
masuda220
PRO
20
11k
意外と簡単!?フロントエンドでパスキー認証を実現する WebAuthn
teamlab
PRO
2
750
Putting The Genie in the Bottle - A Crash Course on running LLMs on Android
iurysza
0
140
さようなら Date。 ようこそTemporal! 3年間先行利用して得られた知見の共有
8beeeaaat
3
1.4k
Tool Catalog Agent for Bedrock AgentCore Gateway
licux
6
2.5k
2025 年のコーディングエージェントの現在地とエンジニアの仕事の変化について
azukiazusa1
24
12k
奥深くて厄介な「改行」と仲良くなる20分
oguemon
1
540
CloudflareのChat Agent Starter Kitで簡単!AIチャットボット構築
syumai
2
500
テストコードはもう書かない:JetBrains AI Assistantに委ねる非同期処理のテスト自動設計・生成
makun
0
300
AI Coding Agentのセキュリティリスク:PRの自己承認とメルカリの対策
s3h
0
230
Laravel Boost 超入門
fire_arlo
3
220
Featured
See All Featured
Designing for Performance
lara
610
69k
GitHub's CSS Performance
jonrohan
1032
460k
Become a Pro
speakerdeck
PRO
29
5.5k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
Designing for humans not robots
tammielis
253
25k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
33
2.4k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
8
520
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4k
A Modern Web Designer's Workflow
chriscoyier
696
190k
How to Ace a Technical Interview
jacobian
279
23k
We Have a Design System, Now What?
morganepeng
53
7.8k
Transcript
ϨΠΞταΠΫϧΛ Ζ͏ forαϙʔλʔζ$P-BCษڧձ K15؊
var employedBy = "YUMEMI Inc." var job = "iOS Developer"
var favoriteLanguage = "Swift" var twitter = "@lovee" var qiita = "lovee" var github = "el-hoshino" var additionalInfo = """ ࣗশ Auto Layout ઈରࡴ͢ϚϯͰ͢ """ class Me: Developable, Talkable { }
J04ʢ6*,JUʣͷϨΠΞταΠΫϧͲͷΑ͏ʹಈ͍ͯΔ͔ ͲͷλΠϛϯάͰϨΠΞτΛΈͳ͓͖͔͢ "VUP-BZPVUͰϨΠΞτΛΉ࣌ͱίʔυͰϨΠΞτ Ή࣌ʹͦΕͧΕԿΛؾΛ͚ͭΔ͖͔ /PU"VUP-BZPVUͷ͍ܰએʢΛ͍ͤͯͩ͘͞͞Xʣ ࠓ͢͜ͱ
͜ͷϨΠΞτ۩ମతʹͲͷΑ͏ʹΜͰ͍Δͷ͔ έʔεόΠέʔε͗͢ΔͷͰΧόʔ͢Δͷແཧ Ͱฉ͖͍ͨͷ͋Ε࣭ٙԠ࠙ձͷ࣌ʹରԠ͍ͨ͠ "VUP-BZPVUͷΈ ڵຯֶ͋ͬͯಘҙͳϑϨϯζੋඇҴݟ͞ΜͷൃදΛ IUUQTXXXZPVUVCFDPNXBUDI W5)0OTZ%,&:2UT ࠓ͞ͳ͍͜ͱ
ੲͷ iPhone ղ૾͕ 1 छྨ͔͠ͳ͘ɺ ը໘ϨΠΞτ͕ͱͯ؆୯ͩͬͨɻ
UIView ͷੜ࣌ʹ frame Λࢦఆ͠ɺ ϨΠΞτͷมԽߟ͑ͳͯ͘Α͔ͬͨɻ ը໘ճస͕ແޮͳΒ let view = UIView(frame:
CGRect(x: 0, ɹɹy: 0, ɹɹwidth: 320, ɹɹheight: 480)) Swiftɿ UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)]; Objective-Cɿ
్த͔Βը໘छྨ͕૿͑ɺ ϨΠΞτ͕໘ʹͳΓ࢝Ίͨɻ
ࠓछྨ͕ଟ͗ͯ͢ɺ ԿΛͲ͏͢Ε͍͍͔… ?
ϨΠΞτ αΠΫϧ
ϨΠΞτίετ͕ߴ͍ʢॏ͍ʣ ͳΔ͘ϨΠΞτΛඞཁͳ࣌ʹඞཁ࠷ݶ͚ͩߦ͍͍ͨ ҙਤ͠ͳ͍ॴͰ࠶ϨΠΞτΛىͤͨ͘͜͞ͳ͍ ϨΠΞτΛࢥ͏Ͳ͓Γʹ࣮ݱ͢Δͷʹඞཁ ϨΠΞτઃఆͯ͠Δ͚ͲɺαΠΫϧ͕ճΒͳ͔͍ͬͨͤ Ͱݟ͕ͨఆͱҧ͏͜ͱΛ͍͗ͨ ͳͥϨΠΞταΠΫϧ͕ॏཁ͔
UIViewController UIView viewDidDisappear() viewWillDisappear() layout runloop viewDidAppear() viewDidLayoutSubviews() layoutSubviews() viewWillLayoutSubviews()
viewWillAppear() viewDidLoad() loadView()
UIViewController UIView Needs layout? viewDidLayoutSubviews() layoutSubviews() updateViewConstraints() updateConstraints() yes no
Needs update constraint? viewWillLayoutSubviews() yes sleep no Needs layout? no yes Disappear? viewDidDisappear() viewWillDisappear() layout runloop viewDidAppear() viewDidLayoutSubviews() layoutSubviews() viewWillLayoutSubviews() viewWillAppear() viewDidLoad() loadView()
setNeedsLayout() Λݺͼग़ͨ͠ σόΠε͕ճసͨ͠ ࢠϏϡʔΛՃআΛͨ͠ ࣗͷϏϡʔ֊ͷ੍͕ߋ৽ͨ͠ ʢUIScrollView Ͱ͋ΔࢠϏϡʔʣ͕εΫϩʔϧͨ͠ ࣗͷࢠϏϡʔͷ frame
transform ͳͲ͕มߋ͞Εͨ ͳͲͳͲ… Ͳ͏ͬͯఆ͍ͯ͠Δͷ͔ʁ Needs layout?
setNeedsLayout() Λݺͼग़ͨ͠ σόΠε͕ճసͨ͠ ࢠϏϡʔΛՃআΛͨ͠ ࣗͷϏϡʔ֊ͷ੍͕ߋ৽ͨ͠ ʢUIScrollView Ͱ͋ΔࢠϏϡʔʣ͕εΫϩʔϧͨ͠ ࣗͷࢠϏϡʔͷ frame
transform ͳͲ͕มߋ͞Εͨ ͳͲͳͲ… Ͳ͏ͬͯఆ͍ͯ͠Δͷ͔ʁ Needs layout? ࣗͷ frame transform ඞͣʹมߋͯ͠Β͓͏ʂ ࣗͷ frame transform Λมߋ͢Ε ͷ layoutSubviews() ݺΕΔʂ
setNeedsLayout() Λݺͼग़ͨ͠ σόΠε͕ճసͨ͠ ࢠϏϡʔΛՃআΛͨ͠ ࣗͷϏϡʔ֊ͷ੍͕ߋ৽ͨ͠ ʢUIScrollView Ͱ͋ΔࢠϏϡʔʣ͕εΫϩʔϧͨ͠ ࣗͷࢠϏϡʔͷ frame
transform ͳͲ͕มߋ͞Εͨ ͳͲͳͲ… Ͳ͏ͬͯఆ͍ͯ͠Δͷ͔ʁ Needs layout? ࠶ϨΠΞτ setNeedsLayout ͷ ޙͰͳ͘ɺ࣍ͷϥϯϧʔϓ ϨΠΞτίετ͕ߴ͍ ͘͠ layoutIfNeeded ݺͼग़࣌͠
Ͳ͏ϨΠΞτ ͢Ε͍͍͔
جຊUIViewͷlayoutSubviewsΛΦʔόʔϥΠυ͠ɺͦ ͷதͰϨΠΞτ͢Ε͍͍ ϏϡʔͷframeϓϩύςΟʔɺ͕ཧ͖͢ ༰ʹԠͯ͡αΠζ͕มΘΔϏϡʔɺsizeThatFitsͰ ͳΜͱ͔ͳΔ label.frame.size = label.sizeThatFits(bounds.size) ඞཁʹԠͯ͡ΦʔόʔϥΠυՄ ࠶ϨΠΞτ͕ඞཁʹͳͬͨΒsetNeedsLayoutΛݺͿ
ϨΠΞτ͕ඞཁͱ͍͏ϑϥάཱ͕͚ͭͩͳͷͰɺҰͷ ॲཧʹෳճݺΜͰϨΠΞτޙͰҰճ͚ͩճΔ ίʔυʢ"VUP-BZPVUͳ͠ʣͰΉ߹
ίʔυʢ"VUP-BZPVUͳ͠ʣͰΉ߹ class ViewController: UIViewController { lazy var myView = View(frame:
UIScreen.main.bounds) override func loadView() { self.view = self.myView } override func viewDidLoad() { super.viewDidLoad() self.myView.button.addTarget(self, action: #selector(self.switchButtonSize), for: .touchUpInside) } @objc func switchButtonSize() { self.myView.showsLargeButton = !self.myView.showsLargeButton self.view.setNeedsLayout() } } class View: UIView { let button = UIButton() var showsLargeButton = false override func layoutSubviews() { if showsLargeButton { self.button.frame = self.bounds } else { self.button.frame.size = .init(width: 100, height: 100) self.button.frame.origin = .zero } } }
ίʔυʢ"VUP-BZPVUͳ͠ʣͰΉ߹ class ViewController: UIViewController { lazy var myView = View(frame:
UIScreen.main.bounds) override func loadView() { self.view = self.myView } override func viewDidLoad() { super.viewDidLoad() self.myView.button.addTarget(self, action: #selector(self.switchButtonSize), for: .touchUpInside) } @objc func switchButtonSize() { self.myView.showsLargeButton = !self.myView.showsLargeButton self.view.setNeedsLayout() } } class View: UIView { let button = UIButton() var showsLargeButton = false override func layoutSubviews() { if showsLargeButton { self.button.frame = self.bounds } else { self.button.frame.size = .init(width: 100, height: 100) self.button.frame.origin = .zero } } }
"VUP-BZPVUͰΉ߹ class ViewController: UIViewController { lazy var button: UIButton =
{ let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.addTarget(self, action: #selector(self.switchButtonSize), for: .touchUpInside) return button }() var showsLargeButton = false lazy var top = button.topAnchor.constraint(equalTo: view.topAnchor) lazy var left = button.leftAnchor.constraint(equalTo: view.leftAnchor) lazy var bottom = button.bottomAnchor.constraint(equalTo: view.bottomAnchor) lazy var right = button.rightAnchor.constraint(equalTo: view.rightAnchor) lazy var width = button.widthAnchor.constraint(equalToConstant: 100) lazy var height = button.heightAnchor.constraint(equalToConstant: 100) override func viewDidLoad() { super.viewDidLoad() self.view.addSubview(button) self.view.setNeedsUpdateConstraints() } override func updateViewConstraints() { self.updateButtonConstraints() super.updateViewConstraints() } @objc func switchButtonSize() { self.showsLargeButton = !self.showsLargeButton self.updateButtonConstraints() } func updateButtonConstraints() { top.isActive = true left.isActive = true if showsLargeButton { bottom.isActive = true right.isActive = true width.isActive = false height.isActive = false } else { bottom.isActive = false right.isActive = false width.isActive = true height.isActive = true } } }
"VUP-BZPVUͰΉ߹ @objc func switchButtonSize() { self.showsLargeButton = !self.showsLargeButton self.updateButtonConstraints() }
func updateButtonConstraints() { top.isActive = true left.isActive = true if showsLargeButton { bottom.isActive = true right.isActive = true width.isActive = false height.isActive = false } else { bottom.isActive = false right.isActive = false width.isActive = true height.isActive = true } }
ϨΠΞτʢʹݶΒͣ6*ܥશൠʣϝΠϯεϨουͰʂ ϨΠΞταΠΫϧͰϨΠΞτҾ͖ى͜͞ͳ͍ setNeedsLayoutsetNeedsUpdateConstraintsΛ ݺͳ͍ ϨΠΞταΠΫϧͷݺͼग़͠ΛͳΔ͘࠷খݶʹ ࢠϏϡʔͷՃͳΔ͘viewDidLoad͔ viewWillLayoutSubviewsʹ layoutIfNeededΛΉΈʹݺͳ͍ ຊʹඞཁͳ͚࣌ͩݺͿ ҙʢڞ௨ʣ
ΞχϝʔγϣϯϒϩοΫͱ͔
sizeThatFitsΛ͏·͘׆༻͠ɺϨΠΞτॲཧΛ࠷খݶʹ ͑Δ sizeThatFitsͷΦʔόʔϥΠυ͢Δ࣌ʹɺதͰͳΔ͘ ϨΠΞτॲཧͤ͞ͳ͍ ࣗࣗͰࣗࣗͷframeΛมߋ͠ͳ͍ʂ ҙʢίʔυʣ
ϏϡʔؔΛ͏·͘׆༻੍ͯ͠ΛΉ ؔੑΛਖ਼͘͠ઃఆͰ͖ͯΕsetNeedsLayout(); layoutIfNeeded()ͷίϯϘཁΒͳ͍ͣʂ ੍ͷߋ৽සΛͳΔ͘࠷খݶʹ ՄೳͳݶΓ੍Λੜʗআ͢ΔͷͰͳ͘ɺಉ੍͡ Λྲྀ༻͠constantsΛมߋ͢Δ͔ɺ੍ΛisActiveͰ Γସ͑Δ isActiveͰΓସ͑Δ߹੍ͷอ࣋ʹҙ ίϯςϯπʹԠͨ͡αΠζઃఆintrinsicContentSize ΛΦʔόʔϥΠυ
ҙʢ"VUP-BZPVUʣ
એ
/PU"VUP-BZPVUઈࢍެ։த IUUQTHJUIVCDPNFMIPTIJOP/PU"VUP-BZPVU
ίʔυʢ/PU"VUP-BZPVUʣͰΉ߹ class ViewController: UIViewController { lazy var myView = View(frame:
UIScreen.main.bounds) override func loadView() { self.view = self.myView } override func viewDidLoad() { super.viewDidLoad() self.myView.button.addTarget(self, action: #selector(self.switchButtonSize), for: .touchUpInside) } @objc func switchButtonSize() { self.myView.showsLargeButton = !self.myView.showsLargeButton self.view.setNeedsLayout() } } class View: UIView { let button = UIButton() var showsLargeButton = false override func layoutSubviews() { if showsLargeButton { self.nal.layout(button, by: { $0.stickOnParent() }) } else { self.nal.layout(button, by: { $0 .setTopLeft(by: { $0.topLeft }) .setSize(to: .init(width: 100, height: 100)) }) } } }
J04ΞϓϦઃܭύλʔϯೖࣥචܾఆ IUUQTQFBLTDDJ04@BSDIJUFDUVSF
גࣜձࣾΏΊΈɺΤϯδχΞืूத IUUQTXXXZVNFNJDPKQKBQBHFSFDSVJU