Skip to content

Commit

Permalink
Cleanup and fixes for CollectionViewDriver
Browse files Browse the repository at this point in the history
- remove hidden supplementary view hacks, close #47
- fix `SupplementaryViewKind` models, close #56
    - `RegistrationMethod` --> `ViewRegistrationMethod` (and remove nesting, since this will apply to all cells in #18)
    - make `kind` a property of `SupplementaryViewInfo`
- Remove `typealias Section`, only used in one place and doesn't add any value
  • Loading branch information
jessesquires committed Jan 25, 2018
1 parent 766e31a commit 1c2e8d1
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 50 deletions.
1 change: 1 addition & 0 deletions Example/CollectionViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ extension CollectionViewController {
viewInfo: SupplementaryViewInfo(
registrationMethod: .nib(name: "CollectionViewHeaderView", bundle: nil),
reuseIdentifier: "CollectionViewHeaderView",
kind: .header,
accessibilityFormat: "CollectionViewHeaderView"
)
)
Expand Down
4 changes: 4 additions & 0 deletions ReactiveLists.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
258E31D41F0D8F3100D6F324 /* SupplementaryViewInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 258E31D21F0D8F3100D6F324 /* SupplementaryViewInfo.swift */; };
25B1B0B920195F1C0036545F /* CollectionViewDriverDiffingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25B1B0B720195F160036545F /* CollectionViewDriverDiffingTests.swift */; };
25B1B0BA201A53CF0036545F /* Typealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32124A712019312200EE12FC /* Typealiases.swift */; };
32E7A1F4201A7F3500B90EBC /* ViewRegistrationMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32E7A1F3201A7F3500B90EBC /* ViewRegistrationMethod.swift */; };
351B0BB920168E2E0034569D /* CollectionViewCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 351B0BB420168D2E0034569D /* CollectionViewCells.swift */; };
351B0BBA20168E2E0034569D /* CollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 351B0BB320168D2E0034569D /* CollectionViewController.swift */; };
357B96DA201934C50000443F /* CollectionToolCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 357B96D9201934C50000443F /* CollectionToolCell.xib */; };
Expand Down Expand Up @@ -114,6 +115,7 @@
276442FEC917A7E0F32CE5B4 /* Pods-ReactiveLists-ReactiveListsExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactiveLists-ReactiveListsExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ReactiveLists-ReactiveListsExample/Pods-ReactiveLists-ReactiveListsExample.debug.xcconfig"; sourceTree = "<group>"; };
2F35530D29268B112F99A187 /* Pods_ReactiveLists_ReactiveListsExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReactiveLists_ReactiveListsExample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
32124A712019312200EE12FC /* Typealiases.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Typealiases.swift; sourceTree = "<group>"; };
32E7A1F3201A7F3500B90EBC /* ViewRegistrationMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewRegistrationMethod.swift; sourceTree = "<group>"; };
351B0BB320168D2E0034569D /* CollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewController.swift; sourceTree = "<group>"; };
351B0BB420168D2E0034569D /* CollectionViewCells.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCells.swift; sourceTree = "<group>"; };
357B96D9201934C50000443F /* CollectionToolCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CollectionToolCell.xib; sourceTree = "<group>"; };
Expand Down Expand Up @@ -262,6 +264,7 @@
258E31AE1F0D8D9C00D6F324 /* TableViewDriver.swift */,
258E31AF1F0D8D9C00D6F324 /* TableViewModel.swift */,
32124A712019312200EE12FC /* Typealiases.swift */,
32E7A1F3201A7F3500B90EBC /* ViewRegistrationMethod.swift */,
);
path = Sources;
sourceTree = "<group>";
Expand Down Expand Up @@ -616,6 +619,7 @@
files = (
2541B73D1F29A13B002C3090 /* Diffing.swift in Sources */,
258E31B31F0D8D9C00D6F324 /* TableViewDriver.swift in Sources */,
32E7A1F4201A7F3500B90EBC /* ViewRegistrationMethod.swift in Sources */,
258E31B11F0D8D9C00D6F324 /* CollectionViewDriver.swift in Sources */,
258E31B41F0D8D9C00D6F324 /* TableViewModel.swift in Sources */,
258E31D31F0D8F3100D6F324 /* AccessibilityFormats.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Sources/AccessibilityFormats.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public struct SupplementaryAccessibilityFormat: ExpressibleByStringLiteral {
self._format = value
}

public func accessibilityIdentifierForSection(_ section: Section) -> String {
public func accessibilityIdentifierForSection(_ section: Int) -> String {
return self._format.replacingOccurrences(of: "%{section}", with: String(section))
}
}
18 changes: 3 additions & 15 deletions Sources/CollectionViewDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ public class CollectionViewDriver: NSObject, UICollectionViewDataSource, UIColle
private var _shouldDeselectUponSelection: Bool
private var _collectionViewDiffer: CollectionViewDiffCalculator<DiffingKey, DiffingKey>?
private var _didReceiveFirstNonNilValue = false
private static let _hiddenSupplementaryViewIdentifier = "hidden-supplementary-view"

/// Initializes a data source that drives a `UICollectionView` based on a `CollectionViewModel`.
///
Expand Down Expand Up @@ -131,7 +130,6 @@ public class CollectionViewDriver: NSObject, UICollectionViewDataSource, UIColle

private func _collectionViewModelDidChange() {
self._registerSupplementaryViews()
self._registerHiddenSupplementaryViews()

guard let newModel = self.collectionViewModel else {
self.refreshViews()
Expand Down Expand Up @@ -196,10 +194,10 @@ public class CollectionViewDriver: NSObject, UICollectionViewDataSource, UIColle
view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: identifier, for: indexPath)
viewModel.applyViewModelToView(view)
view.accessibilityIdentifier = viewModel.viewInfo?.accessibilityFormat.accessibilityIdentifierForSection(section)
} else {
view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CollectionViewDriver._hiddenSupplementaryViewIdentifier, for: indexPath)
return view
}
return view

fatalError("Invalid supplementary view and layout configuration")
}

public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
Expand Down Expand Up @@ -254,16 +252,6 @@ public class CollectionViewDriver: NSObject, UICollectionViewDataSource, UIColle
}
}

private func _registerHiddenSupplementaryViews() {
// A blank header/footer view class must be registered because `viewForSupplementaryElementOfKind` returns
// a non-optional view and yet the `collectionViewModel` may not specify some headers and footers
[UICollectionElementKindSectionHeader, UICollectionElementKindSectionFooter].forEach {
collectionView.register(UICollectionReusableView.self,
forSupplementaryViewOfKind: $0,
withReuseIdentifier: CollectionViewDriver._hiddenSupplementaryViewIdentifier)
}
}

private func _sizeForSupplementaryViewOfKind(_ elementKind: SupplementaryViewKind, inSection section: Int, collectionViewLayout: UICollectionViewLayout) -> CGSize {
guard let sectionModel = self.collectionViewModel?[section] else {
return CGSize.zero
Expand Down
39 changes: 13 additions & 26 deletions Sources/SupplementaryViewInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,24 @@ import Foundation
import UIKit

public struct SupplementaryViewInfo {
/// The method for registering the supplementary view
public enum RegistrationMethod: Equatable {
/// Class-based views
case viewClass(AnyClass)
/// Nib-based views
case nib(name: String, bundle: Bundle?)

public static func == (lhs: RegistrationMethod, rhs: RegistrationMethod) -> Bool {
switch (lhs, rhs) {
case let (.viewClass(lhsClass), .viewClass(rhsClass)):
return lhsClass == rhsClass
case let (.nib(lhsName, lhsBundle), .nib(rhsName, rhsBundle)):
return lhsName == rhsName && lhsBundle == rhsBundle
default:
return false
}
}
}
public let registrationMethod: ViewRegistrationMethod

public let reuseIdentifier: String

public let kind: SupplementaryViewKind

let registrationMethod: RegistrationMethod
let reuseIdentifier: String
/// `TableViewDataSource` and `CollectionViewDataSource` will automatically apply an `accessibilityIdentifier` to the supplementary view based on this format
let accessibilityFormat: SupplementaryAccessibilityFormat
/// `TableViewDataSource` and `CollectionViewDataSource` will automatically apply
/// an `accessibilityIdentifier` to the supplementary view based on this format
public let accessibilityFormat: SupplementaryAccessibilityFormat

public init(
registrationMethod: RegistrationMethod,
reuseIdentifier: String,
accessibilityFormat: SupplementaryAccessibilityFormat
) {
public init(registrationMethod: ViewRegistrationMethod,
reuseIdentifier: String,
kind: SupplementaryViewKind,
accessibilityFormat: SupplementaryAccessibilityFormat) {
self.registrationMethod = registrationMethod
self.reuseIdentifier = reuseIdentifier
self.kind = kind
self.accessibilityFormat = accessibilityFormat
}
}
Expand Down
2 changes: 0 additions & 2 deletions Sources/Typealiases.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

import Foundation

public typealias Section = Int

public typealias CommitEditingStyleClosure = (UITableViewCellEditingStyle) -> Void
public typealias DidSelectClosure = () -> Void
public typealias DidDeleteClosure = () -> Void
Expand Down
38 changes: 38 additions & 0 deletions Sources/ViewRegistrationMethod.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// PlanGrid
// https://www.plangrid.com
// https://medium.com/plangrid-technology
//
// Documentation
// https://plangrid.github.io/ReactiveLists
//
// GitHub
// https://github.com/plangrid/ReactiveLists
//
// License
// Copyright © 2018-present PlanGrid, Inc.
// Released under an MIT license: https://opensource.org/licenses/MIT
//

import Foundation

/// The method for registering cells and supplementary views
public enum ViewRegistrationMethod {
/// Class-based views
case viewClass(AnyClass)
/// Nib-based views
case nib(name: String, bundle: Bundle?)
}

extension ViewRegistrationMethod: Equatable {
public static func == (lhs: ViewRegistrationMethod, rhs: ViewRegistrationMethod) -> Bool {
switch (lhs, rhs) {
case let (.viewClass(lhsClass), .viewClass(rhsClass)):
return lhsClass == rhsClass
case let (.nib(lhsName, lhsBundle), .nib(rhsName, rhsBundle)):
return lhsName == rhsName && lhsBundle == rhsBundle
default:
return false
}
}
}
7 changes: 1 addition & 6 deletions Tests/CollectionView/CollectionViewDriverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,11 @@ final class CollectionViewDriverTests: XCTestCase {

// Test that header and footer view classes explicitly provided in the view model are registered
let registerCalls = self._collectionView.callsToRegisterClass
XCTAssertEqual(registerCalls.count, 6)
XCTAssertEqual(registerCalls.count, 4)
self._testRegisterClassCallInfo(registerCalls[0], viewClass: HeaderView.self, kind: .header, identifier: "reuse_header+A")
self._testRegisterClassCallInfo(registerCalls[1], viewClass: FooterView.self, kind: .footer, identifier: "reuse_footer+A")
self._testRegisterClassCallInfo(registerCalls[2], viewClass: HeaderView.self, kind: .header, identifier: "reuse_header+D")
self._testRegisterClassCallInfo(registerCalls[3], viewClass: FooterView.self, kind: .footer, identifier: "reuse_footer+D")

// Test that the a blank header and footer view class is registered for hidden headers and footers
// Used for headers and footers that are not explicitly provided in the view model
self._testRegisterClassCallInfo(registerCalls[4], viewClass: UICollectionReusableView.self, kind: .header, identifier: "hidden-supplementary-view")
self._testRegisterClassCallInfo(registerCalls[5], viewClass: UICollectionReusableView.self, kind: .footer, identifier: "hidden-supplementary-view")
}

func testCollectionViewSections() {
Expand Down
1 change: 1 addition & 0 deletions Tests/Fixtures/TestCollectionViewModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ struct TestCollectionViewSupplementaryViewModel: CollectionViewSupplementaryView
self.viewInfo = SupplementaryViewInfo(
registrationMethod: .viewClass(viewKind == .header ? HeaderView.self : FooterView.self),
reuseIdentifier: "reuse_\(kindString)+\(sectionLabel)", // e.g. reuse_header+A
kind: viewKind,
accessibilityFormat: SupplementaryAccessibilityFormat("access_\(kindString)+%{section}")) // e.g. access_header+%{section}
}

Expand Down
1 change: 1 addition & 0 deletions Tests/Fixtures/TestTableViewModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ struct TestHeaderFooterViewModel: TableViewSectionHeaderFooterViewModel {
self.viewInfo = SupplementaryViewInfo(
registrationMethod: .viewClass(viewKind == .header ? HeaderView.self : FooterView.self),
reuseIdentifier: "reuse_\(kindString)+\(label)", // e.g. reuse_header_3
kind: viewKind,
accessibilityFormat: SupplementaryAccessibilityFormat("access_\(kindString)+%{section}")) // e.g. access_header+%{section}
}

Expand Down

0 comments on commit 1c2e8d1

Please sign in to comment.