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
260
ゼロから始めるPreferenceの実装 / Let's implement Preferences from scratch
lovee
0
110
Kotlin エンジニアへ送る:Swift 案件に参加させられる日に備えて~似てるけど色々違う Swift の仕様 / from Kotlin to Swift
lovee
1
340
個人アプリを2年ぶりにアプデしたから褒めて / I just updated my personal app, praise me!
lovee
0
600
How did I build an Open-Source SwiftUI Toast Library
lovee
1
150
SwiftUIで使いやすいToastの作り方 / How to build a Toast system which is easy to use in SwiftUI
lovee
3
1.1k
SwiftUIで二重スクロール作ってみた / When I tried to make a dual-scroll-ish view in SwiftUI
lovee
1
350
Observation のあれこれ / A brief introduction about Observation
lovee
3
410
ChatGPT 時代の勉強 / Learning under ChatGPT era
lovee
27
8.9k
Other Decks in Programming
See All in Programming
Combinatorial Interview Problems with Backtracking Solutions - From Imperative Procedural Programming to Declarative Functional Programming - Part 2
philipschwarz
PRO
0
130
GoLab2025 Recap
kuro_kurorrr
0
790
大規模Cloud Native環境におけるFalcoの運用
owlinux1000
0
240
Unicodeどうしてる? PHPから見たUnicode対応と他言語での対応についてのお伺い
youkidearitai
PRO
0
160
Cap'n Webについて
yusukebe
0
160
Python札幌 LT資料
t3tra
7
1.1k
QAフローを最適化し、品質水準を満たしながらリリースまでの期間を最短化する #RSGT2026
shibayu36
0
1.1k
ローカルLLMを⽤いてコード補完を⾏う VSCode拡張機能を作ってみた
nearme_tech
PRO
0
230
Navigation 3: 적응형 UI를 위한 앱 탐색
fornewid
1
510
Kotlin Multiplatform Meetup - Compose Multiplatform 외부 의존성 아키텍처 설계부터 운영까지
wisemuji
0
150
愛される翻訳の秘訣
kishikawakatsumi
3
370
Giselleで作るAI QAアシスタント 〜 Pull Requestレビューに継続的QAを
codenote
0
330
Featured
See All Featured
The B2B funnel & how to create a winning content strategy
katarinadahlin
PRO
0
220
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
2.7k
Product Roadmaps are Hard
iamctodd
PRO
55
12k
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.5k
How to build a perfect <img>
jonoalderson
1
4.8k
The Invisible Side of Design
smashingmag
302
51k
Git: the NoSQL Database
bkeepers
PRO
432
66k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
1
1.4k
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
5k
Build The Right Thing And Hit Your Dates
maggiecrowley
38
3k
The Spectacular Lies of Maps
axbom
PRO
1
410
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.3k
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