時代の変化に応じて進化するCollectionView

Cc8ec9594b83b5dc3c7eef345c05dc8d?s=47 shiz
June 28, 2019

 時代の変化に応じて進化するCollectionView

iOS13で新しく導入されたCollectionViewについての内容です。

Cc8ec9594b83b5dc3c7eef345c05dc8d?s=128

shiz

June 28, 2019
Tweet

Transcript

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

  2. ౎಺ͷࣗࣾαʔϏε։ൃձࣾͷ J04ΤϯδχΞ Ћ TIJ[!TU[O TIJ[ ͣ͠ !TIJ[ TU[O TIJ[ ։ൃܦݧݴޠ

    4XJGU,PUMJO+BWBTDSJQU1)1$+BWB(Pʜ
  3. ࣌୅ͷมԽ J04 ݱࡏ

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

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

  6. 6*$PMMFDUJPO7JFX-BZPVU

  7. 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
  8. $PNQPTJUJPOBM-BZPVUT %J⒎BCMF%BUB4PVSDFT ৽͍͠"1*ͷొ৔

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

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

  11. ͭͷཁૉ 4J[F

  12. 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
  13. https://developer.apple.com/documentation/uikit/nscollectionlayoutitem αΠζΛࢦఆ͢Δ ηϧ΍ϔομʔɺϑολʔͳͲ

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

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

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

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

  18. 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Λ࡞੒
  19. 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
  20. 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
  21. 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ʹࢦఆ
  22. 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ͷεΫϩʔϧͷಈ͖Λௐ੔Ͱ͖Δ
  23. %J⒎BCMF%BUB4PVSDF wσʔλͷҰݩ؅ཧ 4JOHMF4PVSDF5SVUI  wϑϨʔϜϫʔΫʹΑΔෆ੔߹ͷ๷ࢭ wࠩ෼ߋ৽ʹΑΔύϑΥʔϚϯεͷ޲্

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

    SectionIdentifierType : Hashable, ItemIdentifierType : Hashable
  26. %FNPΛ͠ͳ͕Β ࣮૷ΛݟͯΈΔ

  27. 6*ͷߋ৽ εςοϓ func performQuery(with filter: String?) { let mountains =

    mountainsController .filteredMountains(with: filter) .sorted { $0.name < $1.name } let snapshot = NSDiffableDataSourceSnapshot<Section, Mountain>() snapshot.appendSections([.main]) snapshot.appendItems(mountains) dataSource.apply(snapshot, animatingDifferences: true) } ͭͷεςοϓΛ౿Ή͚ͩͰઃఆ͕Ͱ͖Δ .PVOUBJOT7JFX$POUSPMMFS 1. ৽͍͠Snapshotͷੜ੒ 2. ߋ৽͍ͨ͠৘ใͷ௥Ճ 3. applyͷݺͼग़͠
  28. 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
  29. %BUB4PVSDFͷઃఆ func configureDataSource() { dataSource = UICollectionViewDiffableDataSource <Section, Item>(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Λ࢖༻͍ͯ͠ͳ͍ ΫϩʔδϟͷதͰηϧ΁ͷ஋ͷઃఆํ๏Λએݴ
  30. ηΫγϣϯ͕ෳ਺͋Δ৔߹ func updateUI(animated: Bool = true) { guard let controller

    = self.wifiController else { return } let configItems = configurationItems.filter { !($0.type == .currentNetwork && !controller.wifiEnabled) } currentSnapshot = NSDiffableDataSourceSnapshot<Section, Item>() 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
  31. ඞཁͳͱ͜Ζ͚ͩߋ৽͢Δ 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
  32. ·ͱΊ wએݴతͰΘ͔Γ΍͍͢"1* wϚϧνϓϥοτϑΥʔϜͰ౷Ұతʹ࢖༻Ͱ͖Δ wϑϨʔϜϫʔΫ͕͋Δఔ౓ॲཧΛ΍ͬͯ͘ΕΔ ͷͰ༨ܭͳෆ۩߹ͷϦεΫ͕ܰݮ wϑϨʔϜϫʔΫʹΑΔॲཧͷ࠷దԽʹΑΔε ϜʔζͳΞχϝʔγϣϯ

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

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