Upgrade to Pro — share decks privately, control downloads, hide ads and more …

iOSのレイアウトサイクルを知ろう

 iOSのレイアウトサイクルを知ろう

iOS(UIKit)のレイアウトサイクルについてのお話

Af64bc38c0ffcfcabdf430759ee491ce?s=128

Elvis Shi

May 24, 2018
Tweet

More Decks by Elvis Shi

Other Decks in Programming

Transcript

  1. ϨΠΞ΢ταΠΫϧΛ ஌Ζ͏ forαϙʔλʔζ$P-BCษڧձ K15؊

  2. 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 { }
  3. J04ʢ6*,JUʣͷϨΠΞ΢ταΠΫϧ͸ͲͷΑ͏ʹಈ͍ͯΔ͔ ͲͷλΠϛϯάͰϨΠΞ΢τΛ૊Έͳ͓͢΂͖͔ "VUP-BZPVUͰϨΠΞ΢τΛ૊Ή࣌ͱίʔυͰϨΠΞ΢τ૊ Ή࣌ʹͦΕͧΕԿΛؾΛ͚ͭΔ΂͖͔ /PU"VUP-BZPVUͷ͍ܰએ఻ʢΛ͍ͤͯͩ͘͞͞Xʣ ࠓ೔࿩͢͜ͱ

  4. ͜ͷϨΠΞ΢τ͸۩ମతʹͲͷΑ͏ʹ૊ΜͰ͍Δͷ͔ έʔεόΠέʔε͗͢ΔͷͰΧόʔ͢Δͷ͸ແཧ Ͱ΋ฉ͖͍ͨͷ͋Ε͹࣭ٙԠ౴΍࠙਌ձͷ࣌ʹରԠ͍ͨ͠ "VUP-BZPVUͷ࢓૊Έ ڵຯ͋ͬͯ਺ֶಘҙͳϑϨϯζ͸ੋඇҴݟ͞ΜͷൃදΛ
 IUUQTXXXZPVUVCFDPNXBUDI W5)0OTZ%,&:2UT ࠓ೔࿩͞ͳ͍͜ͱ

  5. ੲͷ iPhone ͸ղ૾౓͕ 1 छྨ͔͠ͳ͘ɺ
 ը໘ϨΠΞ΢τ͕ͱͯ΋؆୯ͩͬͨɻ

  6. 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ɿ
  7. ్த͔Βը໘छྨ͕૿͑ɺ
 ϨΠΞ΢τ͕໘౗ʹͳΓ࢝Ίͨɻ

  8. ࠓ͸छྨ͕ଟ͗ͯ͢ɺ
 ΋͸΍ԿΛͲ͏͢Ε͹͍͍͔… ?

  9. ϨΠΞ΢τ
 αΠΫϧ

  10. ϨΠΞ΢τ͸ίετ͕ߴ͍ʢॏ͍ʣ ͳΔ΂͘ϨΠΞ΢τΛඞཁͳ࣌ʹඞཁ࠷௿ݶ͚ͩߦ͍͍ͨ ҙਤ͠ͳ͍৔ॴͰ࠶ϨΠΞ΢τΛىͤͨ͘͜͞ͳ͍ ϨΠΞ΢τΛࢥ͏Ͳ͓Γʹ࣮ݱ͢Δͷʹඞཁ ϨΠΞ΢τઃఆͯ͠Δ͚ͲɺαΠΫϧ͕ճΒͳ͔͍ͬͨͤ Ͱݟͨ໨͕૝ఆͱҧ͏͜ͱΛ๷͍͗ͨ ͳͥϨΠΞ΢ταΠΫϧ͕ॏཁ͔

  11. UIViewController UIView viewDidDisappear() viewWillDisappear() layout runloop viewDidAppear() viewDidLayoutSubviews() layoutSubviews() viewWillLayoutSubviews()

    viewWillAppear() viewDidLoad() loadView()
  12. 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()
  13. setNeedsLayout() Λݺͼग़ͨ͠ σόΠε͕ճసͨ͠ ࢠϏϡʔΛ௥Ճ΍࡟আΛͨ͠ ࣗ෼ͷϏϡʔ֊૚಺ͷ੍໿͕ߋ৽ͨ͠ ʢUIScrollView Ͱ͋ΔࢠϏϡʔʣ͕εΫϩʔϧͨ͠ ࣗ෼ͷࢠϏϡʔͷ frame ΍

    transform ͳͲ͕มߋ͞Εͨ ͳͲͳͲ… ͸Ͳ͏΍ͬͯ൑ఆ͍ͯ͠Δͷ͔ʁ Needs layout?
  14. setNeedsLayout() Λݺͼग़ͨ͠ σόΠε͕ճసͨ͠ ࢠϏϡʔΛ௥Ճ΍࡟আΛͨ͠ ࣗ෼ͷϏϡʔ֊૚಺ͷ੍໿͕ߋ৽ͨ͠ ʢUIScrollView Ͱ͋ΔࢠϏϡʔʣ͕εΫϩʔϧͨ͠ ࣗ෼ͷࢠϏϡʔͷ frame ΍

    transform ͳͲ͕มߋ͞Εͨ ͳͲͳͲ… ͸Ͳ͏΍ͬͯ൑ఆ͍ͯ͠Δͷ͔ʁ Needs layout? ࣗ෼ͷ frame ΍ transform ͸
 ඞͣ਌ʹมߋͯ͠΋Β͓͏ʂ ࣗ෼ͷ frame ΍ transform Λมߋ͢Ε͹
 ਌ͷ layoutSubviews() ΋ݺ͹ΕΔʂ
  15. setNeedsLayout() Λݺͼग़ͨ͠ σόΠε͕ճసͨ͠ ࢠϏϡʔΛ௥Ճ΍࡟আΛͨ͠ ࣗ෼ͷϏϡʔ֊૚಺ͷ੍໿͕ߋ৽ͨ͠ ʢUIScrollView Ͱ͋ΔࢠϏϡʔʣ͕εΫϩʔϧͨ͠ ࣗ෼ͷࢠϏϡʔͷ frame ΍

    transform ͳͲ͕มߋ͞Εͨ ͳͲͳͲ… ͸Ͳ͏΍ͬͯ൑ఆ͍ͯ͠Δͷ͔ʁ Needs layout? ࠶ϨΠΞ΢τ͸ setNeedsLayout ͷ
 ௚ޙͰ͸ͳ͘ɺ࣍ͷϥϯϧʔϓ ϨΠΞ΢τ͸ίετ͕ߴ͍ ΋͘͠͸ layoutIfNeeded ݺͼग़࣌͠
  16. Ͳ͏ϨΠΞ΢τ ͢Ε͹͍͍͔

  17. جຊUIViewͷlayoutSubviewsΛΦʔόʔϥΠυ͠ɺͦ ͷத਎ͰϨΠΞ΢τ͢Ε͹͍͍ ϏϡʔͷframeϓϩύςΟʔ͸ɺ਌͕؅ཧ͢΂͖ ಺༰ʹԠͯ͡αΠζ͕มΘΔϏϡʔ͸ɺsizeThatFitsͰ
 ͳΜͱ͔ͳΔ label.frame.size = label.sizeThatFits(bounds.size) ඞཁʹԠͯ͡ΦʔόʔϥΠυ΋Մ ࠶౓ϨΠΞ΢τ͕ඞཁʹͳͬͨΒsetNeedsLayoutΛݺͿ

    ϨΠΞ΢τ͕ඞཁͱ͍͏ϑϥάཱ͕͚ͭͩͳͷͰɺҰ౓ͷ ॲཧʹෳ਺ճݺΜͰ΋ϨΠΞ΢τ͸ޙͰҰճ͚ͩճΔ ίʔυʢ"VUP-BZPVUͳ͠ʣͰ૊Ή৔߹
  18. ίʔυʢ"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 } } }
  19. ίʔυʢ"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 } } }
  20. "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 } } }
  21. "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 } }
  22. ϨΠΞ΢τʢʹݶΒͣ6*ܥશൠʣ͸ϝΠϯεϨουͰʂ ϨΠΞ΢ταΠΫϧ಺ͰϨΠΞ΢τҾ͖ى͜͞ͳ͍ setNeedsLayout΍setNeedsUpdateConstraintsΛ ݺ͹ͳ͍ ϨΠΞ΢ταΠΫϧͷݺͼग़͠ΛͳΔ΂͘࠷খݶʹ ࢠϏϡʔͷ௥Ճ͸ͳΔ΂͘viewDidLoad͔ viewWillLayoutSubviews಺ʹ layoutIfNeededΛΉ΍Έʹݺ͹ͳ͍ ຊ౰ʹඞཁͳ͚࣌ͩݺͿ ஫ҙ఺ʢڞ௨ʣ

    ΞχϝʔγϣϯϒϩοΫͱ͔
  23. sizeThatFitsΛ͏·͘׆༻͠ɺϨΠΞ΢τॲཧΛ࠷খݶʹ ཈͑Δ sizeThatFitsͷΦʔόʔϥΠυ͢Δ࣌ʹɺத਎ͰͳΔ΂͘ ϨΠΞ΢τॲཧͤ͞ͳ͍ ࣗ෼ࣗ਎Ͱࣗ෼ࣗ਎ͷframe౳Λมߋ͠ͳ͍ʂ ஫ҙ఺ʢίʔυʣ

  24. Ϗϡʔؔ܎Λ͏·͘׆༻੍ͯ͠໿Λ૊Ή ؔ܎ੑΛਖ਼͘͠ઃఆͰ͖ͯΕ͹setNeedsLayout(); layoutIfNeeded()ͷίϯϘ͸ཁΒͳ͍͸ͣʂ ੍໿ͷߋ৽ස౓ΛͳΔ΂͘࠷খݶʹ ՄೳͳݶΓ੍໿Λ౎౓ੜ੒ʗ࡟আ͢ΔͷͰͳ͘ɺಉ੍͡໿ Λྲྀ༻͠constantsΛมߋ͢Δ͔ɺ੍໿ΛisActiveͰ ੾Γସ͑Δ isActiveͰ੾Γସ͑Δ৔߹͸੍໿ͷอ࣋ʹ஫ҙ ίϯςϯπʹԠͨ͡αΠζઃఆ͸intrinsicContentSize ΛΦʔόʔϥΠυ

    ஫ҙ఺ʢ"VUP-BZPVUʣ
  25. એ఻

  26. /PU"VUP-BZPVUઈࢍެ։த IUUQTHJUIVCDPNFMIPTIJOP/PU"VUP-BZPVU

  27. ίʔυʢ/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)) }) } } }
  28. J04ΞϓϦઃܭύλʔϯೖ໳ࣥචܾఆ IUUQTQFBLTDDJ04@BSDIJUFDUVSF

  29. גࣜձࣾΏΊΈɺΤϯδχΞืूத IUUQTXXXZVNFNJDPKQKBQBHFSFDSVJU