Slide 1

Slide 1 text

એݴత 6*$PMMFDUJPO7JFX JTILBXB

Slide 2

Slide 2 text

w גࣜձࣾ9$50 w 4XJGU ,PUMJO (P +BWB4DSJQU w "1*,JU %*,JU w 4XJGU࣮ફೖ໳ J041SPHSBNNJOH JTILBXB

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

࣮૷͠·͢

Slide 5

Slide 5 text

class VenueDetailViewController: UIViewController { var venue: Venue var reviews: [Review] var relatedVenues: [Venue] }

Slide 6

Slide 6 text

func collectionView( _ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { switch section { case 0: return 1 case 1: return reviews.count case 2: return relatedVenues.count default: fatalError("unknown section \(section)") } }

Slide 7

Slide 7 text

func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { switch indexPath.section { case 0: let cell = collectionView.dequeueReusableCell( withReuseIdentifier: "VenueOutline", for: indexPath) as! VenueOutlineCell cell.bind(venue) return cell case 1: let cell = collectionView.dequeueReusableCell( withReuseIdentifier: "Review", for: indexPath) as! ReviewCell cell.bind(reviews[indexPath.item]) return cell case 2: let cell = collectionView.dequeueReusableCell( withReuseIdentifier: "RelatedVenue", for: indexPath) as! RelatedVenueCell cell.bind(relatedVenues[indexPath.item]) return cell default: fatalError() } }

Slide 8

Slide 8 text

func collectionView( _ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { guard kind == UICollectionView.elementKindSectionHeader else { fatalError() } switch indexPath.section { case 1: let view = collectionView.dequeueReusableCell( withReuseIdentifier: "SectionHeader", for: indexPath) as! SectionHeaderCell view.bind("Reviews") return view case 2: let view = collectionView.dequeueReusableCell( withReuseIdentifier: "SectionHeader", for: indexPath) as! SectionHeaderCell view.bind("Related Venues") return view default: fatalError() } }

Slide 9

Slide 9 text

func collectionView( _ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout switch section { case 0: return .zero case 1: return reviews.isEmpty ? .zero : flowLayout.headerReferenceSize case 2: return relatedVenues.isEmpty ? .zero : flowLayout.headerReferenceSize default: fatalError() } }

Slide 10

Slide 10 text

w ίʔυ͔Β݁Ռ͕૝૾ͮ͠Β͍ w JOEFYʹґଘͨ͠৚݅෼ذ͕͋Ϳͳ͍༧ײ w ৚݅෼ذ͕ॏෳ͍ͯ͠ΔͷͰߋʹ͋Ϳͳ͍

Slide 11

Slide 11 text

ͳΜͱ͔͠Α͏

Slide 12

Slide 12 text

ॲཧͷϑϩʔΛ௥ͬͯΈΔ

Slide 13

Slide 13 text

7FOVF%FUBJM7JFX$POUSPMMFS 6*$PMMFDUJPO7JFX switch section { case 0: return 1 case 1: return reviews.count case 2: return relatedVenues.count default: fatalError() } w WFOVF w SFWJFXT w SFMBUFE7FOVFT ηϧͷ਺͸ʁ Ͱ͢ʂ

Slide 14

Slide 14 text

7FOVF%FUBJM7JFX$POUSPMMFS 6*$PMMFDUJPO7JFX ൪໨ͷηϧ͸ʁ 7FOVF0VUMJOF$FMMͰ͢ʂ switch indexPath.section { case 0: let cell = collectionView.dequeueReusableCell( withReuseIdentifier: "VenueOutline", for: indexPath) as! VenueOutlineCell cell.bind(venue) return cell case 1: let cell = collectionView.dequeueReusableCell( withReuseIdentifier: "Review", for: indexPath) as! ReviewCell cell.bind(reviews[indexPath.item]) return cell case 2: let cell = collectionView.dequeueReusableCell( withReuseIdentifier: "RelatedVenue", for: indexPath) as! RelatedVenueCell cell.bind(relatedVenues[indexPath.item]) return cell default: fatalError() } w WFOVF w SFWJFXT w SFMBUFE7FOVFT

Slide 15

Slide 15 text

าҾ͍ͯଊ͑ͯΈΔ

Slide 16

Slide 16 text

7FOVF%FUBJM7JFX$POUSPMMFS 6*$PMMFDUJPO7JFX w WFOVF w SFWJFXT w SFMBUFE7FOVFT w OVNCFS0G*UFNT w DFMM'PS*UFN"U σʔλม׵ ঢ়ଶΛද͢σʔλ 6*$PMMFDUJPO7JFXͷσʔλ

Slide 17

Slide 17 text

7FOVF%FUBJM7JFX$POUSPMMFS 6*$PMMFDUJPO7JFX ঢ়ଶΛද͢σʔλ 6*$PMMFDUJPO7JFXͷσʔλ w OVNCFS0G*UFNT w DFMM'PS*UFN"U ϝιου͝ͱʹ σʔλม׵ w WFOVF w SFWJFXT w SFMBUFE7FOVFT

Slide 18

Slide 18 text

ʮঢ়ଶΛද͢σʔλʯ͔Β ʮ6*$PMMFDUJPO7JFXͷσʔλʯ͸ԕ͍ͷʹ
 ϝιου͝ͱʹΠν͔Βม׵͍ͯ͠Δ

Slide 19

Slide 19 text

σʔλม׵Λ؍࡯͢Δ

Slide 20

Slide 20 text

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { switch section { case 0: return 1 case 1: return reviews.count case 2: return relatedVenues.count default: fatalError("unknown section \(section)") } } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { switch indexPath.section { case 0: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VenueOutline", for: indexPath) as! VenueOutlineCell cell.bind(venue) return cell case 1: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Review", for: indexPath) as! ReviewCell cell.bind(reviews[indexPath.item]) return cell case 2: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RelatedVenue", for: indexPath) as! RelatedVenueCell cell.bind(relatedVenues[indexPath.item]) return cell default: fatalError() } }

Slide 21

Slide 21 text

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { switch section { case 0: return 1 case 1: return reviews.count case 2: return relatedVenues.count default: fatalError("unknown section \(section)") } } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { switch indexPath.section { case 0: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VenueOutline", for: indexPath) as! VenueOutlineCell cell.bind(venue) return cell case 1: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Review", for: indexPath) as! ReviewCell cell.bind(reviews[indexPath.item]) return cell case 2: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RelatedVenue", for: indexPath) as! RelatedVenueCell cell.bind(relatedVenues[indexPath.item]) return cell default: fatalError() } } ৚݅෼ذ͕ॏෳ͍ͯ͠Δ

Slide 22

Slide 22 text

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { switch section { case 0: return 1 case 1: return reviews.count case 2: return relatedVenues.count default: fatalError("unknown section \(section)") } } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { switch indexPath.section { case 0: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VenueOutline", for: indexPath) as! VenueOutlineCell cell.bind(venue) return cell case 1: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Review", for: indexPath) as! ReviewCell cell.bind(reviews[indexPath.item]) return cell case 2: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RelatedVenue", for: indexPath) as! RelatedVenueCell cell.bind(relatedVenues[indexPath.item]) return cell default: fatalError() } } ݸͷ7FOVF0VUMJOF$FMMΛฦͨ͢Ίͷॲཧ

Slide 23

Slide 23 text

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { switch section { case 0: return 1 case 1: return reviews.count case 2: return relatedVenues.count default: fatalError("unknown section \(section)") } } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { switch indexPath.section { case 0: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VenueOutline", for: indexPath) as! VenueOutlineCell cell.bind(venue) return cell case 1: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Review", for: indexPath) as! ReviewCell cell.bind(reviews[indexPath.item]) return cell case 2: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RelatedVenue", for: indexPath) as! RelatedVenueCell cell.bind(relatedVenues[indexPath.item]) return cell default: fatalError() } } SFWJFXTDPVOUݸͷ3FWJFX$FMMΛฦͨ͢Ίͷॲཧ

Slide 24

Slide 24 text

w ৚݅෼ذηϧͷछྨΛܾΊΔॲཧ w ࣮ߦจ6*$PMMFDUJPO7JFXʹ஋Λ౉͢ॲཧ

Slide 25

Slide 25 text

ॲཧΛஈ֊ʹ෼͚Δ

Slide 26

Slide 26 text

7FOVF%FUBJM7JFX$POUSPMMFS 6*$PMMFDUJPO7JFX σʔλม׵ σʔλม׵ 6*$PMMFDUJPO7JFXʹ஋Λ౉͢ॲཧ ηϧͷछྨΛܾΊΔॲཧ w WFOVF w SFWJFXT w SFMBUFE7FOVFT w DFMM%FDMBSBUJPOT w OVNCFS0G*UFNT w DFMM'PS*UFN"U 6*$PMMFDUJPO7JFXͷσʔλ ίϨΫγϣϯΛද͢σʔλ ঢ়ଶΛද͢σʔλ

Slide 27

Slide 27 text

ஈ֊໨ͷॲཧ

Slide 28

Slide 28 text

w ηϧͷछྨͱඥ͚ͮΔ஋ͷϖΞ w 6*$PMMFDUJPO7JFX$FMMͱରͷؔ܎ enum CellDeclaration: Equatable { case outline(Venue) case sectionHeader(String) case review(Review) case relatedVenue(Venue) } ੒Ռ෺

Slide 29

Slide 29 text

w WFOVF7FOVF w SFWJFXT<> w SFMBUFE7FOVFT<> w DFMM%FDMBSBUJPOT<
 $FMM%FDMBSBUJPOPVUMJOF 7FOVF 
 > ίϨΫγϣϯΛද͢σʔλ ঢ়ଶΛද͢σʔλ

Slide 30

Slide 30 text

w WFOVF7FOVF w SFWJFXT<
 3FWJFX JE 
 3FWJFX JE 
 > w SFMBUFE7FOVFT<> w DFMM%FDMBSBUJPOT<
 $FMM%FDMBSBUJPOPVUMJOF 7FOVF 
 $FMM%FDMBSBUJPOTFDUJPO)FBEFS 
 $FMM%FDMBSBUJPOSFWJFX 3FWJFX JE 
 $FMM%FDMBSBUJPOSFWJFX 3FWJFX JE 
 > ίϨΫγϣϯΛද͢σʔλ ঢ়ଶΛද͢σʔλ

Slide 31

Slide 31 text

struct Data: CellsDeclarator { var venue: Venue var reviews: [Review] var relatedVenues: [Venue] func declareCells(_ cell: (CellDeclaration) -> Void) { cell(.outline(venue)) if !reviews.isEmpty { cell(.sectionHeader("Reviews")) for review in reviews { cell(.review(review)) } } if !relatedVenues.isEmpty { cell(.sectionHeader("Related Venues")) for relatedVenue in relatedVenues { cell(.relatedVenue(relatedVenue)) } } } } ͜ͷؔ਺ΛݺΜͰηϧΛએݴ͍ͯ͘͠

Slide 32

Slide 32 text

struct Data: CellsDeclarator { var venue: Venue var reviews: [Review] var relatedVenues: [Venue] func declareCells(_ cell: (CellDeclaration) -> Void) { cell(.outline(venue)) if !reviews.isEmpty { cell(.sectionHeader("Reviews")) for review in reviews { cell(.review(review)) } } if !relatedVenues.isEmpty { cell(.sectionHeader("Related Venues")) for relatedVenue in relatedVenues { cell(.relatedVenue(relatedVenue)) } } } }

Slide 33

Slide 33 text

protocol CellsDeclarator { associatedtype CellDeclaration func declareCells(_ cell: (CellDeclaration) -> Void) } extension CellsDeclarator { var cellDeclarations: [CellDeclaration] { var declarations = [] as [CellDeclaration] declareCells { declaration in declarations.append(declaration) } return declarations } }

Slide 34

Slide 34 text

ஈ֊໨ͷॲཧ

Slide 35

Slide 35 text

7FOVF%FUBJM7JFX$POUSPMMFS 6*$PMMFDUJPO7JFX σʔλม׵ σʔλม׵ 6*$PMMFDUJPO7JFXʹ஋Λ౉͢ॲཧ ڞ௨ͷ࢓૊ΈͰߦ͏ ηϧͷछྨΛܾΊΔॲཧ ը໘͝ͱʹߦ͏ w WFOVF w SFWJFXT w SFMBUFE7FOVFT w DFMM%FDMBSBUJPOT w OVNCFS0G*UFNT w DFMM'PS*UFN"U 6*$PMMFDUJPO7JFXͷσʔλ ίϨΫγϣϯΛද͢σʔλ ঢ়ଶΛද͢σʔλ

Slide 36

Slide 36 text

σʔλม׵ w OVNCFS0G*UFNT w DFMM'PS*UFN"U<> w DFMM%FDMBSBUJPOT<
 $FMM%FDMBSBUJPOPVUMJOF 7FOVF 
 $FMM%FDMBSBUJPOTFDUJPO)FBEFS 
 $FMM%FDMBSBUJPOSFWJFX *UFN JE 
 $FMM%FDMBSBUJPOSFWJFX *UFN JE 
 > ίϨΫγϣϯΛද͢σʔλ 6*$PMMFDUJPO7JFXͷσʔλ 6*$PMMFDUJPO7JFXʹ஋Λ౉͢ॲཧ ڞ௨ͷ࢓૊ΈͰߦ͏

Slide 37

Slide 37 text

6*$PMMFDUJPO7JFX$FMMΛ ฦͨ͢Ίʹඞཁͳ͜ͱ͸ʁ

Slide 38

Slide 38 text

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { switch indexPath.section { case 0: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VenueOutline", for: indexPath) as! VenueOutlineCell cell.bind(venue) return cell case 1: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Review", for: indexPath) as! ReviewCell cell.bind(reviews[indexPath.item]) return cell case 2: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RelatedVenue", for: indexPath) as! RelatedVenueCell cell.bind(relatedVenues[indexPath.item]) return cell default: fatalError() } } ηϧͷEFRVFVF ηϧ΁ͷ஋ͷඥ͚ͮ

Slide 39

Slide 39 text

protocol BindableNibCell { static var nib: UINib { get } static var reuseIdentifier: String { get } 
 associatedtype Value func bind(_ value: Value) } ηϧ΁ͷ஋ͷඥ͚ͮ ηϧͷEFRVFVF ඞཁͳ΋ͷΛηϧʹఏڙͤ͞Δ

Slide 40

Slide 40 text

struct CellBinder { let nib: UINib let reuseIdentifier: String let configureCell: (UICollectionViewCell) -> Void fileprivate init(cellType: Cell.Type, value: Cell.Value) { self.nib = cellType.nib self.reuseIdentifier = cellType.reuseIdentifier self.configureCell = { cell in guard let cell = cell as? Cell else { fatalError("Could not cast UICollectionView cell to \(Cell.self)") } cell.bind(value) } } } extension BindableNibCell { static func makeBinder(with value: Value) -> CellBinder { return CellBinder(cellType: Self.self, value: value) } } ηϧ΁ͷ஋ͷඥ͚ͮ ηϧͷEFRVFVF DFMM'PS*UFN"U಺Ͱ࢖͑ΔΑ͏ʹܕΛফڈ

Slide 41

Slide 41 text

͜ΕΛ࢖ͬͯ6*$PMMFDUJPO7JFX%BUB4PVSDFΛ࣮૷

Slide 42

Slide 42 text

class CollectionViewDataSource: NSObject, UICollectionViewDataSource { var cellDeclarations = [] as [CellDeclaration] private var registeredReuseIdentifiers = [] as [String] private let binderFromDeclaration: (CellDeclaration) -> CellBinder init(binderFromDeclaration: @escaping (CellDeclaration) -> CellBinder) { self.binderFromDeclaration = binderFromDeclaration super.init() } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cellDeclarations.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cellBinder = binderFromDeclaration(cellDeclarations[indexPath.item]) if !registeredReuseIdentifiers.contains(cellBinder.reuseIdentifier) { collectionView.register(cellBinder.nib, forCellWithReuseIdentifier: cellBinder.reuseIdentifier) registeredReuseIdentifiers.append(cellBinder.reuseIdentifier) } let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellBinder.reuseIdentifier, for: indexPath) cellBinder.configureCell(cell) return cell } } $FMM%FDMBSBUJPOͱ$FMM#JOEFSͷϚοϐϯάΛҾ਺ʹऔΔ $FMM%FDMBSBUJPOΛ$FMM#JOEFSΛม׵ͯ͠6*$PMMFDUJPO7JFX$FMMΛฦ͢

Slide 43

Slide 43 text

͜ΕΛ7FOVF%FUBJM7JFX$POUSPMMFSͰ ΠϯελϯεԽͯ͠࢖͏

Slide 44

Slide 44 text

final class VenueDetailViewController: UIViewController { private let dataSource = CollectionViewDataSource { cellDeclaration in switch cellDeclaration { case .outline(let venue): return VenueOutlineCell.makeBinder(with: venue) case .sectionHeader(let title): return SectionHeaderCell.makeBinder(with: title) case .review(let review): return ReviewCell.makeBinder(with: review) case .relatedVenue(let venue): return RelatedVenueCell.makeBinder(with: venue) } } ... } $FMM%FDMBSBUJPOͱ$FMM#JOEFSΛରԠ͚ͮ

Slide 45

Slide 45 text

׬੒

Slide 46

Slide 46 text

7FOVF%FUBJM7JFX$POUSPMMFS 6*$PMMFDUJPO7JFX σʔλม׵ σʔλม׵ 6*$PMMFDUJPO7JFXʹ஋Λ౉͢ॲཧ ڞ௨ͷ࢓૊ΈͰߦ͏ ηϧͷछྨΛܾΊΔॲཧ ը໘͝ͱʹߦ͏ w WFOVF w SFWJFXT w SFMBUFE7FOVFT w DFMM%FDMBSBUJPOT w OVNCFS0G*UFNT w DFMM'PS*UFN"U 6*$PMMFDUJPO7JFXͷσʔλ ίϨΫγϣϯΛද͢σʔλ ঢ়ଶΛද͢σʔλ

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

;Γ͔͑Γ

Slide 49

Slide 49 text

final class VenueDetailViewController: UIViewController { enum CellDeclaration: Equatable { case outline(Venue) case sectionHeader(String) case review(Review) case relatedVenue(Venue) } struct Data: CellsDeclarator { var venue: Venue var reviews: [Review] var relatedVenues: [Venue] func declareCells(_ cell: (CellDeclaration) -> Void) { cell(.outline(venue)) if !reviews.isEmpty { cell(.sectionHeader("Reviews")) for review in reviews { cell(.review(review)) } } if !relatedVenues.isEmpty { cell(.sectionHeader("Related Venues")) for relatedVenue in relatedVenues { cell(.relatedVenue(relatedVenue)) } } } } ... }

Slide 50

Slide 50 text

final class VenueDetailViewController: UIViewController { enum CellDeclaration: Equatable { case outline(Venue) case sectionHeader(String) case review(Review) case relatedVenue(Venue) } struct Data: CellsDeclarator { var venue: Venue var reviews: [Review] var relatedVenues: [Venue] func declareCells(_ cell: (CellDeclaration) -> Void) { cell(.outline(venue)) if !reviews.isEmpty { cell(.sectionHeader("Reviews")) for review in reviews { cell(.review(review)) } } if !relatedVenues.isEmpty { cell(.sectionHeader("Related Venues")) for relatedVenue in relatedVenues { cell(.relatedVenue(relatedVenue)) } } } } ... }

Slide 51

Slide 51 text

final class VenueDetailViewController: UIViewController { ... @IBOutlet private weak var collectionView: UICollectionView! private let dataSource = CollectionViewDataSource { cellDeclaration in switch cellDeclaration { case .outline(let venue): return VenueOutlineCell.makeBinder(with: venue) case .sectionHeader(let title): return SectionHeaderCell.makeBinder(with: title) case .review(let review): return ReviewCell.makeBinder(with: review) case .relatedVenue(let venue): return RelatedVenueCell.makeBinder(with: venue) } } private var data: Data! { didSet { dataSource.cellDeclarations = data.cellDeclarations collectionView.reloadData() } } }

Slide 52

Slide 52 text

w ηϧͷએݴ͸දࣔ݁Ռͱಉ͡ߏ଄ͳͷͰಡΈ΍͍͢ w ॳݟͰ͸Ͳ͏΍ͬͯಈ͘ͷ͔ཧղ͢Δͷʹֻ͕͔࣌ؒΔ w ௕ظతʹ͸ಡΈ΍͢͞ͷϝϦοτ͕উͪͦ͏ Մಡੑ

Slide 53

Slide 53 text

࣮૷ίετ w ηϧͷએݴ͸දࣔ݁Ռͱಉ͡ߏ଄ͳͷͰॻ͖΍͍͢ w ίʔυͷ૯ྔ΋ݮͬͨ w ॏෳͨ͠৚݅෼ذΛഉআͰ͖ͨͷͰϛεͮ͠Β͘ͳͬͨ w ૝ఆ͍ͯ͠ͳ͍࢖͍ํͰ͸ίετ্͕͕ΔՄೳੑ͕͋Δ

Slide 54

Slide 54 text

w ηϧͷએݴͷςετ͕Մೳʹͳͬͨ w ैདྷ͸Ұؾʹஈ֊ͷॲཧΛ͍ͯͨ͠ͷͰ೉͔ͬͨ͠ ςελϏϦςΟ

Slide 55

Slide 55 text

final class VenueDetailViewController: UIViewController { enum CellDeclaration: Equatable { case outline(Venue) case sectionHeader(String) case review(Review) case relatedVenue(Venue) } struct Data: CellsDeclarator { var venue: Venue var reviews: [Review] var relatedVenues: [Venue] func declareCells(_ cell: (CellDeclaration) -> Void) { cell(.outline(venue)) if !reviews.isEmpty { cell(.sectionHeader("Reviews")) for review in reviews { cell(.review(review)) } } if !relatedVenues.isEmpty { cell(.sectionHeader("Related Venues")) for relatedVenue in relatedVenues { cell(.relatedVenue(relatedVenue)) } } } } ... } ͜͜ͷ݁Ռͷ<$FMM%FDMBSBUJPO>͸ςετՄೳ extension CellsDeclarator { var cellDeclarations: [CellDeclaration] { var declarations = [] as [CellDeclaration] declareCells { declaration in declarations.append(declaration) } return declarations } }

Slide 56

Slide 56 text

func testEmptyRelatedVenues() { let venue = Venue(photo: nil, name: "Kaminarimon") let review1 = Review(authorImage: nil, authorName: "Yosuke Ishikawa", body: "Foo") let review2 = Review(authorImage: nil, authorName: "Masatake Yamoto", body: "Bar") let data = VenueDetailViewController.Data( venue: venue, reviews: [ review1, review2, ], relatedVenues: []) XCTAssertEqual(data.cellDeclarations, [ .outline(venue), .sectionHeader("Reviews"), .review(review1), .review(review2), ]) }

Slide 57

Slide 57 text

·ͱΊ