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

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

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

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

[email protected]

June 28, 2019
Tweet

Other Decks in Programming

Transcript

  1. ࣌୅ͷมԽʹԠͯ͡
    ਐԽ͢ΔCollectionView

    30110/(*TXJGU

    View Slide

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

    !TIJ[
    TU[O TIJ[

    ։ൃܦݧݴޠ
    4XJGU,PUMJO+BWBTDSJQU1)1$+BWB(Pʜ

    View Slide

  3. ࣌୅ͷมԽ
    J04
    ݱࡏ

    View Slide

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

    View Slide

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

    View Slide

  6. 6*$PMMFDUJPO7JFX-BZPVU

    View Slide

  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[[email protected]@[email protected] [email protected]
    ͜ͷॱ൪Ͱͳ͍ͱ
    /4*ODPOTJTUFODZ&YDFQUJPO

    View Slide

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

    View Slide

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

    ڞ௨తͳಛ௃

    View Slide

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

    View Slide

  11. ͭͷཁૉ
    4J[F

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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Λ࡞੒

    View Slide

  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

    View Slide

  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

    View Slide

  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ʹࢦఆ

    View Slide

  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ͷεΫϩʔϧͷಈ͖Λௐ੔Ͱ͖Δ

    View Slide

  23. %J⒎BCMF%BUB4PVSDF
    wσʔλͷҰݩ؅ཧ 4JOHMF4PVSDF5SVUI

    wϑϨʔϜϫʔΫʹΑΔෆ੔߹ͷ๷ࢭ
    wࠩ෼ߋ৽ʹΑΔύϑΥʔϚϯεͷ޲্

    View Slide

  24. View Slide

  25. /4%J⒎BCMF%BUB4PVSDF4OBQTIPU
    w6*ͷঢ়ଶΛද͢།Ұͷଘࡏ

    4JOHMF4PVSDFPG5SVUI

    w*OEFY1BUIΛ࢖Θͳ͍
    w*EFOUJpFS͕؊
    class NSDiffableDataSourceSnapshotItemIdentifierType>
    where SectionIdentifierType : Hashable,
    ItemIdentifierType : Hashable

    View Slide

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

    View Slide

  27. 6*ͷߋ৽ εςοϓ

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

    View Slide

  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

    View Slide

  29. %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Λ࢖༻͍ͯ͠ͳ͍
    ΫϩʔδϟͷதͰηϧ΁ͷ஋ͷઃఆํ๏Λએݴ

    View Slide

  30. ηΫγϣϯ͕ෳ਺͋Δ৔߹
    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

    View Slide

  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

    View Slide

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

    View Slide

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


    View Slide

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

    View Slide