diff --git a/Kickstarter-iOS/DataSources/PillCollectionViewDataSource.swift b/Kickstarter-iOS/DataSources/PillCollectionViewDataSource.swift new file mode 100644 index 0000000000..8a374a05fe --- /dev/null +++ b/Kickstarter-iOS/DataSources/PillCollectionViewDataSource.swift @@ -0,0 +1,22 @@ +import Foundation +import Library +import UIKit + +final class PillCollectionViewDataSource: ValueCellDataSource { + func load(_ values: [String]) { + self.set( + values: values, + cellClass: PillCell.self, + inSection: 0 + ) + } + + override func configureCell(collectionCell cell: UICollectionViewCell, withValue value: Any) { + switch (cell, value) { + case let (cell as PillCell, value as String): + cell.configureWith(value: value) + default: + assertionFailure("Unrecognized (cell, value) combo.") + } + } +} diff --git a/Kickstarter-iOS/DataSources/PillCollectionViewDataSourceTests.swift b/Kickstarter-iOS/DataSources/PillCollectionViewDataSourceTests.swift new file mode 100644 index 0000000000..25655073e8 --- /dev/null +++ b/Kickstarter-iOS/DataSources/PillCollectionViewDataSourceTests.swift @@ -0,0 +1,18 @@ +@testable import Kickstarter_Framework +import Prelude +import XCTest + +final class PillCollectionViewDataSourceTests: XCTestCase { + private let dataSource = PillCollectionViewDataSource() + private let collectionView = UICollectionView( + frame: .zero, + collectionViewLayout: UICollectionViewFlowLayout() + ) + + func testLoadValues() { + self.dataSource.load(["one", "two", "three"]) + + XCTAssertEqual(1, self.dataSource.numberOfSections(in: self.collectionView)) + XCTAssertEqual(3, self.dataSource.collectionView(self.collectionView, numberOfItemsInSection: 0)) + } +} diff --git a/Kickstarter-iOS/Views/Cells/PillCell.swift b/Kickstarter-iOS/Views/Cells/PillCell.swift new file mode 100644 index 0000000000..c22ee71ce1 --- /dev/null +++ b/Kickstarter-iOS/Views/Cells/PillCell.swift @@ -0,0 +1,63 @@ +import Library +import Prelude +import UIKit + +final class PillCell: UICollectionViewCell, ValueCell { + // MARK: - Properties + + private(set) lazy var label = { + UILabel(frame: .zero) + |> \.translatesAutoresizingMaskIntoConstraints .~ false + }() + + // MARK: - Lifecycle + + override init(frame: CGRect) { + super.init(frame: frame) + + _ = self.contentView + |> \.layoutMargins .~ UIEdgeInsets(topBottom: Styles.gridHalf(2), leftRight: Styles.gridHalf(3)) + + _ = (self.label, self.contentView) + |> ksr_addSubviewToParent() + |> ksr_constrainViewToMarginsInParent() + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Styles + + override func bindStyles() { + super.bindStyles() + + _ = self.contentView + |> contentViewStyle + + _ = self.label + |> labelStyle + } + + // MARK: - Configuration + + func configureWith(value: String) { + _ = self.label + |> \.text .~ value + } +} + +// MARK: - Styles + +private let contentViewStyle: ViewStyle = { view in + view + |> checkoutRoundedCornersStyle + |> \.backgroundColor .~ UIColor.ksr_green_500.withAlphaComponent(0.06) +} + +private let labelStyle: LabelStyle = { label in + label + |> \.font .~ UIFont.ksr_footnote().bolded + |> \.numberOfLines .~ 0 + |> \.textColor .~ UIColor.ksr_green_500 +} diff --git a/Kickstarter-iOS/Views/Cells/RewardCell.swift b/Kickstarter-iOS/Views/Cells/RewardCell.swift index a4bcaf9d28..99e760fb4d 100644 --- a/Kickstarter-iOS/Views/Cells/RewardCell.swift +++ b/Kickstarter-iOS/Views/Cells/RewardCell.swift @@ -12,6 +12,7 @@ final class RewardCell: UICollectionViewCell, ValueCell { // MARK: - Properties weak var delegate: RewardCellDelegate? + private let pillDataSource = PillCollectionViewDataSource() private let viewModel: RewardCellViewModelType = RewardCellViewModel() private let baseStackView: UIStackView = { @@ -23,7 +24,6 @@ final class RewardCell: UICollectionViewCell, ValueCell { private let descriptionLabel = UILabel(frame: .zero) private let descriptionStackView = UIStackView(frame: .zero) private let descriptionTitleLabel = UILabel(frame: .zero) - private let includedItemsStackView = UIStackView(frame: .zero) private let includedItemsTitleLabel = UILabel(frame: .zero) private let minimumPriceConversionLabel = UILabel(frame: .zero) @@ -33,6 +33,28 @@ final class RewardCell: UICollectionViewCell, ValueCell { |> \.translatesAutoresizingMaskIntoConstraints .~ false }() + private lazy var pillCollectionView: UICollectionView = { + UICollectionView( + frame: .zero, + collectionViewLayout: PillLayout( + minimumInteritemSpacing: Styles.grid(1), + minimumLineSpacing: Styles.grid(1), + sectionInset: UIEdgeInsets(topBottom: Styles.grid(1)) + ) + ) + |> \.backgroundColor .~ UIColor.white + |> \.contentInsetAdjustmentBehavior .~ UIScrollView.ContentInsetAdjustmentBehavior.always + |> \.dataSource .~ self.pillDataSource + |> \.delegate .~ self + |> \.isHidden .~ true + |> \.translatesAutoresizingMaskIntoConstraints .~ false + }() + + private lazy var pillCollectionViewHeightConstraint: NSLayoutConstraint = { + self.pillCollectionView.heightAnchor.constraint(equalToConstant: 0) + |> \.priority .~ .defaultHigh + }() + private let pledgeButtonLayoutGuide = UILayoutGuide() private let priceStackView = UIStackView(frame: .zero) private let rewardTitleLabel = UILabel(frame: .zero) @@ -50,17 +72,40 @@ final class RewardCell: UICollectionViewCell, ValueCell { private let titleStackView = UIStackView(frame: .zero) + // MARK: - Lifecycle + override init(frame: CGRect) { super.init(frame: frame) self.configureViews() + self.setupConstraints() self.bindViewModel() + + self.pledgeButton.addTarget(self, action: #selector(self.pledgeButtonTapped), for: .touchUpInside) + self.containerView.addGestureRecognizer( + UITapGestureRecognizer(target: self, action: #selector(self.rewardCardTapped)) + ) } required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } + override func layoutSubviews() { + super.layoutSubviews() + + self.updateCollectionViewConstraints() + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + self.updateCollectionViewConstraints() + self.pillCollectionView.reloadData() + } + + // MARK: - Styles + override func bindStyles() { super.bindStyles() @@ -135,6 +180,8 @@ final class RewardCell: UICollectionViewCell, ValueCell { |> titleStackViewStyle } + // MARK: - View model + override func bindViewModel() { super.bindViewModel() @@ -166,6 +213,9 @@ final class RewardCell: UICollectionViewCell, ValueCell { _ = self?.containerView ?|> \.isUserInteractionEnabled .~ isUserInteractionEnabled } + + self.pillDataSource.load(["Ends in 3 days", "16 left"]) + self.pillCollectionView.reloadData() } // MARK: - Private Helpers @@ -194,8 +244,13 @@ final class RewardCell: UICollectionViewCell, ValueCell { _ = (self.pledgeButtonLayoutGuide, self.containerView) |> ksr_addLayoutGuideToView() - let baseSubviews = [ - self.titleStackView, self.rewardTitleLabel, self.includedItemsStackView, self.descriptionStackView + let baseSubviews: [UIView] = [ + self.titleStackView, + self.priceStackView, + self.rewardTitleLabel, + self.includedItemsStackView, + self.descriptionStackView, + self.pillCollectionView ] _ = (baseSubviews, self.baseStackView) @@ -219,28 +274,26 @@ final class RewardCell: UICollectionViewCell, ValueCell { _ = ([self.priceStackView, self.stateImageViewContainer], self.titleStackView) |> ksr_addArrangedSubviewsToStackView() - self.setupConstraints() - - self.pledgeButton.addTarget(self, action: #selector(self.pledgeButtonTapped), for: .touchUpInside) - - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.rewardCardTapped)) - self.containerView.addGestureRecognizer(tapGestureRecognizer) + self.pillCollectionView.register(PillCell.self) } private func setupConstraints() { + // Margins + let containerMargins = self.containerView.layoutMarginsGuide + let contentMargins = self.contentView.layoutMarginsGuide + + // Constraints let containerConstraints = [ self.containerView.widthAnchor.constraint(equalTo: self.contentView.widthAnchor) ] - let containerMargins = self.containerView.layoutMarginsGuide - let baseStackViewConstraints = [ self.baseStackView.leftAnchor.constraint(equalTo: containerMargins.leftAnchor), self.baseStackView.rightAnchor.constraint(equalTo: containerMargins.rightAnchor), self.baseStackView.topAnchor.constraint(equalTo: containerMargins.topAnchor) ] - let imageViewContraints = [ + let stateImageViewContraints = [ self.stateImageView.widthAnchor.constraint(equalToConstant: Styles.grid(3)), self.stateImageView.heightAnchor.constraint(equalTo: self.stateImageView.widthAnchor), self.stateImageView.centerXAnchor.constraint(equalTo: self.stateImageViewContainer.centerXAnchor), @@ -249,14 +302,15 @@ final class RewardCell: UICollectionViewCell, ValueCell { self.stateImageViewContainer.heightAnchor.constraint(equalTo: self.stateImageViewContainer.widthAnchor) ] - let topConstraint = self.pledgeButton.topAnchor - .constraint(equalTo: self.pledgeButtonLayoutGuide.topAnchor) - |> \.priority .~ .defaultLow - - let contentMargins = self.contentView.layoutMarginsGuide + let pillCollectionViewConstraints = [ + self.pillCollectionView.leftAnchor.constraint(equalTo: contentMargins.leftAnchor), + self.pillCollectionView.rightAnchor.constraint(equalTo: contentMargins.rightAnchor), + self.pillCollectionViewHeightConstraint + ] let pledgeButtonConstraints = [ - topConstraint, + self.pledgeButton.topAnchor.constraint(equalTo: self.pledgeButtonLayoutGuide.topAnchor) + |> \.priority .~ .defaultLow, self.pledgeButton.leftAnchor.constraint(equalTo: contentMargins.leftAnchor), self.pledgeButton.rightAnchor.constraint(equalTo: contentMargins.rightAnchor), self.pledgeButton.bottomAnchor.constraint(lessThanOrEqualTo: contentMargins.bottomAnchor), @@ -267,20 +321,28 @@ final class RewardCell: UICollectionViewCell, ValueCell { self.pledgeButtonLayoutGuide.bottomAnchor.constraint(equalTo: containerMargins.bottomAnchor), self.pledgeButtonLayoutGuide.leftAnchor.constraint(equalTo: containerMargins.leftAnchor), self.pledgeButtonLayoutGuide.rightAnchor.constraint(equalTo: containerMargins.rightAnchor), - // swiftlint:disable:next line_length - self.pledgeButtonLayoutGuide.topAnchor.constraint(equalTo: self.baseStackView.bottomAnchor, constant: Styles.grid(3)), + self.pledgeButtonLayoutGuide.topAnchor.constraint( + equalTo: self.baseStackView.bottomAnchor, constant: Styles.grid(3) + ), self.pledgeButtonLayoutGuide.heightAnchor.constraint(equalTo: pledgeButton.heightAnchor) ] NSLayoutConstraint.activate([ containerConstraints, baseStackViewConstraints, - imageViewContraints, + pillCollectionViewConstraints, pledgeButtonConstraints, - pledgeButtonLayoutGuideConstraints + pledgeButtonLayoutGuideConstraints, + stateImageViewContraints ].flatMap { $0 }) } + private func updateCollectionViewConstraints() { + self.pillCollectionView.layoutIfNeeded() + + self.pillCollectionViewHeightConstraint.constant = self.pillCollectionView.contentSize.height + } + fileprivate func load(items: [String]) { _ = self.includedItemsStackView.subviews ||> { $0.removeFromSuperview() } @@ -311,6 +373,8 @@ final class RewardCell: UICollectionViewCell, ValueCell { |> ksr_addArrangedSubviewsToStackView() } + // MARK: - Configuration + internal func configureWith(value: (Project, Either)) { self.viewModel.inputs.configureWith(project: value.0, rewardOrBacking: value.1) } @@ -326,6 +390,21 @@ final class RewardCell: UICollectionViewCell, ValueCell { } } +// MARK: - UICollectionViewDelegate + +extension RewardCell: UICollectionViewDelegate { + func collectionView( + _ collectionView: UICollectionView, + willDisplay cell: UICollectionViewCell, + forItemAt _: IndexPath + ) { + guard let pillCell = cell as? PillCell else { return } + + _ = pillCell.label + |> \.preferredMaxLayoutWidth .~ collectionView.bounds.width + } +} + // MARK: - Styles private let baseRewardLabelStyle: LabelStyle = { label in diff --git a/Kickstarter-iOS/Views/Layouts/PillLayout.swift b/Kickstarter-iOS/Views/Layouts/PillLayout.swift new file mode 100644 index 0000000000..fdc423a6e5 --- /dev/null +++ b/Kickstarter-iOS/Views/Layouts/PillLayout.swift @@ -0,0 +1,54 @@ +import Prelude +import UIKit + +class PillLayout: UICollectionViewFlowLayout { + // MARK: - Lifecycle + + required init( + minimumInteritemSpacing: CGFloat = 0, + minimumLineSpacing: CGFloat = 0, + sectionInset: UIEdgeInsets = .zero + ) { + super.init() + + _ = self + |> \.estimatedItemSize .~ UICollectionViewFlowLayout.automaticSize + |> \.minimumInteritemSpacing .~ minimumInteritemSpacing + |> \.minimumLineSpacing .~ minimumLineSpacing + |> \.sectionInset .~ sectionInset + |> \.sectionInsetReference .~ .fromSafeArea + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Layout + + override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + guard let baseAttributes = super.layoutAttributesForElements(in: rect) else { return nil } + + let layoutAttributes = baseAttributes.compactMap { $0.copy() as? UICollectionViewLayoutAttributes } + + guard scrollDirection == .vertical else { return layoutAttributes } + + // Filter attributes to compute only cell attributes + let cellAttributes = layoutAttributes.filter { $0.representedElementCategory == .cell } + + let dictionary = Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) + + // Group cell attributes by row (cells with same vertical center) and loop on those groups + for (_, attributes) in dictionary { + // Set the initial left inset + var leftInset = sectionInset.left + + // Loop on cells to adjust each cell's origin and prepare leftInset for the next cell + for attribute in attributes { + attribute.frame.origin.x = leftInset + leftInset = attribute.frame.maxX + minimumInteritemSpacing + } + } + + return layoutAttributes + } +} diff --git a/Kickstarter.xcodeproj/project.pbxproj b/Kickstarter.xcodeproj/project.pbxproj index 738e3b04c6..a2d49b0dbb 100644 --- a/Kickstarter.xcodeproj/project.pbxproj +++ b/Kickstarter.xcodeproj/project.pbxproj @@ -95,6 +95,10 @@ 373AB25B222A063500769FC2 /* CreatePasswordViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373AB25A222A063500769FC2 /* CreatePasswordViewModelTests.swift */; }; 373AB25D222A0D8900769FC2 /* PasswordValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373AB25C222A0D8900769FC2 /* PasswordValidation.swift */; }; 373AB25F222A0DAC00769FC2 /* PasswordValidationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373AB25E222A0DAC00769FC2 /* PasswordValidationTests.swift */; }; + 3742036D22DD41BA007AA86E /* PillLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3742036C22DD41BA007AA86E /* PillLayout.swift */; }; + 3742036F22DD45A2007AA86E /* PillCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3742036E22DD45A2007AA86E /* PillCell.swift */; }; + 3742037322DEB67A007AA86E /* PillCollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3742037222DEB67A007AA86E /* PillCollectionViewDataSource.swift */; }; + 3742037522DEB691007AA86E /* PillCollectionViewDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3742037422DEB691007AA86E /* PillCollectionViewDataSourceTests.swift */; }; 374CB94F22C17D4E00B84219 /* CharacterSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374CB94E22C17D4E00B84219 /* CharacterSet.swift */; }; 374CB95122C17D6700B84219 /* CharacterSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374CB95022C17D6700B84219 /* CharacterSetTests.swift */; }; 374F507922614A1000DE6746 /* PledgeFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374F507822614A1000DE6746 /* PledgeFooterView.swift */; }; @@ -1385,6 +1389,10 @@ 373AB25A222A063500769FC2 /* CreatePasswordViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePasswordViewModelTests.swift; sourceTree = ""; }; 373AB25C222A0D8900769FC2 /* PasswordValidation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordValidation.swift; sourceTree = ""; }; 373AB25E222A0DAC00769FC2 /* PasswordValidationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordValidationTests.swift; sourceTree = ""; }; + 3742036C22DD41BA007AA86E /* PillLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillLayout.swift; sourceTree = ""; }; + 3742036E22DD45A2007AA86E /* PillCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillCell.swift; sourceTree = ""; }; + 3742037222DEB67A007AA86E /* PillCollectionViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillCollectionViewDataSource.swift; sourceTree = ""; }; + 3742037422DEB691007AA86E /* PillCollectionViewDataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillCollectionViewDataSourceTests.swift; sourceTree = ""; }; 374CB94E22C17D4E00B84219 /* CharacterSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterSet.swift; sourceTree = ""; }; 374CB95022C17D6700B84219 /* CharacterSetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterSetTests.swift; sourceTree = ""; }; 374F507822614A1000DE6746 /* PledgeFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PledgeFooterView.swift; sourceTree = ""; }; @@ -2521,6 +2529,14 @@ path = Library; sourceTree = ""; }; + 3742036B22DD41A0007AA86E /* Layouts */ = { + isa = PBXGroup; + children = ( + 3742036C22DD41BA007AA86E /* PillLayout.swift */, + ); + path = Layouts; + sourceTree = ""; + }; 775DFADB2162A56D00620CED /* mutations */ = { isa = PBXGroup; children = ( @@ -2704,6 +2720,7 @@ D0D2AB1A1E96444B008D298A /* VideoGridView.swift */, A751A51C1C85EAD8009C5DEA /* Cells */, A751A51D1C85EAD8009C5DEA /* Controllers */, + 3742036B22DD41A0007AA86E /* Layouts */, A73923AA1D272242004524C3 /* Storyboards */, A7D8B67F1DCCD4B9009BF854 /* Transitions */, ); @@ -2756,6 +2773,7 @@ A773531E1D5E8AEF0017E239 /* MostPopularSearchProjectCell.swift */, A7B1EBB21D90496A00BEE8B3 /* NoRewardCell.swift */, A74382041D3458C900040A95 /* PaddingCell.swift */, + 3742036E22DD45A2007AA86E /* PillCell.swift */, A7FA38A31D9068940041FC9C /* PledgeTitleCell.swift */, 9D9F57CB1D131AF200CE81DE /* ProjectActivityBackingCell.swift */, 9D2546F71D23101E0053844D /* ProjectActivityCommentCell.swift */, @@ -3205,6 +3223,8 @@ 015102741F1943D60006C0FC /* MessageThreadsDataSourceTests.swift */, D63BBCF8217F666B007E01F0 /* PaymentMethodsDataSource.swift */, D65E8F8821821EF500AB9412 /* PaymentMethodsDataSourceTests.swift */, + 3742037222DEB67A007AA86E /* PillCollectionViewDataSource.swift */, + 3742037422DEB691007AA86E /* PillCollectionViewDataSourceTests.swift */, 9D9F58141D131D4A00CE81DE /* ProjectActivitiesDataSource.swift */, A7ED200F1E83229E00BFFA01 /* ProjectActivitiesDataSourceTests.swift */, A73EF7091DCC17DD008FDBE5 /* ProjectNavigatorPagesDataSource.swift */, @@ -4823,6 +4843,7 @@ 776B6F70215183A400AB0652 /* ChangeEmailViewController.swift in Sources */, 0170E7701D25C55200E2CCE4 /* ProjectActivityCommentCell.swift in Sources */, 9D14FF901D133351005F4ABB /* ProjectActivityNegativeStateChangeCell.swift in Sources */, + 3742036D22DD41BA007AA86E /* PillLayout.swift in Sources */, 015A07461D247564007AE210 /* UpdateDraftViewController.swift in Sources */, 7720BA6E22DE79BF0071FDA1 /* UIViewController+Presentation.swift in Sources */, 7793B16D21077AEB007857C0 /* SettingsHeaderView.swift in Sources */, @@ -4845,6 +4866,7 @@ 59019FBA1D21ABD200EAEC9D /* DashboardReferrerRowStackView.swift in Sources */, 015102AD1F1947C50006C0FC /* MessageThreadsDataSource.swift in Sources */, 379CFFFF2242DAF900F6F0C2 /* UIImageView+URL.swift in Sources */, + 3742036F22DD45A2007AA86E /* PillCell.swift in Sources */, 59AE35E21D67643100A310E6 /* DiscoveryPostcardCell.swift in Sources */, A7FA38A41D9068940041FC9C /* PledgeTitleCell.swift in Sources */, 37096C3422BC24DD003D1F40 /* UIFeedbackGeneratorType.swift in Sources */, @@ -4979,6 +5001,7 @@ 77EFBAE52268E01400DA5C3C /* RewardCell.swift in Sources */, 77FD8B46216D6245000A95AC /* LoadingBarButtonItemView.swift in Sources */, 9D14FF8E1D133351005F4ABB /* ProjectActivityEmptyStateCell.swift in Sources */, + 3742037322DEB67A007AA86E /* PillCollectionViewDataSource.swift in Sources */, 017508161D67A4E300BB1863 /* DiscoveryNavigationHeaderViewController.swift in Sources */, 014D625B1E6E20BB0033D2BD /* BackerDashboardProjectsDataSource.swift in Sources */, A78012651D2EEA620027396E /* ReferralChartView.swift in Sources */, @@ -5037,6 +5060,7 @@ 3767EDAF22CFFF010088E8E4 /* ShippingRulesTableViewControllerTests.swift in Sources */, 37DEC2242257CB650051EF9B /* PledgeViewControllerTests.swift in Sources */, A7ED20161E83229E00BFFA01 /* DiscoveryPagesDataSourceTests.swift in Sources */, + 3742037522DEB691007AA86E /* PillCollectionViewDataSourceTests.swift in Sources */, 77EFBAE92268E3D200DA5C3C /* RewardsCollectionViewDataSourceTests.swift in Sources */, A7ED201D1E8322EC00BFFA01 /* TestCase.swift in Sources */, A7ED20111E83229E00BFFA01 /* ActivitiesDataSourceTests.swift in Sources */, diff --git a/Library/DataSource/UICollectionView-Extensions.swift b/Library/DataSource/UICollectionView-Extensions.swift index 0e357eff1a..40421d308e 100644 --- a/Library/DataSource/UICollectionView-Extensions.swift +++ b/Library/DataSource/UICollectionView-Extensions.swift @@ -1,8 +1,10 @@ import UIKit public extension UICollectionView { + // MARK: - Registration + func registerCellClass(_ cellClass: CellClass.Type) { - self.register(cellClass, forCellWithReuseIdentifier: cellClass.description()) + self.register(cellClass, forCellWithReuseIdentifier: classNameWithoutModule(cellClass)) } func register(_ cellClass: T.Type) { @@ -10,15 +12,16 @@ public extension UICollectionView { } func registerCellNibForClass(_ cellClass: AnyClass) { - let classNameWithoutModule = cellClass - .description() - .components(separatedBy: ".") - .dropFirst() - .joined(separator: ".") + let className = classNameWithoutModule(cellClass) + + self.register(UINib(nibName: className, bundle: nil), forCellWithReuseIdentifier: className) + } + + // MARK: - Reuse - self.register( - UINib(nibName: classNameWithoutModule, bundle: nil), - forCellWithReuseIdentifier: classNameWithoutModule - ) + func dequeueReusableCell(withClass cellClass: UICollectionViewCell.Type, for indexPath: IndexPath) + -> UICollectionViewCell { + let className = classNameWithoutModule(cellClass) + return self.dequeueReusableCell(withReuseIdentifier: className, for: indexPath) } } diff --git a/Library/DataSource/UITableView-Extensions.swift b/Library/DataSource/UITableView-Extensions.swift index 693f5f5e03..e458d081d3 100644 --- a/Library/DataSource/UITableView-Extensions.swift +++ b/Library/DataSource/UITableView-Extensions.swift @@ -1,44 +1,34 @@ import UIKit public extension UITableView { + // MARK: - Registration + func registerHeaderFooterClass(_ headerFooterClass: UITableViewHeaderFooterView.Type) { - let className = self.classNameWithoutModule(headerFooterClass) + let className = classNameWithoutModule(headerFooterClass) self.register(headerFooterClass, forHeaderFooterViewReuseIdentifier: className) } func registerCellClass(_ cellClass: UITableViewCell.Type) { - let className = self.classNameWithoutModule(cellClass) + let className = classNameWithoutModule(cellClass) self.register(cellClass, forCellReuseIdentifier: className) } - // MARK: - Nibs - func registerCellNibForClass(_ cellClass: AnyClass) { - let className = self.classNameWithoutModule(cellClass) + let className = classNameWithoutModule(cellClass) self.register(UINib(nibName: className, bundle: nil), forCellReuseIdentifier: className) } - // Reuse + // MARK: - Reuse func dequeueReusableHeaderFooterView(withClass headerFooterClass: UITableViewHeaderFooterView.Type) -> UITableViewHeaderFooterView? { - let className = self.classNameWithoutModule(headerFooterClass) + let className = classNameWithoutModule(headerFooterClass) return self.dequeueReusableHeaderFooterView(withIdentifier: className) } func dequeueReusableCell(withClass cellClass: UITableViewCell.Type, for indexPath: IndexPath) -> UITableViewCell { - let className = self.classNameWithoutModule(cellClass) + let className = classNameWithoutModule(cellClass) return self.dequeueReusableCell(withIdentifier: className, for: indexPath) } - - // MARK: - Functions - - private func classNameWithoutModule(_ klass: AnyClass) -> String { - return klass - .description() - .components(separatedBy: ".") - .dropFirst() - .joined(separator: ".") - } } diff --git a/Library/SharedFunctions.swift b/Library/SharedFunctions.swift index c0d60dda75..4f6f9a368f 100644 --- a/Library/SharedFunctions.swift +++ b/Library/SharedFunctions.swift @@ -187,3 +187,11 @@ public func defaultShippingRule(fromShippingRules shippingRules: [ShippingRule]) return shippingRuleInUSA ?? shippingRules.first } + +internal func classNameWithoutModule(_ klass: AnyClass) -> String { + return klass + .description() + .components(separatedBy: ".") + .dropFirst() + .joined(separator: ".") +}