Slide 1

Slide 1 text

AutoLayoutҎ֎ͷબ୒ࢶ @muukii Akiba.swift 2018.03.28

Slide 2

Slide 2 text

About Me ‣ muukii ‣ iOS Engineer at eureka, Inc. ‣ Pairs Global Team ‣ GitHub : @muukii ‣ https://muukii.me ☕ ⌚

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Agenda › AutoLayoutͷύϑΥʔϚϯε໰୊ͱ୅ସҊ › ͲΜͳ࣌ʹAutoLayoutͷύϑΥʔϚϯε͕໰୊ʹͳΔͷ͔ › ผͷϨΠΞ΢τΤϯδϯͷ঺հ › TextureGroup/Texture (AsyncDisplayKitʣʹ͍ͭͯ঺հ › ࢖͍ํͷงғؾ (࣌ؒ͋Ε͹) › AutoLayoutʹର͢Δײ૝

Slide 5

Slide 5 text

ίʔυͰAutoLayoutॻ͘ͳΒ › SnapKit/SnapKit › PureLayout/PureLayout › robb/Cartography <= Couples ͱ Pairs ೔ຊ൛Ͱ࢖༻த › stevestreza/Relayout › freshOS/Stevia › roberthein/TinyConstraints › nakiostudio/EasyPeasy <= ࠓͷͱ͜Ζ͓ؾʹೖΓͰւ֎൛PairsͰར༻த › Raizlabs/Anchorage › and more

Slide 6

Slide 6 text

AutoLayoutͷύϑΥʔϚϯε

Slide 7

Slide 7 text

AutoLayout͸੍໿ͷ਺͕૿͑Δͱ஗͘ͳ͍ͬͯ͘ Ҿ༻ : https://github.com/layoutBox/FlexLayout#performance AutoLayout ManualLayout

Slide 8

Slide 8 text

AutoLayout͕஗͘ͳΔ࣌ › ੍໿ͷ਺͕ଟ͍࣌ (੍໿ಉ࢜ͷܨ͕Γ͕ଟ͍࣌) › ಺༰ʹԠͯ͡େ͖͕͞มԽ͢Δίϯϙʔωϯτ͕ଟؚ͘·ΕΔ࣌ › intrinsicContentSizeΛར༻ͨ͠ϨΠΞ΢τ › UILabelͳͲͰwidth, heightͷ੍໿Λ͚ͭͳ͍࣌

Slide 9

Slide 9 text

͜ΜͳϨΠΞ΢τ

Slide 10

Slide 10 text

஗͍ͱԿ͕ى͖Δʁ › UIεϨου͕ϒϩοΫ͞ΕΔ͕࣌ؒ௕͘ͳ͍ͬͯ͘ › ಈ͖͕஗͘ͳΓɺ׈Β͔ʹΞϓϦ(UI)͕ૢ࡞Ͱ͖ͳ͘ͳΔ › UXͷ௿Լʹͭͳ͕͍ͬͯ͘ › ετϨε͕ཷ·Δ? › ໨͕ർΕΔ?

Slide 11

Slide 11 text

1. AutoLayout͕ϘτϧωοΫʹͳΔέʔε › εΫϩʔϧ͢ΔUI › ϦετܥͷUI (UICollectionView/UITableView)ͷCellͷϨΠΞ΢τʹ࢖༻͞ Ε͍ͯΔ࣌ › ྫ : iOSͷ௨஌ηϯλʔ (࠷ۙͷσόΠεͩͱΘ͔ΓͮΒ͍͔΋) › εΫϩʔϧ࣌ʹCellͷத਎ͷϨΠΞ΢τ͕஗͍ͱද͕ࣔ஗ΕΔ › ϑϨʔϜ͕མͪΔ › δϟϯϓ͍ͯ͠ΔΑ͏ʹݟ͑Δ › ໨Ͱ௥͏ͷ͕େม

Slide 12

Slide 12 text

2. AutoLayout͕ϘτϧωοΫʹͳΔέʔε › ը໘ભҠͷͱ͖ › ભҠઌͷViewControllerͷϨΠΞ΢τ͕ෳࡶͩͱॳظදࣔͷϨΠΞ ΢τʹ͕͔͔࣌ؒΔ › ભҠΛߦ͏ϘλϯΛλοϓ͔ͯ͠ΒҰॠݻ·ΓભҠ͕ߦΘΕΔ Α͏ͳݟͨ໨ʹͳΔ › ൓Ԡ͕஗͘ײ͡ΔͷͰɺ΋ͬ͞Γͨ͠ΞϓϦʹݟ͑Δ

Slide 13

Slide 13 text

Ͳͷ͙Β͍໰୊ʹͳΔͷ͔? › ϨΠΞ΢τ͚ͩͰ͋Ε͹ͦΜͳʹ໰୊ʹ͸ͳΒͳ͍ › ͔͠͠ɺͦͷ΄͔ͷϩδοΫ͕ೖΔ͜ͱʹΑΓϨΠΞ΢τͷॲཧ ͕࣌ؒϘτϧωοΫͱͳΔ

Slide 14

Slide 14 text

UI͕ߴ଎ʹಈ࡞͢ΔͨΊʹ͸ʁ › ը໘ͷϦϑϨογϡϨʔτ60Hz => ΠϕϯτϧʔϓͰϒϩοΫͯ͠ྑ͍࣌ؒ͸ 16ms (1000ms / 60) › 16msΛ௒͍͑ͯ͘ͱϑϨʔϜ͕ͲΜͲΜམ͍ͪͯ͘(υϩοϓϑϨʔϜ) › ͜ͷؒʹUIKitࣗମͷॲཧ΋ೖΔͷͰࢲ͕ͨͪॻ͘ίʔυ͸10ms͙Β͍ʹ͸͍ͨ͠ ΋ͷ › ࠷ۙͷiPad Pro͸120HzʹͳͬͯΔͷͰɺ΋ͬͱ଎͘͠ͳ͍ͱσΟεϓϨΠͷՁ஋ ͸ൃش͞Εͳ͍ɻૢ࡞ʹΑΔεΫϩʔϧͷ଎͞ʹCellͷද͕ࣔ௥͍͔ͭͳ͚Ε͹ͳ Βͳ͍ɻ ͦ΋ͦ΋

Slide 15

Slide 15 text

UI͕ߴ଎ʹಈ࡞͢ΔͨΊʹ͸ʁ › ͔͠͠ɺͲΜͳʹࣗ෼ͨͪͷίʔυΛ࠷దԽͯ͠΋ͦͷݶք͸͋Δ › AutoLayoutͷίετΛݮΒ͢ͷ͸೉͍͠ ϨΠΞ΢τॲཧ ϨΠΞ΢τॲཧ ͦͷଞϩδοΫ ͦͷଞϩδοΫ 16ms ࠷దԽ 0ms

Slide 16

Slide 16 text

ϨΠΞ΢τҎ֎ͷϩδοΫ͸ͦΜͳʹߴ଎ԽͰ͖ͳ͍ › Α΄Ͳॏ͍ॲཧΛॻ͍͍ͯͳ͍ݶΓɺ͢Ͱʹߴ଎ͳίʔυʹͳͬͯ ͍ΔՄೳੑ͸ߴ͍ › ϨΠΞ΢τॲཧΛͲ͏ʹ͔͠ͳ͍ݶΓυϩοϓϑϨʔϜ͸ආ͚Β Εͳ͍ ࣮ࡍͷͱ͜Ζ

Slide 17

Slide 17 text

ϨΠΞ΢τॲཧ͕ߴ଎ʹͳΕ͹ɺͦͷଞͷ ϩδοΫʹCPUϦιʔεΛඅ΍͢͜ͱ͕Ͱ ͖ΔΑ͏ʹͳΔ ϨΠΞ΢τॲཧ ͦͷଞϩδοΫ 16ms ͜͏͍ͨ͠ 0ms ͦͷଞϩδοΫ

Slide 18

Slide 18 text

AutoLayoutΛ΍ΊͯΈΔ

Slide 19

Slide 19 text

ͱ͸͍͑ɺView.frame͸৮Γͨ ͘ͳ͍

Slide 20

Slide 20 text

ผͷϨΠΞ΢τΤϯδϯ › facebook/Yoga (ւ֎൛PairsͰ࢖༻த) › TextureGroup/Texture (ւ֎൛PairsͰ࢖༻த) › mirego/PinLayout (ͪΐͬͱ৮ͬͯߟ͑த) › linkedin/LayoutKit › mirego/PinLayout › mamaral/Neon

Slide 21

Slide 21 text

facebook/yoga *

Slide 22

Slide 22 text

Yoga › FacebookʹΑͬͯ։ൃ͞Ε͍ͯΔ › ඇৗʹߴ଎ͳΫϩεϓϥοτϑΥʔϜͳϨΠΞ΢τΤϯδϯ › AutoLayoutͱൺֱͯ͠8ഒఔ౓଎͍ › https://github.com/layoutBox/FlexLayout#performance

Slide 23

Slide 23 text

Yoga › CSS Flexbox Layoutͱඇৗʹ͍ۙಈ࡞ͱࢦఆํࣜ › جຊ͸UIStackViewͷverticalͱhorizontalΛ૊Έ߹ΘͤͯϨΠΞ΢ τ͢Δײ֮ › ίΞͱͳΔίʔυ͸C++Ͱ3000ߦ΄Ͳ › ؤுΕ͹ಡΊͦ͏Ͱ͢ɻ

Slide 24

Slide 24 text

Yoga › UIView.sizeThatFitsΛݺͼग़ͯ͠಺༰ʹԠͨ͡αΠζΛܾఆ͢Δ › ReactNativeͰ΋࢖ΘΕ͍ͯΔ › ͜Ε͸ͭ·ΓɺReactNative੡ΞϓϦͷํ͕UIͷಈ࡞͕଎͍Մೳੑ ͕͋Δͱ͍͏͜ͱ

Slide 25

Slide 25 text

Yoga › APIͱͯ͠͸Objective-C༻ͳͷͰSwift͔Β͸ͪΐͬͱ࢖͍ͮΒ͍ › layoutBox/FlexLayoutͱ͍͏Swift༻ʹYogaΛϥοϓͨ͠΋ͷ͕͋ Δ › muukii/Mondrian͍ͬͯ͏ͷ΋࡞ͬͯΈͯΔ(ࠓࢭ·ͬͯΔ)

Slide 26

Slide 26 text

TextureGroup/Texture

Slide 27

Slide 27 text

Texture › ݩͷ໊͸AsyncDisplayKit › PinterestͷiOSΞϓϦͰશ໘తʹ࢖༻͞Ε͍ͯΔɻ › Pinterestͷૉ੖Β͍͠ಈ͖͸Textureͷύϫʔ

Slide 28

Slide 28 text

Texture › UIKitΛϥοϓ͢ΔܗͰUIύϑΥʔϚϯεΛ࠷େ·ͰҾ্͖͛ΔϥΠ ϒϥϦ › Objective-C++ʹΑΔ࣮૷

Slide 29

Slide 29 text

Texture › ϥΠϒϥϦར༻ऀ͸UIKitΦϒδΣΫτΛૢ࡞͢ΔͷͰ͸ͳ͘ɺNodeͱ͍͏ΦϒδΣΫ τΛ௨ͯ͠UIΛૢ࡞͢Δɻ › Node͸εϨουηʔϑͰ͋ΓɺόοΫάϥ΢ϯυεϨου͔ΒมߋՄೳ › Node -> UIView΁ͷల։͸Texture͕ߦ͏ › ඇಉظͰUIΛߋ৽͢Δ (AsyncʹDisplay͢Δ) › MainThread͕UIૢ࡞Ͱ๩͚͠Ε͹ߋ৽ΛݟૹΔ › UI͕ݻ·Βͳ͍ › ήʔϜΤϯδϯͬΆ͍ߟ͑ํ

Slide 30

Slide 30 text

Nodeͷछྨ › ASDisplayNode : NSObject › ASTextNode : ASDisplayNode - UILabelͷ୅ΘΓ › ASImageNode : ASDisplayNode - UIImageViewͷ୅ΘΓ › ASButtonNode : ASDisplayNode - UIButtonͷ୅ΘΓ › ASCellNode : ASDisplayNode - UICollectionViewCell/UITableViewCellͷ୅ ΘΓ › and more… Texture

Slide 31

Slide 31 text

ϨΠΞ΢τ › ϨΠΞ΢τ͸Node಺ʹίʔυͰهड़ › CSS FlexboxͷۭؾײΛҾ͖ܧ͍Ͱ͓Γɺ͞ΒʹUIϨΠΞ΢τʮ͋ Δ͋ΔʯʹରԠ͠΍͍͢هड़ํ๏͕༻ҙ͞Ε͍ͯΔɻ › ׳Εͯ͘ΔͱUIͷ։ൃͷޮ཰͕Α͘ͳΔ › ܭࢉ଎౓͸YogaฒΈʹ଎͍͔ͱࢥΘΕΔ Texture

Slide 32

Slide 32 text

ڻ͘΄Ͳͷ࠷దԽ › ՄೳͳݶΓόοΫάϥ΢ϯυεϨουͰॲཧΛߦ͏ › UIͷϨεϙϯε଎౓͕࠷େԽ › UILabel΍UIImageView͸࢖ΘΕͣʹϨΠϠʔʹϨϯμϦϯά › λοϓ൑ఆͷඞཁͷͳ͍΋ͷ͸CALayerͱͯ͠දࣔ (layerBacking) (UIViewΑΓϝϞϦফඅ͕཈͑ΒΕΔͨΊ) › όοΫάϥ΢ϯυͰϝϞϦղ์Λߦ͏Ωϡʔ (releaseʹ΋CPUίετ͕͔͔ΔͨΊ) › Nodeʹهड़ͨ͠ϨΠΞ΢τ͸όοΫάϥ΢ϯυͰαΠζܭࢉ͕Մೳ › ConcurrentͰ࣮ߦ͞ΕΔͨΊɺNode͕࣋ͭσʔλ͕ଞεϨουʹґଘ͍ͯ͠ͳ͚Ε͹ɺCPUͷίΞ͕ϑ ϧͰ࢖͑Δ › iPhoneXͳΒ6ݸCellಉ࣌ʹܭࢉ Texture

Slide 33

Slide 33 text

TextureΛ࢖࣮ͬͨ૷ͷงғؾ https://github.com/muukii/PlayTexture ࡉ͔͘આ໌͢Δͱ࿩͕௕͘ͳͬͯ͠·͏ͷͰงғؾ͚ͩʹ͓͖ͯ͠·͢ɻ

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

UIKit class CardView : UIView { } ௨ৗ௨ΓUIKitͰViewΛ࡞ΔͳΒUIViewͷαϒ ΫϥεΛ࡞੒ͯ͠ViewΛ͍͖ͭͬͯ͘·͢

Slide 37

Slide 37 text

Texture // Textureʹͳ͚ͬͨͲModuleͷ໊લ͸ݹ͍·· import AsyncDisplayKit class CardNode : ASDisplayNode { } TextureͰ͸UIViewͷ୅ΘΓʹ ASDisplayNodeͷαϒΫϥεΛ࡞੒͠·͢ɻ

Slide 38

Slide 38 text

import AsyncDisplayKit class CardNode : ASDisplayNode { private let imageNode: ASImageNode = .init() private let titleNode: ASTextNode = .init() private let detailNode: ASTextNode = .init() } ίϯϙʔωϯτΛఆٛ

Slide 39

Slide 39 text

class CardNode : ASDisplayNode { … init(image: UIImage, title: String, detail: String) { super.init() titleNode.attributedText = NSAttributedString( string: title, attributes: […] ) detailNode.attributedText = NSAttributedString( string: detail, attributes: […] ) imageNode.image = image 
 addSubnode(imageNode)
 addSubnode(titleNode) addSubnode(detailNode) // addSubnodeͷ୅ΘΓʹ `automaticallyManagesSubnodes = true` Ͱ΋Α͍ } }

Slide 40

Slide 40 text

class CardNode : ASDisplayNode { … override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { // ⭐ ͜͜ʹϨΠΞ΢τΛهड़͢Δ } }

Slide 41

Slide 41 text

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { return ASRatioLayoutSpec( ratio: 1.6, child: ASBackgroundLayoutSpec( child: ASInsetLayoutSpec( insets: .init(top: 8, left: 8, bottom: 8, right: 8), child: ASStackLayoutSpec( direction: .vertical, spacing: 8, justifyContent: .end, alignItems: .start, children: [ titleNode, detailNode, ] ) ), background: imageNode ) ) }

Slide 42

Slide 42 text

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { return ASRatioLayoutSpec( ratio: 1.6, child: ASBackgroundLayoutSpec( child: ASInsetLayoutSpec( insets: .init(top: 8, left: 8, bottom: 8, right: 8), child: ASStackLayoutSpec( direction: .vertical, spacing: 8, justifyContent: .end, alignItems: .start, children: [ titleNode, detailNode, ] ) ), background: imageNode ) ) }

Slide 43

Slide 43 text

let info = ASStackLayoutSpec( direction: .vertical, spacing: 8, justifyContent: .end, alignItems: .start, children: [ titleNode, detailNode, ] ) Vertical

Slide 44

Slide 44 text

let body = ASBackgroundLayoutSpec( child: info, background: imageNode ) let info = ASStackLayoutSpec(…

Slide 45

Slide 45 text

class CardNode : ASDisplayNode { … override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { return ASRatioLayoutSpec( ratio: 1.6, // ॎ௕ͷΧʔυͰදࣔ͢ΔͨΊbodyͷΞεϖΫτൺΛࢦఆ͢Δ child: body ) } }

Slide 46

Slide 46 text

let node = CardNode( image: UIImage(named: "sample")!, title: "Lorem Ipsum", detail: "Lorem Ipsum……" ) view.addSubnode(node) let layout = node.calculateLayoutThatFits( ASSizeRange( min: .init(width: 0.0, height: 0), max: .init(width: 240.0, height: .infinity) ) ) node.frame.size = layout.size In ViewController ࣮ࡍɺ͜͏͍͏ίʔυ͸͋·Γॻ͔ͳ͍Ͱ͢ɻ CardNodeΛॳظԽ ViewController.viewʹaddSubnode nodeͷαΠζܭࢉΛߦ͏ αΠζͷRangeΛ༩͑Δ UIView.sizeThatFitsͷײ֮

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

let insetInfo = ASInsetLayoutSpec( insets: UIEdgeInsets( top: 8, left: 8, bottom: 8, right: 8 ), child: info ) let info = ASStackLayoutSpec( direction: .vertical, spacing: 8, justifyContent: .end, alignItems: .start, children: [ titleNode, detailNode, ] )

Slide 49

Slide 49 text

let body = ASBackgroundLayoutSpec( child: info, background: imageNode ) let body = ASBackgroundLayoutSpec( child: insetInfo, background: imageNode )

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { return ASRatioLayoutSpec( ratio: 1.6, child: ASBackgroundLayoutSpec( child: ASInsetLayoutSpec( insets: .init(top: 8, left: 8, bottom: 8, right: 8), child: ASStackLayoutSpec( direction: .vertical, spacing: 8, justifyContent: .end, alignItems: .start, children: [ titleNode, detailNode, ] ) ), background: imageNode ) ) } શ෦·ͱΊΔͱ

Slide 52

Slide 52 text

DispatchQueue.global().async { let layout = node.calculateLayoutThatFits( ASSizeRange( min: .init(width: 0.0, height: 0), max: .init(width: 240.0, height: .infinity) ) ) DispatchQueue.main.async { node.frame.size = layout.size } } όοΫάϥ΢ϯυͰαΠζܭࢉ

Slide 53

Slide 53 text

·ͱΊ › AutoLayout͸ॊೈͰڧྗͳϨΠΞ΢τΤϯδϯ͕ͩɺ·ͩύϑΥʔϚϯε໘Ͱ՝ ୊͕͋Δɻ › ҰԠɺ࠷ۙͷiOSͰ͸ϨΠΞ΢τॲཧΛগͳͤ͘͞Δ޻෉͸ೖ͖͍ͬͯͯΔ › UICollectionViewͷestimated~ͱ͔ › ݱঢ়ɺCPUͷΫϩοΫ਺ͷ৳ͼʹ͸ظ଴ͮ͠Β͍ͷͰɺUIύϑΥʔϚϯε޲্ͷ ͨΊʹ͸ϚϧνεϨουʹΑΔΞϓϩʔν͕ඞਢʹͳͬͯ͘Δ͸ͣɻ › ͔͠͠ɺUIKit͸ݱ࣌఺Ͱ͸ϚϧνεϨουʹ͸ରԠ͍ͯ͠ͳ͍ɻ

Slide 54

Slide 54 text

·ͱΊ › ͜ͷݱঢ়ʹରͯ͠ɺFacebook,Instagram,PinterestͷΑ͏ͳαʔϏε͸ϓϩμΫτʹ͓ ͍ͯAutoLayoutͷ࢖༻Λආ͚͓ͯΓɺͦΕͧΕ͕UIύϑΥʔϚϯεΛ࠷େԽ͢ΔOSS Λެ։͍ͯ͠Δ › Pinterest - Texture › Facebook - ComponentKit, ReactNative, Texture › Instagram - IGListKit (ͪΐͬͱझࢫ͸ҧ͏͚Ͳ) › ͜ͷঢ়گʹରͯ͠ɺࠓޙApple͕औΔUIKit΁ͷߟָ͕͑͠ΈͰ͢ɻAutoLayoutؤுͬ ͯཉ͍͠Ͱ͢Ͷʂ

Slide 55

Slide 55 text

Thank you