Slide 1

Slide 1 text

࣌୅ͷมԽʹԠͯ͡ ਐԽ͢ΔCollectionView 30110/(*TXJGU

Slide 2

Slide 2 text

౎಺ͷࣗࣾαʔϏε։ൃձࣾͷ J04ΤϯδχΞЋ TIJ[!TU[O TIJ[ ͣ͠ !TIJ[ TU[O TIJ[ ։ൃܦݧݴޠ 4XJGU,PUMJO+BWBTDSJQU1)1$+BWB(Pʜ

Slide 3

Slide 3 text

࣌୅ͷมԽ J04 ݱࡏ

Slide 4

Slide 4 text

ϋʔυ΢ΣΞͷਐԽɺωοτϫʔΫ଎౓ͷ޲্ Ұͭͷը໘Ͱදࣔ͢Δ߲໨ͷ૿Ճ ϨΠΞ΢τͷෳࡶԽ σʔλ؅ཧͷෳࡶԽ දࣔ͢Δσʔλͷऔಘํ๏ͷଟ༷Խ

Slide 5

Slide 5 text

6*$PMMFDUJPO7JFX'MPX-BZPVU 6*$PMMFDUJPO7JFXͷొ৔

Slide 6

Slide 6 text

6*$PMMFDUJPO7JFX-BZPVU

Slide 7

Slide 7 text

QFSGPSN#BUDI6QEBUFT // Animate all other update types together. collectionView.performBatchUpdates({ var deletes = [Int]() var inserts = [(person:Person, index:Int)]() for update in remoteUpdates { switch update { case let .delete(index): collectionView.deleteItems(at: [IndexPath(item: index, section: 0)]) deletes.append(index) case let .insert(person, index): collectionView.insertItems(at: [IndexPath(item: index, section: 0)]) inserts.append((person, index)) case let .move(fromIndex, toIndex): // Updates that move a person are split into an addition and a deletion. collectionView.moveItem(at: IndexPath(item: fromIndex, section: 0), to: IndexPath(item: toIndex, section: 0)) deletes.append(fromIndex) inserts.append((people[fromIndex], toIndex)) default: break } } // Apply deletions in descending order. for deletedIndex in deletes.sorted().reversed() { people.remove(at: deletedIndex) } // Apply insertions in ascending order. let sortedInserts = inserts.sorted(by: { (personA, personB) -> Bool in return personA.index <= personB.index }) for insertion in sortedInserts { people.insert(insertion.person, at: insertion.index) } // The update button is enabled only if the list still has people in it. navigationItem.rightBarButtonItem?.isEnabled = !people.isEmpty }) IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOVJLJUVJDPMMFDUJPOWJFXDVTUPNJ[JOH@DPMMFDUJPO@WJFX@MBZPVUT DIBOHFTMBUFTU@NJOPS ͜ͷॱ൪Ͱͳ͍ͱ /4*ODPOTJTUFODZ&YDFQUJPO

Slide 8

Slide 8 text

$PNQPTJUJPOBM-BZPVUT %J⒎BCMF%BUB4PVSDFT ৽͍͠"1*ͷొ৔

Slide 9

Slide 9 text

wએݴతͳ"1* wϚϧνϓϥοτϑΥʔϜରԠ w͋Δఔ౓ϑϨʔϜϫʔΫ͕؅ཧ wখ͞ͳ෦඼Λ૊Έ߹ΘͤΔ $PNQPTJUJPO ڞ௨తͳಛ௃

Slide 10

Slide 10 text

$PNQPTJUJPOBM-BZPVUT wෳࡶͳϨΠΞ΢τΛγϯϓϧʹߏஙͰ͖Δ w͜Ε·ͰࣗલͰ࣮૷͍ͯͨ͠෦෼ΛϑϨʔ ϜϫʔΫ͕୲ͬͯ͘ΕΔ wͭͷϝΠϯཁૉͰߏ੒͢Δ

Slide 11

Slide 11 text

ͭͷཁૉ 4J[F

Slide 12

Slide 12 text

class NSCollectionLayoutDimension { class func fractionalWidth(_ fractionalWidth: CGFloat) -> Self class func fractionalHeight(_ fractionalHeight: CGFloat) -> Self class func absolute(_ absoluteDimension: CGFloat) -> Self class func estimated(_ estimatedDimension: CGFloat) -> Self } ਌ͷίϯϙʔωϯτͷ αΠζʹର͢Δׂ߹Ͱࢦఆ ϙΠϯτͰࢦఆ /4$PMMFDUJPO-BZPVU4J[F https://developer.apple.com/documentation/uikit/nscollectionlayoutsize

Slide 13

Slide 13 text

https://developer.apple.com/documentation/uikit/nscollectionlayoutitem αΠζΛࢦఆ͢Δ ηϧ΍ϔομʔɺϑολʔͳͲ

Slide 14

Slide 14 text

*UFNͷͻͱ͔ͨ·Γ ϨΠΞ΢τͷجຊ୯Ґ https://developer.apple.com/documentation/uikit/nscollectionlayoutgroup

Slide 15

Slide 15 text

Ұͭͷ(SPVQΛࢦఆ (SPVQʹ௥Ճ৘ใΛࢦఆ https://developer.apple.com/documentation/uikit/nscollectionlayoutsection

Slide 16

Slide 16 text

https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayout https://developer.apple.com/documentation/appkit/nscollectionviewcompositionallayout

Slide 17

Slide 17 text

%FNPΛ͠ͳ͕Β ࣮૷ΛݟͯΈΔ

Slide 18

Slide 18 text

private func createLayout() -> UICollectionViewLayout { let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)) let item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(44)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) let layout = UICollectionViewCompositionalLayout(section: section) return layout } ᶃ4J[FΛܾΊΔ -JTU7JFX$POUSPMMFS ᶄ4J[FΛࢦఆͯ͠*UFNΛ࡞Δ ᶅ4J[Fͱ*UFNΛࢦఆͯ͠(SPVQΛ࡞੒ ᶆ(SPVQΛࢦఆͯ͠4FDUJPOΛ࡞੒ ᶇ4FDUJPOΛࢦఆͯ͠-BZPVUΛ࡞੒

Slide 19

Slide 19 text

private func createLayout() -> UICollectionViewLayout { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(0.2), heightDimension: .fractionalHeight(1.0)) let item = NSCollectionLayoutItem(layoutSize: itemSize) // add for each item allocate same box item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(0.2)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) let layout = UICollectionViewCompositionalLayout(section: section) return layout } ̍ͭͷάϧʔϓʹॎ͸ˋɺԣ͸ˋͷαΠζͰ*UFNΛฒ΂Δ (SJE7JFX$POUSPMMFS

Slide 20

Slide 20 text

func createLayout() -> UICollectionViewLayout { let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in guard let sectionLayoutKind = SectionLayoutKind(rawValue: sectionIndex) else { return nil } let columns = sectionLayoutKind.columnCount // The `group` auto-calculates the actual item width to make // the requested number of `columns` fit, so this `widthDimension` will be ignored. let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)) let item = NSCollectionLayoutItem(layoutSize: itemSize) item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2) let groupHeight = columns == 1 ? NSCollectionLayoutDimension.absolute(44) : NSCollectionLayoutDimension.fractionalWidth(0.2) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: groupHeight) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: columns) let section = NSCollectionLayoutSection(group: group) section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20) return section } return layout } 4FDUJPOʹԠͯ͡ΧϥϜ਺Λมߋ DistinctSectionsViewController

Slide 21

Slide 21 text

let badgeAnchor = NSCollectionLayoutAnchor( edges: [.top, .trailing], fractionalOffset: CGPoint(x: 0.3, y: -0.3)) let badgeSize = NSCollectionLayoutSize(widthDimension: .absolute(20), heightDimension: .absolute(20)) let badge = NSCollectionLayoutSupplementaryItem( layoutSize: badgeSize, elementKind: ViewController.badgeElementKind, containerAnchor: badgeAnchor) let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.25), heightDimension: .fractionalHeight(1.0)) let item = NSCollectionLayoutItem(layoutSize: itemSize, supplementaryItems: [badge]) /4$PMMFDUJPO-BZPVU"ODIPS Ͳ͜ʹόονΛஔ͘ͷ͔ΛܾΊ͍ͯΔ ItemBadgeSupplementaryViewController "badge-element-kind" ϔομʔ΍ϑολʔ͸ NSCollectionLayoutBoundarySupplementaryItem Itemʹࢦఆ

Slide 22

Slide 22 text

func createLayout() -> UICollectionViewLayout { let layout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex:Int, layoutEnvironment:NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in let leadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(0.7), heightDimension: .fractionalHeight(1.0))) leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10) let trailingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.3))) trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10) let trailingGroup = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(0.3), heightDimension: .fractionalHeight(1.0)), subitem: trailingItem, count: 2) let containerGroupFractionalWidth = orthogonallyScrolls ? CGFloat(0.85) : CGFloat(1.0) let groupSize = NSCollectionLayoutGroup.horizontal( layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(containerGroupFractionalWidth), heightDimension: .fractionalHeight(0.4)) let containerGroup = NSCollectionLayoutGroup.horizontal( layoutSize: groupSize, subitems: [leadingItem, trailingGroup]) let section = NSCollectionLayoutSection(group: containerGroup) section.orthogonalScrollingBehavior = orthogonalScrollingBehavior() return section }, configuration: config) return layout } (SPVQͷதʹ(SPVQΛೖΕΔ OrthogonalScrollingViewController ͜ΕߦͰωετ͞Εͨ(SPVQͷεΫϩʔϧͷಈ͖Λௐ੔Ͱ͖Δ

Slide 23

Slide 23 text

%J⒎BCMF%BUB4PVSDF wσʔλͷҰݩ؅ཧ 4JOHMF4PVSDF5SVUI wϑϨʔϜϫʔΫʹΑΔෆ੔߹ͷ๷ࢭ wࠩ෼ߋ৽ʹΑΔύϑΥʔϚϯεͷ޲্

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

/4%J⒎BCMF%BUB4PVSDF4OBQTIPU w6*ͷঢ়ଶΛද͢།Ұͷଘࡏ
 4JOHMF4PVSDFPG5SVUI w*OEFY1BUIΛ࢖Θͳ͍ w*EFOUJpFS͕؊ class NSDiffableDataSourceSnapshot where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable

Slide 26

Slide 26 text

%FNPΛ͠ͳ͕Β ࣮૷ΛݟͯΈΔ

Slide 27

Slide 27 text

6*ͷߋ৽ εςοϓ func performQuery(with filter: String?) { let mountains = mountainsController .filteredMountains(with: filter) .sorted { $0.name < $1.name } let snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) snapshot.appendItems(mountains) dataSource.apply(snapshot, animatingDifferences: true) } ͭͷεςοϓΛ౿Ή͚ͩͰઃఆ͕Ͱ͖Δ .PVOUBJOT7JFX$POUSPMMFS 1. ৽͍͠Snapshotͷੜ੒ 2. ߋ৽͍ͨ͠৘ใͷ௥Ճ 3. applyͷݺͼग़͠

Slide 28

Slide 28 text

4FDUJPO *UFN struct Mountain: Hashable { let name: String let height: Int let identifier = UUID() func hash(into hasher: inout Hasher) { hasher.combine(identifier) } static func == (lhs: Mountain, rhs: Mountain) -> Bool { return lhs.identifier == rhs.identifier } } enum Section: CaseIterable { case main } .PVOUBJOT7JFX$POUSPMMFS

Slide 29

Slide 29 text

%BUB4PVSDFͷઃఆ func configureDataSource() { dataSource = UICollectionViewDiffableDataSource (collectionView: collectionView) { (collectionView: UICollectionView, indexPath: IndexPath, item: Item) -> UICollectionViewCell? in guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: Cell.reuseIdentifier, for: indexPath) as? Cell else { fatalError("Cannot create new cell") } cell.label.text = item.name return cell } } *OEFY1BUIΛ࢖༻͍ͯ͠ͳ͍ ΫϩʔδϟͷதͰηϧ΁ͷ஋ͷઃఆํ๏Λએݴ

Slide 30

Slide 30 text

ηΫγϣϯ͕ෳ਺͋Δ৔߹ func updateUI(animated: Bool = true) { guard let controller = self.wifiController else { return } let configItems = configurationItems.filter { !($0.type == .currentNetwork && !controller.wifiEnabled) } currentSnapshot = NSDiffableDataSourceSnapshot() currentSnapshot.appendSections([.config]) currentSnapshot.appendItems(configItems, toSection: .config) if controller.wifiEnabled { let sortedNetworks = controller.availableNetworks.sorted { $0.name < $1.name } let networkItems = sortedNetworks.map { Item(network: $0) } currentSnapshot.appendSections([.networks]) currentSnapshot.appendItems(networkItems, toSection: .networks) } self.dataSource.apply(currentSnapshot, animatingDifferences: animated) } ηΫγϣϯ͝ͱʹ஋Λઃఆ͢Δ 8J'J4FUUJOHT7JFX$POUSPMMFS

Slide 31

Slide 31 text

ඞཁͳͱ͜Ζ͚ͩߋ৽͢Δ func performSortStep() { let updatedSnapshot = dataSource.snapshot() updatedSnapshot.sectionIdentifiers.forEach { let section = $0 if !section.isSorted { section.sortNext() let items = section.values updatedSnapshot.deleteItems(items) updatedSnapshot.appendItems(items, toSection: section) sectionCountNeedingSort += 1 } } var shouldReset = false var delay = 125 if sectionCountNeedingSort > 0 { dataSource.apply(updatedSnapshot) } else { delay = 1000 shouldReset = true } } ݱࡏͷ4OBQTIPUͷঢ়ଶΛऔಘ ඞཁͳͱ͜Ζ͚ͩߋ৽Λ͢Δ *OEFY1BUIͷ؅ཧ͕ෆཁͳͷͰ ෆ੔߹͕ى͖Δ͜ͱ͕ͳ͍ *OTFSUJPO4PSU7JFX$POUSPMMFS

Slide 32

Slide 32 text

·ͱΊ wએݴతͰΘ͔Γ΍͍͢"1* wϚϧνϓϥοτϑΥʔϜͰ౷Ұతʹ࢖༻Ͱ͖Δ wϑϨʔϜϫʔΫ͕͋Δఔ౓ॲཧΛ΍ͬͯ͘ΕΔ ͷͰ༨ܭͳෆ۩߹ͷϦεΫ͕ܰݮ wϑϨʔϜϫʔΫʹΑΔॲཧͷ࠷దԽʹΑΔε ϜʔζͳΞχϝʔγϣϯ

Slide 33

Slide 33 text

͜Ε͔Β஌Γ͍ͨ͜ͱ wΧελϚΠζ͸Ͳ͜·ͰͰ͖Δʁ w4XJGU6*ͱͷ࿈ܞ ͓ޓ͍ͷ࢖͍෼͚ʁ

Slide 34

Slide 34 text

͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ 2JJUBͰ΋ಉ಺༰ͷهࣄΛࡌ͍ͤͯ·͢ IUUQTRJJUBDPNTIJ[JUFNTBBCGFE