From 033cf878a656abe1538d4f59e079e1207125d9ec Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 3 Jul 2019 13:18:27 -0400 Subject: [PATCH] carousel --- MVMCoreUI.xcodeproj/project.pbxproj | 8 ++ MVMCoreUI/Molecules/Carousel.swift | 128 ++++++++++++++++++ .../MoleculeCollectionViewCell.swift | 65 +++++++++ .../MVMCoreUIMoleculeMappingObject.m | 4 +- 4 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 MVMCoreUI/Molecules/Carousel.swift create mode 100644 MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 04c2b811..afe89464 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -162,6 +162,8 @@ D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A514662213885800345BFB /* StandardHeaderView.swift */; }; D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; }; D2A638FD22CA98280052ED1F /* HeadlineBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A638FC22CA98280052ED1F /* HeadlineBody.swift */; }; + D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A6390022CBB1820052ED1F /* Carousel.swift */; }; + D2A6390522CBCE160052ED1F /* MoleculeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */; }; D2C5001821F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h in Headers */ = {isa = PBXBuildFile; fileRef = D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */; }; D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */; }; @@ -335,6 +337,8 @@ D2A514662213885800345BFB /* StandardHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardHeaderView.swift; sourceTree = ""; }; D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerViewController.swift; sourceTree = ""; }; D2A638FC22CA98280052ED1F /* HeadlineBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBody.swift; sourceTree = ""; }; + D2A6390022CBB1820052ED1F /* Carousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Carousel.swift; sourceTree = ""; }; + D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeCollectionViewCell.swift; sourceTree = ""; }; D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIViewControllerMappingObject.h; sourceTree = ""; }; D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIViewControllerMappingObject.m; sourceTree = ""; }; D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUIDelegateObject.swift; sourceTree = ""; }; @@ -475,6 +479,8 @@ B8200E182281DC1A007245F4 /* ProgressBarWithLabel.swift */, D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */, D2A638FC22CA98280052ED1F /* HeadlineBody.swift */, + D2A6390022CBB1820052ED1F /* Carousel.swift */, + D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, ); path = Molecules; sourceTree = ""; @@ -951,6 +957,8 @@ 01DF55E021F8FAA800CC099B /* MFTextFieldListView.swift in Sources */, D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */, D29DF2C921E7BFC6003B2FB9 /* MFSizeObject.m in Sources */, + D2A6390522CBCE160052ED1F /* MoleculeCollectionViewCell.swift in Sources */, + D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */, D29DF2C721E7BF57003B2FB9 /* MFTabBarInteractor.m in Sources */, 016A1071228122180009D605 /* SwitchLineItem.swift in Sources */, D29DF29521E7ADB8003B2FB9 /* ProgrammaticScrollViewController.m in Sources */, diff --git a/MVMCoreUI/Molecules/Carousel.swift b/MVMCoreUI/Molecules/Carousel.swift new file mode 100644 index 00000000..3333855b --- /dev/null +++ b/MVMCoreUI/Molecules/Carousel.swift @@ -0,0 +1,128 @@ +// +// Carousel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 7/2/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class Carousel: ViewConstrainingView { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + var currentIndex = 1 + var numberOfCards = 0 + var molecules: [[AnyHashable : Any]]? + + var collectionViewHeight: NSLayoutConstraint? + + open override func setupView() { + super.setupView() + guard collectionView.superview == nil else { + return + } + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.dataSource = self + collectionView.delegate = self + collectionView.showsHorizontalScrollIndicator = false + collectionView.backgroundColor = .clear + addSubview(collectionView) + pinView(toSuperView: collectionView) + + collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300) + collectionViewHeight?.isActive = true + } + + /// Registers the cells with the collection view + func registerCells(with json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) { + if let molecules = json?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] { + for molecule in molecules { + if let info = getMoleculeInfo(with: molecule, delegateObject: delegateObject) { + collectionView.register(info.class, forCellWithReuseIdentifier: info.identifier) + } + } + } + } + + /// Updates the layout being used + func setupLayout(with json:[AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.minimumLineSpacing = json?["spacing"] as? CGFloat ?? 0 + layout.minimumInteritemSpacing = 0 + //layout.itemSize = CGSize(width: 300, height: 200) + collectionView.collectionViewLayout = layout + } + + func prepareMolecules(_ json: [AnyHashable : Any]?) { + guard let newMolecules = json?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] else { + numberOfCards = 0 + molecules = nil + return + } + + numberOfCards = newMolecules.count + molecules = newMolecules + if json?.boolForKey("loop") ?? false && newMolecules.count > 2 { + // Sets up the row data with a buffer cell on each side (for illusion of endless scroll... also has one more buffer cell on right since we can peek that cell). + molecules?.insert(newMolecules.last!, at: 0) + molecules?.append(newMolecules.first!) + molecules?.append(newMolecules[1]) + } + } + + open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + collectionViewHeight?.constant = json?.optionalCGFloatForKey("height") ?? 300 + + registerCells(with: json, delegateObject: delegateObject) + setupLayout(with: json, delegateObject: delegateObject) + prepareMolecules(json) + collectionView.reloadData() + + // Go to starting cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. + collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: .left, animated: false) + collectionView.layoutIfNeeded() + } + + open override func setAsMolecule() { + super.setAsMolecule() + updateViewHorizontalDefaults = false + } + + // MARK: - Convenience + /// Returns the (identifier, class) of the molecule for the given map. + func getMoleculeInfo(with molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> (identifier: String, class: AnyClass, molecule: [AnyHashable: Any])? { + guard let molecule = molecule, + let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule), + let moleculeName = moleculeClass.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) else { + return nil + } + return (moleculeName, moleculeClass, molecule) + } +} + +extension Carousel: UICollectionViewDelegate { + +} + +extension Carousel: UICollectionViewDataSource { + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return molecules?.count ?? 0 + } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let molecule = molecules?[indexPath.row], + let moleculeInfo = getMoleculeInfo(with: molecule, delegateObject: nil) else { + return UICollectionViewCell() + } + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: moleculeInfo.identifier, for: indexPath) + if let protocolCell = cell as? MVMCoreUIMoleculeViewProtocol { + protocolCell.reset?() + protocolCell.setWithJSON(moleculeInfo.molecule, delegateObject: nil, additionalData: nil) + protocolCell.updateView(collectionView.bounds.width) + } + return cell + } +} diff --git a/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift b/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift new file mode 100644 index 00000000..d28819ac --- /dev/null +++ b/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift @@ -0,0 +1,65 @@ +// +// MoleculeCollectionViewCell.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 7/2/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeViewProtocol, MoleculeListCellProtocol { + open var molecule: (UIView & MVMCoreUIMoleculeViewProtocol)? + + public override init(frame: CGRect) { + super.init(frame: .zero) + setupView() + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupView() + } + + public func setupView() { + } + + public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + guard let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule) else { + return + } + if molecule == nil { + if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) { + contentView.addSubview(moleculeView) + let standardConstraints = (moleculeView as? MVMCoreUIViewConstrainingProtocol)?.useStandardConstraints?() ?? true + NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: moleculeView, useMargins: standardConstraints).values)) + if standardConstraints { + let constraint = contentView.heightAnchor.constraint(equalToConstant: 80) + constraint.priority = .defaultLow + constraint.isActive = true + } + molecule = moleculeView + } + } else { + molecule?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) + } + if let castView = molecule as? MVMCoreUIViewConstrainingProtocol { + let standardConstraints = castView.useStandardConstraints?() ?? true + castView.shouldSetHorizontalMargins?(!standardConstraints) + castView.shouldSetVerticalMargins?(!standardConstraints) + } + + backgroundColor = molecule?.backgroundColor + } + + public static func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { + guard let molecule = molecule?.optionalDictionaryForKey(KeyMolecule) else { + return nil + } + return MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) + } + + public func updateView(_ size: CGFloat) { + + } +} diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 67279c6e..61230208 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -48,7 +48,9 @@ @"image": MFLoadImageView.class, @"leftRightLabelView": LeftRightLabelView.class, @"moduleMolecule": ModuleMolecule.class, - @"headlineBody": HeadlineBody.class + @"headlineBody": HeadlineBody.class, + @"carousel": Carousel.class, + @"carouselItem": MoleculeCollectionViewCell.class } mutableCopy]; }); return mapping;