From 033cf878a656abe1538d4f59e079e1207125d9ec Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 3 Jul 2019 13:18:27 -0400 Subject: [PATCH 01/12] 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; From 33020844dc414336be7b81a5138e62e17d41acdd Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 11 Jul 2019 11:54:14 -0400 Subject: [PATCH 02/12] carousel --- MVMCoreUI.xcodeproj/project.pbxproj | 12 + MVMCoreUI/MVMCoreUI.h | 2 + MVMCoreUI/Molecules/Carousel.swift | 394 ++++++++++++++--- MVMCoreUI/Molecules/MVMCoreUIPageControl.h | 60 +++ MVMCoreUI/Molecules/MVMCoreUIPageControl.m | 395 ++++++++++++++++++ MVMCoreUI/Molecules/MVMCoreUIPagingProtocol.h | 22 + .../MoleculeCollectionViewCell.swift | 7 +- .../MVMCoreUIMoleculeMappingObject.m | 4 +- 8 files changed, 840 insertions(+), 56 deletions(-) create mode 100644 MVMCoreUI/Molecules/MVMCoreUIPageControl.h create mode 100644 MVMCoreUI/Molecules/MVMCoreUIPageControl.m create mode 100644 MVMCoreUI/Molecules/MVMCoreUIPagingProtocol.h diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index afe89464..9323507e 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -31,6 +31,9 @@ D22D1F47220496A30077CEC0 /* MVMCoreUISwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = D22D1F45220496A30077CEC0 /* MVMCoreUISwitch.m */; }; D22D1F562204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D22D1F542204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D22D1F552204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m */; }; + D260D7B122D65BDD007E7233 /* MVMCoreUIPageControl.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */ = {isa = PBXBuildFile; fileRef = D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */; }; + D260D7B622D68514007E7233 /* MVMCoreUIPagingProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D274CA332236A78900B01B62 /* StandardFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D274CA322236A78900B01B62 /* StandardFooterView.swift */; }; D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */; }; D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB9224131D100C46919 /* MFTransparentGIFView.swift */; }; @@ -202,6 +205,9 @@ D22D1F45220496A30077CEC0 /* MVMCoreUISwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUISwitch.m; sourceTree = ""; }; D22D1F542204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIStackableViewController.h; sourceTree = ""; }; D22D1F552204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIStackableViewController.m; sourceTree = ""; }; + D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPageControl.h; sourceTree = ""; }; + D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIPageControl.m; sourceTree = ""; }; + D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPagingProtocol.h; sourceTree = ""; }; D274CA322236A78900B01B62 /* StandardFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardFooterView.swift; sourceTree = ""; }; D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFLoadImageView.swift; sourceTree = ""; }; D282AAB9224131D100C46919 /* MFTransparentGIFView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFTransparentGIFView.swift; sourceTree = ""; }; @@ -481,6 +487,9 @@ D2A638FC22CA98280052ED1F /* HeadlineBody.swift */, D2A6390022CBB1820052ED1F /* Carousel.swift */, D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, + D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */, + D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */, + D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */, ); path = Molecules; sourceTree = ""; @@ -757,6 +766,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + D260D7B622D68514007E7233 /* MVMCoreUIPagingProtocol.h in Headers */, D29DF18021E69E49003B2FB9 /* MFView.h in Headers */, D29DF27921E7A533003B2FB9 /* MVMCoreUISession.h in Headers */, D206997721FB8A0B00CAE0DE /* MVMCoreUINavigationController.h in Headers */, @@ -815,6 +825,7 @@ D29770FD21F7C77400B2F0D0 /* MVMCoreUITextFieldView.h in Headers */, D29DF17421E69E1F003B2FB9 /* MFCustomButton.h in Headers */, D29DF29721E7ADB8003B2FB9 /* MFScrollingViewController.h in Headers */, + D260D7B122D65BDD007E7233 /* MVMCoreUIPageControl.h in Headers */, D29DF26F21E6AA0B003B2FB9 /* FLAnimatedImageView.h in Headers */, D29DF2A121E7AF4E003B2FB9 /* MVMCoreUIUtility.h in Headers */, D29DF17621E69E1F003B2FB9 /* PrimaryButton.h in Headers */, @@ -940,6 +951,7 @@ DBEFFA04225A829700230692 /* Label.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, D28B4F8B21FF967C00712C7A /* MVMCoreUIObject.m in Sources */, + D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */, D282AACB2243C61700C46919 /* ButtonView.swift in Sources */, diff --git a/MVMCoreUI/MVMCoreUI.h b/MVMCoreUI/MVMCoreUI.h index 4ef880c0..258fcd1c 100644 --- a/MVMCoreUI/MVMCoreUI.h +++ b/MVMCoreUI/MVMCoreUI.h @@ -108,5 +108,7 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[]; #pragma mark - Molecules #import #import +#import +#import #pragma mark - Templates diff --git a/MVMCoreUI/Molecules/Carousel.swift b/MVMCoreUI/Molecules/Carousel.swift index 3333855b..cb0bbf2b 100644 --- a/MVMCoreUI/Molecules/Carousel.swift +++ b/MVMCoreUI/Molecules/Carousel.swift @@ -10,12 +10,44 @@ 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? + /// The current index of the collection view. Includes dummy cells when looping. + var currentIndex = 0 + + /// The index of the page, does not include dummy cells. + var pageIndex: Int { + get { + return loop ? currentIndex - 2 : currentIndex + } + set(newIndex) { + currentIndex = loop ? newIndex + 2 : newIndex + } + } + + /// The number of pages that there are. Used for the page control and for calculations. Should not include the looping dummy cells. Be sure to set this if subclassing and not using the molecules. + var numberOfPages = 0 + + /// The json for the molecules. + var molecules: [[AnyHashable: Any]]? + /// The horizontal alignment of the cell in the collection view. Only noticeable if the itemWidthPercent is less than 100%. + var itemAlignment = UICollectionView.ScrollPosition.left + + /// From 0-1. The item width as a percent of the carousel width. + var itemWidthPercent: CGFloat = 1 + + /// The height of the carousel. Default is 300. + var collectionViewHeight: NSLayoutConstraint? + + /// The view that we use for paging + var pagingView: (UIView & MVMCoreUIPagingProtocol)? + + /// If the carousel should loop after scrolling past the first and final cells. + var loop = false + private var dragging = false + private var previousContentOffsetX: CGFloat = 0 + + // MARK: - MVMCoreViewProtocol open override func setupView() { super.setupView() guard collectionView.superview == nil else { @@ -33,8 +65,67 @@ open class Carousel: ViewConstrainingView { collectionViewHeight?.isActive = true } + open override func updateView(_ size: CGFloat) { + super.updateView(size) + collectionView.collectionViewLayout.invalidateLayout() + + // Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled. + DispatchQueue.main.async { + self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) + self.collectionView.layoutIfNeeded() + } + } + + // MARK: - MVMCoreUIMoleculeViewProtocol + open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + registerCells(with: json, delegateObject: delegateObject) + setupLayout(with: json) + prepareMolecules(with: json) + itemWidthPercent = (json?.optionalCGFloatForKey("itemWidthPercent") ?? 100) / 100 + setAlignment(with: json?.optionalStringForKey("itemAlignment")) + collectionViewHeight?.constant = json?.optionalCGFloatForKey("height") ?? 300 + setupPagingMolecule(json: json?.optionalDictionaryForKey("pagingMolecule"), delegateObject: delegateObject) + collectionView.reloadData() + } + + open override func shouldSetHorizontalMargins(_ shouldSet: Bool) { + super.shouldSetHorizontalMargins(shouldSet) + updateViewHorizontalDefaults = false + } + + // MARK: - JSON Setters + /// Updates the layout being used + func setupLayout(with json:[AnyHashable: Any]?) { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.minimumLineSpacing = json?["spacing"] as? CGFloat ?? 1 + layout.minimumInteritemSpacing = 0 + collectionView.collectionViewLayout = layout + } + + func prepareMolecules(with json: [AnyHashable: Any]?) { + guard let newMolecules = json?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] else { + numberOfPages = 0 + molecules = nil + return + } + + numberOfPages = newMolecules.count + molecules = newMolecules + if json?.boolForKey("loop") ?? false && newMolecules.count > 2 { + // Sets up the row data with buffer cells on each side (for illusion of endless scroll... also has one more buffer cell on each side in case we can peek that cell). + loop = true + molecules?.insert(newMolecules.last!, at: 0) + molecules?.insert(newMolecules[(newMolecules.count - 1)], at: 0) + molecules?.append(newMolecules.first!) + molecules?.append(newMolecules[1]) + } + pageIndex = 0 + } + /// Registers the cells with the collection view - func registerCells(with json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) { + 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) { @@ -44,51 +135,13 @@ open class Carousel: ViewConstrainingView { } } - /// 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 + /// Sets up the paging molecule + open func setupPagingMolecule(json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) { + var pagingView: (UIView & MVMCoreUIPagingProtocol)? = nil + if let json = json { + pagingView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: json, delegateObject: delegateObject, constrainIfNeeded: true) as? (UIView & MVMCoreUIPagingProtocol) } - - 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 + addPaging(view: pagingView, position: (json?.optionalCGFloatForKey("position") ?? 20)) } // MARK: - Convenience @@ -101,10 +154,60 @@ open class Carousel: ViewConstrainingView { } return (moleculeName, moleculeClass, molecule) } + + /// Sets the alignment from the string. + open func setAlignment(with string: String?) { + switch string { + case "leading": + itemAlignment = .left + case "trailing": + itemAlignment = .right + case "center": + itemAlignment = .centeredHorizontally + default: break + } + } + + /// Adds a paging view. Centers it horizontally with the collection view. The position is the vertical distance from the center of the page view to the bottom of the collection view. + open func addPaging(view: (UIView & MVMCoreUIPagingProtocol)?, position: CGFloat) { + pagingView?.removeFromSuperview() + guard let pagingView = view else { + bottomPin?.isActive = false + bottomPin = bottomAnchor.constraint(equalTo: collectionView.bottomAnchor) + bottomPin?.isActive = true + return + } + pagingView.translatesAutoresizingMaskIntoConstraints = false + addSubview(pagingView) + pagingView.centerXAnchor.constraint(equalTo: collectionView.centerXAnchor).isActive = true + collectionView.bottomAnchor.constraint(equalTo: pagingView.centerYAnchor, constant: position).isActive = true + bottomAnchor.constraint(greaterThanOrEqualTo: pagingView.bottomAnchor).isActive = true + bottomPin?.isActive = false + bottomPin = bottomAnchor.constraint(equalTo: collectionView.bottomAnchor) + bottomPin?.priority = .defaultLow + bottomPin?.isActive = true + + pagingView.setNumberOfPages(numberOfPages) + (pagingView as? MVMCoreUIViewConstrainingProtocol)?.alignHorizontal?(.fill) + pagingView.setPagingTouch { [weak self] (pager) in + MVMCoreDispatchUtility.performBlock(onMainThread: { + guard let localSelf = self else { + return + } + let currentPage = pager.currentPage() + localSelf.pageIndex = currentPage + self?.collectionView.scrollToItem(at: IndexPath(row: localSelf.currentIndex, section: 0), at: (self?.itemAlignment ?? .left), animated: true) + }) + } + self.pagingView = pagingView + } } -extension Carousel: UICollectionViewDelegate { - +extension Carousel: UICollectionViewDelegateFlowLayout { + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let itemWidth = collectionView.bounds.width * itemWidthPercent + return CGSize(width: itemWidth, height: collectionView.bounds.height) + } } extension Carousel: UICollectionViewDataSource { @@ -123,6 +226,195 @@ extension Carousel: UICollectionViewDataSource { protocolCell.setWithJSON(moleculeInfo.molecule, delegateObject: nil, additionalData: nil) protocolCell.updateView(collectionView.bounds.width) } + return cell } } + +extension Carousel: UIScrollViewDelegate { + /*// For getting the scroll progress to set the page control color progress. + - (CGFloat)getPageControlPercentBasedOnScrollView:(UIScrollView *)scrollView { + CGFloat cardWidth = ((UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout).itemSize.width; + CGFloat separatorWidth = ((UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout).minimumLineSpacing; + CGFloat contentOfsetInCard = fmodf(scrollView.contentOffset.x, cardWidth + separatorWidth); + CGFloat endThresholdPageControl = cardWidth + separatorWidth - CGRectGetMaxX(self.pageControl.frame); + CGFloat progress = contentOfsetInCard - endThresholdPageControl; + CGFloat width = CGRectGetWidth(self.pageControl.bounds); + CGFloat percent = (width - progress)/width; + CGFloat cappedPercent = MAX(MIN(percent, 1), 0); + return cappedPercent; + } + + - (void)setPageControlColorsBasedOnScrollView:(UIScrollView *)scrollView { + + // Check if we will need to change colors. + BOOL needToShiftColors = NO; + NSInteger nextCardIndex = 0; + CGFloat cardWidth = ((UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout).itemSize.width; + CGFloat separatorWidth = ((UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout).minimumLineSpacing; + NSInteger currentCard = scrollView.contentOffset.x / (cardWidth + separatorWidth); + CGFloat cardStart = currentCard * (cardWidth + separatorWidth); + CGFloat cardEnd = cardStart + cardWidth + separatorWidth; + NSInteger pageIndicator = currentCard; + if ((self.previousContentOffsetX == NSNotFound || self.previousContentOffsetX <= cardStart) && scrollView.contentOffset.x >= cardStart) { + + // We are passed the threshold and moving right, change to right card color. + needToShiftColors = YES; + nextCardIndex = currentCard + 1; + pageIndicator = currentCard - 1; + } else if ((self.previousContentOffsetX == NSNotFound || self.previousContentOffsetX >= cardEnd) && scrollView.contentOffset.x < cardEnd) { + + // We are passed the threshold and moving left, change to left card color. + needToShiftColors = YES; + nextCardIndex = currentCard - 1; + } + + if (needToShiftColors) { + // Only shift the page control if we are dragging still, otherwise end animation will control. + if (self.dragging) { + [self.pageControl setCurrentPage:pageIndicator]; + } + + // Get the current page color + NSString *colorString = [[self.feedModules objectAtIndex:currentCard] string:KeyPageIndicatorColor]; + UIColor *currentCardPageControlColor = colorString ? [UIColor mfGetColorForHex:colorString] : [UIColor blackColor]; + + // Get the next page color and set accordingly. + colorString = [[self.feedModules dictionaryAtIndex:nextCardIndex] string:KeyPageIndicatorColor]; + UIColor *nextCardPageControlColor = colorString ? [UIColor mfGetColorForHex:colorString] : [UIColor blackColor]; + + // Which color needs to be on top or bottom depends on which direction we are moving. + if (nextCardIndex > currentCard) { + [self setPageControlColor:nextCardPageControlColor progressColor:currentCardPageControlColor]; + } else { + [self setPageControlColor:currentCardPageControlColor progressColor:nextCardPageControlColor]; + } + } + } + + */ + + func handleUserOnBufferCell() { + guard loop else { + return + } + + let lastPageIndex = numberOfPages + 1 + let goToIndex = {(index: Int) in + self.currentIndex = index + self.previousContentOffsetX = CGFloat(NSNotFound) + self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) + self.collectionView.layoutIfNeeded() + self.pagingView?.setPage(self.pageIndex) + } + + if currentIndex < 2 { + // If on a "buffer" last row (which is the first index), go to the real last row secretly. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking. + goToIndex(lastPageIndex) + } else if currentIndex > lastPageIndex { + // If on the "buffer" first row (which is the index after the real last row), go to the real first row secretly. + goToIndex(2) + } + } + + func checkForDraggingOutOfBounds(_ scrollView: UIScrollView) { + guard loop, dragging else { + return + } + // Checks if the user is not paging but attempting to drag endlessly and goes out of bounds. Caps the index. + if let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing { + let itemWidth = collectionView.bounds.width * itemWidthPercent + let index = Int(scrollView.contentOffset.x / (itemWidth + separatorWidth)) + let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1 + if index < 0 { + self.currentIndex = 0 + } else if index > lastCellIndex { + self.currentIndex = lastCellIndex + } + } + + handleUserOnBufferCell() + } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + // Check if the user is dragging the card even further past the next card. + checkForDraggingOutOfBounds(scrollView) + + // Set the page control direction colors if needed. + //[self setPageControlColorsBasedOnScrollView:scrollView]; + + // Set the percent of progress. + //self.pageControl.progressView.progress = [self getPageControlPercentBasedOnScrollView:scrollView]; + + previousContentOffsetX = scrollView.contentOffset.x; + } + + public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + dragging = true + +// // Hide coverview and arrow. +// FeedBaseCollectionViewCell *peakingCell = (FeedBaseCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.currentIndex + 1 inSection:0]]; +// [peakingCell setPeaking:NO animated:YES]; + } + + public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + dragging = false + targetContentOffset.pointee = scrollView.contentOffset + + // This is for setting up smooth custom paging. (Since UICollectionView only handles paging based on collection view size and not cell size). + guard let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else { + return + } + + // We switch cards if we pass the velocity threshold or position threshold (currently 50%). + let itemWidth = collectionView.bounds.width * itemWidthPercent + var cellToSwipeTo = Int(scrollView.contentOffset.x/(itemWidth + separatorWidth) + 0.5) + let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1 + let velocityThreshold: CGFloat = 1.1 + if velocity.x > velocityThreshold { + cellToSwipeTo = currentIndex + 1 + } else if velocity.x < -velocityThreshold { + cellToSwipeTo = currentIndex - 1 + } + + // Cap the index. + currentIndex = min(max(cellToSwipeTo, 0), lastCellIndex) + + collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: itemAlignment, animated: true) + + // Notify that card changed + /*if (self.cardChanged) { + self.cardChanged(self.currentIndex, [self.module string:[MFBaseHomeViewController getFeedContainerNameKey]]); + }*/ + } + + // To give the illusion of endless scrolling. Since we are always calling scrollToItem we can assume finished paging in here. + public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + // Cycle to other end if on buffer cell. + handleUserOnBufferCell() + + pagingView?.setPage(pageIndex) + /* + // Update to the new page in the control if needed. + if (self.currentIndex - 1 != self.pageControl.currentPage) { + [self.pageControl setCurrentPage:self.currentIndex - 1]; + } + // Show overlay and arrow in next Cell + FeedBaseCollectionViewCell *nextCell = (FeedBaseCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.currentIndex + 1 inSection:0]]; + [nextCell setPeaking:YES animated:YES]; + + FeedBaseCollectionViewCell *currentCell = (FeedBaseCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.currentIndex inSection:0]]; + if (currentCell) { + self.accessibilityElements = @[currentCell.containerView, self.pageControl]; + currentCell.containerView.isAccessibilityElement = YES; + currentCell.accessibilityElementsHidden = NO; + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification,currentCell.containerView); + } + + // Set the page control again if pageControl is tapped or voice over is using. + [self setPageControlColorsBasedOnScrollView:scrollView]; + + // send adobe tracker action + [self sendAdobeTrackerAction];*/ + } +} diff --git a/MVMCoreUI/Molecules/MVMCoreUIPageControl.h b/MVMCoreUI/Molecules/MVMCoreUIPageControl.h new file mode 100644 index 00000000..af699ddc --- /dev/null +++ b/MVMCoreUI/Molecules/MVMCoreUIPageControl.h @@ -0,0 +1,60 @@ +// +// MVMCoreUIPageControl.h +// MobileFirstFramework +// +// Created by Seshamani, Shreyas on 1/5/18. +// Copyright © 2018 Verizon Wireless. All rights reserved. +// + +#import +#import +#import +#import + +@interface MVMCoreUIPageControl : UIControl + +// These properties effectively do what their corresponding namesakes do in UIPageControl +@property (nonatomic) NSInteger currentPage; +@property (nonatomic) NSInteger numberOfPages; +@property (nonatomic, getter=isAnimated) BOOL animated; +@property (nullable, strong, nonatomic) UIColor *pageIndicatorTintColor; +@property (nullable, strong, nonatomic) UIColor *currentPageIndicatorTintColor; +@property (nullable, strong, nonatomic, readonly) NSArray *rectangles; +@property (nullable, weak, nonatomic) UIView *containerView; +@property (nullable, weak, nonatomic) UIView *indicatorRectangle; +@property (nullable, copy, nonatomic) PagingTouchBlock pagingTouchBlock; + +///set YES to make the accessibility value as "Slide #currentPage of #totalPage", otherwise will be "Page #currentPage of #totalPage", default is NO +@property (nonatomic) BOOL isSlidesAcc; + +/// This property may be used for indicating the user's selected option (not the currentPage). For instance, in Plan Explore Sizes, it indicates what plan the user is currently on. +@property (nonatomic) NSInteger persistentPreselectedPage; + +//customize pagecontrol properties +@property (nonatomic) CGFloat rectangleWidth; + +/// Indicates the color of the persistentPreselectedPage +@property (nullable, strong, nonatomic) UIColor *persisitentPreselectedPageTintColor; + +//top bottom constraints +@property (nullable, strong, nonatomic) NSLayoutConstraint *topConstraint; +@property (nullable, strong, nonatomic) NSLayoutConstraint *bottomConstraint; + +//a flag to allow to send UIControlEventValueChanged actions all the time +//e.g. going to previous element at first place and going to next at last place +//While current rectangle won't change, need update current page +@property (nonatomic) BOOL alwaysSendingControlEvent; + +- (nullable instancetype)initWithAnimation:(BOOL)animated; +- (void)setCurrentPage:(NSInteger)currentPage animated:(BOOL)animated; + +// Sets up the horizontal constraint to have the pages centered horizontally. +- (void)setupHorizontalConstraints; + +// For subclassing only. +- (void)setupView; +- (void)setupRectangles; + +- (void)setTopBottomSpace:(CGFloat)constant; + +@end diff --git a/MVMCoreUI/Molecules/MVMCoreUIPageControl.m b/MVMCoreUI/Molecules/MVMCoreUIPageControl.m new file mode 100644 index 00000000..e5855f7d --- /dev/null +++ b/MVMCoreUI/Molecules/MVMCoreUIPageControl.m @@ -0,0 +1,395 @@ +// +// MVMCoreUIPageControl.m +// MobileFirstFramework +// +// Created by Seshamani, Shreyas on 1/5/18. +// Copyright © 2018 Verizon Wireless. All rights reserved. +// + +#import "MVMCoreUIPageControl.h" +#import "MVMCoreUICommonViewsUtility.h" +#import +#import +#import "MVMCoreUIUtility.h" +#import "MVMCoreUIConstants.h" +@interface MVMCoreUIPageControl () + +@property (nullable, weak, nonatomic) UIView *animationRectangle; +@property (nullable, strong, nonatomic, readwrite) NSArray *rectangles; + +@property (nullable, strong, nonatomic) NSLayoutConstraint *indicatorRectangleLeadingConstraint; + +@property (nonatomic) CGFloat interRectangleSpacing; +@property (nonatomic) BOOL isDoAnimating; +@property (nonatomic) BOOL isDisAnimating; + +@end + +@implementation MVMCoreUIPageControl + +static CGFloat const DefaultInterRectangleSpacing = 6; +static CGFloat const DefaultRectangleWidth = 24; +static CGFloat const RectangleHeight = 1; +static CGFloat const IndicatorRectangleHeight = 4; + +- (void)updateView:(CGFloat)size { +} + +#pragma mark - Properties + +- (instancetype)init { + self = [super init]; + if (self) { + [self initValues]; + [self setupView]; + } + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self initValues]; + [self setupView]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + if (self) { + [self initValues]; + [self setupView]; + } + return self; +} + +- (instancetype)initWithAnimation:(BOOL)animated { + self = [super init]; + if (self) { + [self initValues]; + [self setupView]; + self.animated = animated; + } + return self; +} + +- (void)initValues { + self.interRectangleSpacing = DefaultInterRectangleSpacing; + self.rectangleWidth = DefaultRectangleWidth; + self.animated = YES; + self.isSlidesAcc = NO; +} + +- (void)setCurrentPage:(NSInteger)currentPage { + [self setCurrentPage:currentPage animated:self.animated]; +} + +- (void)setCurrentPage:(NSInteger)currentPage animated:(BOOL)animated { + if (_currentPage!=currentPage) { + _currentPage = currentPage; + if (currentPage >= 0 && currentPage < self.numberOfPages) { + self.animationRectangle.frame = self.indicatorRectangle.frame; + self.indicatorRectangleLeadingConstraint.constant = (_currentPage * (self.rectangleWidth + self.interRectangleSpacing) + self.interRectangleSpacing); + [self displayIndicator:animated]; + [self dismissAnimationIndicator:animated]; + [self layoutIfNeeded]; + } + } +} + +- (void)setNumberOfPages:(NSInteger)numberOfPages { + if (_numberOfPages != numberOfPages) { + _numberOfPages = numberOfPages; + [self updateRectangleWidthAndSpacing]; + [self setupRectangles]; + } +} + +- (void)setPersistentPreselectedPage:(NSInteger)persistentPreselectedPage { + _persistentPreselectedPage = persistentPreselectedPage; + + if (!self.rectangles) { + [self setupRectangles]; + } + + if (persistentPreselectedPage >= 0 && persistentPreselectedPage < self.numberOfPages) { + UIView *persistentRectangle = self.rectangles[persistentPreselectedPage]; + persistentRectangle.backgroundColor = self.persisitentPreselectedPageTintColor; + } +} + +@synthesize currentPageIndicatorTintColor = _currentPageIndicatorTintColor; + +- (UIColor *)currentPageIndicatorTintColor { + if (!_currentPageIndicatorTintColor) { + _currentPageIndicatorTintColor = [UIColor blackColor]; + } + return _currentPageIndicatorTintColor; +} + +- (void)setCurrentPageIndicatorTintColor:(UIColor *)currentPageIndicatorTintColor { + + _currentPageIndicatorTintColor = currentPageIndicatorTintColor; + if (!self.indicatorRectangle) { + [self setupRectangles]; + } + self.indicatorRectangle.backgroundColor = currentPageIndicatorTintColor; + self.animationRectangle.backgroundColor = currentPageIndicatorTintColor; +} + +@synthesize pageIndicatorTintColor = _pageIndicatorTintColor; + +- (UIColor *)pageIndicatorTintColor { + if (!_pageIndicatorTintColor) { + return [UIColor mfBattleshipGrey]; + } + return _pageIndicatorTintColor; +} + +- (void)setPageIndicatorTintColor:(UIColor *)pageIndicatorTintColor { + _pageIndicatorTintColor = pageIndicatorTintColor; + if (!self.rectangles) { + [self setupRectangles]; + } + for (UIView *rectangle in self.rectangles) { + rectangle.backgroundColor = pageIndicatorTintColor; + } +} + +@synthesize persisitentPreselectedPageTintColor = _persisitentPreselectedPageTintColor; + +- (UIColor *)persisitentPreselectedPageTintColor { + if (!_persisitentPreselectedPageTintColor) { + _persisitentPreselectedPageTintColor = [UIColor mfCerulean]; + } + return _persisitentPreselectedPageTintColor; +} + +- (void)setPersisitentPreselectedPageTintColor:(UIColor *)persisitentPreselectedPageTintColor { + _persisitentPreselectedPageTintColor = persisitentPreselectedPageTintColor; + + if (!self.rectangles) { + [self setupRectangles]; + } + + if (self.persistentPreselectedPage >= 0 && self.persistentPreselectedPage < self.numberOfPages) { + UIView *persistentRectangle = self.rectangles[self.persistentPreselectedPage]; + persistentRectangle.backgroundColor = persisitentPreselectedPageTintColor; + } +} + +#pragma mark - Setup + +- (void)updateRectangleWidthAndSpacing { + + CGFloat screenWidth = [MVMCoreUIUtility getWidth]; + CGFloat pageControlRequiredWidth = self.numberOfPages * (self.rectangleWidth + self.interRectangleSpacing) + DefaultInterRectangleSpacing; + + if (pageControlRequiredWidth > screenWidth) { + // the Inter rectangle spacing is a quarter of the rectangle width + self.interRectangleSpacing = screenWidth / ((self.numberOfPages + 1) + (self.numberOfPages * 4)); + self.rectangleWidth = self.interRectangleSpacing * 4; + } +} + +- (void)setupRectangles { + + // Create the rectangles for all the indexes + [self removeRectangles]; + NSMutableArray *rectangles = [[NSMutableArray alloc] init]; + for (int index = 0; index < self.numberOfPages; index++) { + + UIView *rectangle = [MVMCoreUICommonViewsUtility commonView]; + [rectangles addObject:rectangle]; + rectangle.translatesAutoresizingMaskIntoConstraints = NO; + rectangle.frame = CGRectMake((self.interRectangleSpacing * (index + 1)) + (self.rectangleWidth * index), IndicatorRectangleHeight- RectangleHeight, self.rectangleWidth, RectangleHeight); + [rectangle.heightAnchor constraintEqualToConstant:RectangleHeight].active = YES; + [rectangle.widthAnchor constraintEqualToConstant:self.rectangleWidth].active = YES; + rectangle.backgroundColor = self.pageIndicatorTintColor; + } + + [StackableViewController populateViewHorizontally:self.containerView withUIArray:rectangles withSpacingBlock:^UIEdgeInsets(id _Nullable object) { + + if (object == [rectangles lastObject]) { + return UIEdgeInsetsMake(RectangleHeight, self.interRectangleSpacing, 0, self.interRectangleSpacing); + } + return UIEdgeInsetsMake(RectangleHeight, self.interRectangleSpacing, 0, 0); + }]; + self.rectangles = rectangles; + + // Create the indicator rectangle + UIView *indicatorRectangle = [MVMCoreUICommonViewsUtility commonView]; + [self.containerView addSubview:indicatorRectangle]; + indicatorRectangle.backgroundColor = self.currentPageIndicatorTintColor; + [indicatorRectangle.heightAnchor constraintEqualToConstant:IndicatorRectangleHeight].active = YES; + [indicatorRectangle.widthAnchor constraintEqualToConstant:self.rectangleWidth].active = YES; + [indicatorRectangle.topAnchor constraintGreaterThanOrEqualToAnchor:self.containerView.topAnchor].active = YES; + [indicatorRectangle.bottomAnchor constraintEqualToAnchor:self.containerView.bottomAnchor].active = YES; + self.indicatorRectangleLeadingConstraint = [indicatorRectangle.leadingAnchor constraintEqualToAnchor:self.containerView.leadingAnchor constant:self.interRectangleSpacing]; + self.indicatorRectangleLeadingConstraint.active = YES; + self.indicatorRectangle = indicatorRectangle; + indicatorRectangle.frame = CGRectMake(self.interRectangleSpacing, 0, self.rectangleWidth, IndicatorRectangleHeight); + + // Create shadow indicator rectangle for animaiton + UIView *animatedIndicatorRectangle = [MVMCoreUICommonViewsUtility commonView]; + [self.containerView addSubview:animatedIndicatorRectangle]; + animatedIndicatorRectangle.backgroundColor = self.currentPageIndicatorTintColor; + self.animationRectangle = animatedIndicatorRectangle; +} + +- (void)setupView { + + if (!self.containerView) { + // Create a container view that keeps everything centered + UIView *containerView = [MVMCoreUICommonViewsUtility commonView]; + [self addSubview:containerView]; + self.containerView = containerView; + [self setupHorizontalConstraints]; + + NSLayoutConstraint *topConstraint = [containerView.topAnchor constraintEqualToAnchor:self.topAnchor constant:PaddingThree]; + topConstraint.priority = UILayoutPriorityDefaultHigh; + topConstraint.active = YES; + self.topConstraint = topConstraint; + + NSLayoutConstraint *bottomConstraint = [self.bottomAnchor constraintEqualToAnchor:containerView.bottomAnchor constant:PaddingThree]; + bottomConstraint.priority = UILayoutPriorityDefaultHigh; + bottomConstraint.active = YES; + self.bottomConstraint = bottomConstraint; + + [self updateRectangleWidthAndSpacing]; + + // Create the rectangles for all the indexes + [self setupRectangles]; + + UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] init]; + [tapGesture addTarget:self action:@selector(rectangleTapped:)]; + [self addGestureRecognizer:tapGesture]; + } +} + +- (void)setupHorizontalConstraints { + [self.containerView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor].active = YES; + [self.containerView.leadingAnchor constraintGreaterThanOrEqualToAnchor:self.leadingAnchor].active = YES; + [self.containerView.trailingAnchor constraintLessThanOrEqualToAnchor:self.trailingAnchor].active = YES; +} + +- (void)removeRectangles { + for (UIView *subview in self.containerView.subviews) { + [subview removeFromSuperview]; + } +} + +#pragma mark - Actions + +- (void)rectangleTapped:(UITapGestureRecognizer *)tapGesture { + if (self.userInteractionEnabled) { + CGPoint touchPoint = [tapGesture locationInView:self.containerView]; + NSInteger selectedIndex = [self.rectangles indexOfObjectPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + UIView *subview = self.rectangles[idx]; + if (CGRectGetMaxX(subview.frame) >= touchPoint.x && CGRectGetMinX(subview.frame) <= touchPoint.x) { + *stop = YES; + } + return *stop; + }]; + if (selectedIndex != NSNotFound) { + self.currentPage = selectedIndex; + [self sendActionsForControlEvents:UIControlEventValueChanged]; + self.pagingTouchBlock(self); + } + } +} + +#pragma mark - MVMCoreUIPagingProtocol + +- (void)setPage:(NSUInteger)page { + self.currentPage = page; +} + +#pragma mark - MoleculeViewProtocol + +- (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData { + NSString *colorString = [json string:KeyBackgroundColor]; + if (colorString) { + self.backgroundColor = [UIColor mfGetColorForHex:colorString]; + } + colorString = [json string:@"barsColor"]; + if (colorString) { + UIColor *color = [UIColor mfGetColorForHex:colorString]; + self.pageIndicatorTintColor = color; + self.currentPageIndicatorTintColor = color; + } + colorString = [json string:@"currentBarColor"]; + if (colorString) { + self.currentPageIndicatorTintColor = [UIColor mfGetColorForHex:colorString]; + } +} + +#pragma mark - Accessibility +- (UIAccessibilityTraits)accessibilityTraits { + return UIAccessibilityTraitAdjustable; +} + +- (NSString *)accessibilityValue { + NSString *stringKey = self.isSlidesAcc ? @"MVMCoreUIPageControlslides_currentpage_index" : @"MVMCoreUIPageControl_currentpage_index"; + return [NSString stringWithFormat:[MVMCoreUIUtility hardcodedStringWithKey:stringKey],self.currentPage+1, self.numberOfPages]; +} + +- (void)accessibilityIncrement { + [self accessibilityAdjustToPage:self.currentPage + 1]; +} + +- (void)accessibilityDecrement { + [self accessibilityAdjustToPage:self.currentPage -1]; +} + +//when self.awlaysSenfingControlEven it NO, and user is already at first or final index, if user try to increment or decrement, won't do action +//while self.awlaysSenfingControlEven is YES, it still send control event, while the rectangle won't change, need set currentPage again. +- (void)accessibilityAdjustToPage:(NSInteger)index { + if ((index < self.numberOfPages && index >= 0) || self.alwaysSendingControlEvent) { + [self setCurrentPage:index animated:NO]; + [self sendActionsForControlEvents:UIControlEventValueChanged]; + } +} + +#pragma mark - Animate pagecontrol indicator + +- (void)displayIndicator:(BOOL)animated { + if (!animated) { + return; + } + if (!self.isDoAnimating) { + self.indicatorRectangle.frame = CGRectMake(CGRectGetMinX(self.indicatorRectangle.frame), CGRectGetMinY(self.indicatorRectangle.frame) + IndicatorRectangleHeight, CGRectGetWidth(self.indicatorRectangle.frame), 0.f); + [UIView animateWithDuration:0.3f animations:^{ + self.isDoAnimating = YES; + self.indicatorRectangle.frame = CGRectMake(CGRectGetMinX(self.indicatorRectangle.frame), CGRectGetMinY(self.indicatorRectangle.frame)-IndicatorRectangleHeight, CGRectGetWidth(self.indicatorRectangle.frame), IndicatorRectangleHeight); + } completion:^(BOOL finished) { + self.isDoAnimating = NO; + }]; + } +} + +- (void)dismissAnimationIndicator:(BOOL)animated { + if (animated) { + if (!self.isDisAnimating) { + [UIView animateWithDuration:0.3f animations:^{ + self.isDisAnimating = YES; + self.animationRectangle.frame = CGRectMake(CGRectGetMinX(self.animationRectangle.frame), CGRectGetMinY(self.animationRectangle.frame) + IndicatorRectangleHeight, CGRectGetWidth(self.animationRectangle.frame), 0.f); + } completion:^(BOOL finished) { + [self layoutIfNeeded]; + self.animationRectangle.frame = self.indicatorRectangle.frame; + self.isDisAnimating = NO; + }]; + } + } else { + [self layoutIfNeeded]; + self.animationRectangle.frame = self.indicatorRectangle.frame; + } +} + +- (void)setTopBottomSpace:(CGFloat)constant { + self.bottomConstraint.constant = constant; + self.topConstraint.constant = constant; +} + +@end diff --git a/MVMCoreUI/Molecules/MVMCoreUIPagingProtocol.h b/MVMCoreUI/Molecules/MVMCoreUIPagingProtocol.h new file mode 100644 index 00000000..ac8aee3c --- /dev/null +++ b/MVMCoreUI/Molecules/MVMCoreUIPagingProtocol.h @@ -0,0 +1,22 @@ +// +// MVMCoreUIPagingProtocol.h +// MVMCoreUI +// +// Created by Scott Pfeil on 7/10/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// +#import + +@protocol MVMCoreUIPagingProtocol + +typedef void (^PagingTouchBlock)(NSObject* _Nonnull sender); + +- (NSInteger)currentPage; + +- (void)setNumberOfPages:(NSInteger)numberOfPages; + +- (void)setPage:(NSInteger)page; + +- (void)setPagingTouchBlock:(nullable PagingTouchBlock)pagingTouchBlock; + +@end diff --git a/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift b/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift index d28819ac..2eb61e68 100644 --- a/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift @@ -45,10 +45,9 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi } if let castView = molecule as? MVMCoreUIViewConstrainingProtocol { let standardConstraints = castView.useStandardConstraints?() ?? true - castView.shouldSetHorizontalMargins?(!standardConstraints) - castView.shouldSetVerticalMargins?(!standardConstraints) + castView.shouldSetHorizontalMargins?(standardConstraints) + castView.shouldSetVerticalMargins?(standardConstraints) } - backgroundColor = molecule?.backgroundColor } @@ -60,6 +59,6 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi } public func updateView(_ size: CGFloat) { - + molecule?.updateView(size) } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 61230208..325a8fed 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -14,6 +14,7 @@ #import "MVMCoreUIObject.h" #import #import "MFTextField.h" +#import "MVMCoreUIPageControl.h" #import "MVMCoreUIViewConstrainingProtocol.h" @implementation MVMCoreUIMoleculeMappingObject @@ -50,7 +51,8 @@ @"moduleMolecule": ModuleMolecule.class, @"headlineBody": HeadlineBody.class, @"carousel": Carousel.class, - @"carouselItem": MoleculeCollectionViewCell.class + @"carouselItem": MoleculeCollectionViewCell.class, + @"barsPager": MVMCoreUIPageControl.class, } mutableCopy]; }); return mapping; From 8866e91caa86612da557aff4af19fe9bb6e14d14 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 11 Jul 2019 16:11:58 -0400 Subject: [PATCH 03/12] add peaking logic --- MVMCoreUI/Molecules/Carousel.swift | 127 ++++-------------- MVMCoreUI/Molecules/MVMCoreUIPagingProtocol.h | 3 + .../MoleculeCollectionViewCell.swift | 60 ++++++++- .../Media.xcassets/Contents.json | 6 + .../peakingRightArrow.imageset/Contents.json | 23 ++++ .../E_UBI_003_G.png | Bin 0 -> 168 bytes .../E_UBI_003_G@2x.png | Bin 0 -> 275 bytes .../E_UBI_003_G@3x.png | Bin 0 -> 379 bytes 8 files changed, 115 insertions(+), 104 deletions(-) create mode 100644 MVMCoreUI/SupportingFiles/Media.xcassets/Contents.json create mode 100644 MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/Contents.json create mode 100644 MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G.png create mode 100644 MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G@2x.png create mode 100644 MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G@3x.png diff --git a/MVMCoreUI/Molecules/Carousel.swift b/MVMCoreUI/Molecules/Carousel.swift index cb0bbf2b..cb5d7243 100644 --- a/MVMCoreUI/Molecules/Carousel.swift +++ b/MVMCoreUI/Molecules/Carousel.swift @@ -45,7 +45,6 @@ open class Carousel: ViewConstrainingView { /// If the carousel should loop after scrolling past the first and final cells. var loop = false private var dragging = false - private var previousContentOffsetX: CGFloat = 0 // MARK: - MVMCoreViewProtocol open override func setupView() { @@ -73,6 +72,7 @@ open class Carousel: ViewConstrainingView { DispatchQueue.main.async { self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) self.collectionView.layoutIfNeeded() + self.showPeaking(true) } } @@ -201,6 +201,24 @@ open class Carousel: ViewConstrainingView { } self.pagingView = pagingView } + + open func showPeaking(_ peaking: Bool) { + if peaking { + // Show overlay and arrow in peaking Cell + let visibleItemsPaths = collectionView.indexPathsForVisibleItems.sorted { $0.row < $1.row } + if let firstItem = visibleItemsPaths.first, firstItem.row != currentIndex { + (collectionView.cellForItem(at: firstItem) as? MoleculeCollectionViewCell)?.setPeaking(true, animated: true) + } + if let lastItem = visibleItemsPaths.last, lastItem.row != currentIndex { + (collectionView.cellForItem(at: lastItem) as? MoleculeCollectionViewCell)?.setPeaking(true, animated: true) + } + } else { + // Hide peaking. + for item in collectionView.visibleCells { + (item as? MoleculeCollectionViewCell)?.setPeaking(false, animated: true) + } + } + } } extension Carousel: UICollectionViewDelegateFlowLayout { @@ -232,68 +250,6 @@ extension Carousel: UICollectionViewDataSource { } extension Carousel: UIScrollViewDelegate { - /*// For getting the scroll progress to set the page control color progress. - - (CGFloat)getPageControlPercentBasedOnScrollView:(UIScrollView *)scrollView { - CGFloat cardWidth = ((UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout).itemSize.width; - CGFloat separatorWidth = ((UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout).minimumLineSpacing; - CGFloat contentOfsetInCard = fmodf(scrollView.contentOffset.x, cardWidth + separatorWidth); - CGFloat endThresholdPageControl = cardWidth + separatorWidth - CGRectGetMaxX(self.pageControl.frame); - CGFloat progress = contentOfsetInCard - endThresholdPageControl; - CGFloat width = CGRectGetWidth(self.pageControl.bounds); - CGFloat percent = (width - progress)/width; - CGFloat cappedPercent = MAX(MIN(percent, 1), 0); - return cappedPercent; - } - - - (void)setPageControlColorsBasedOnScrollView:(UIScrollView *)scrollView { - - // Check if we will need to change colors. - BOOL needToShiftColors = NO; - NSInteger nextCardIndex = 0; - CGFloat cardWidth = ((UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout).itemSize.width; - CGFloat separatorWidth = ((UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout).minimumLineSpacing; - NSInteger currentCard = scrollView.contentOffset.x / (cardWidth + separatorWidth); - CGFloat cardStart = currentCard * (cardWidth + separatorWidth); - CGFloat cardEnd = cardStart + cardWidth + separatorWidth; - NSInteger pageIndicator = currentCard; - if ((self.previousContentOffsetX == NSNotFound || self.previousContentOffsetX <= cardStart) && scrollView.contentOffset.x >= cardStart) { - - // We are passed the threshold and moving right, change to right card color. - needToShiftColors = YES; - nextCardIndex = currentCard + 1; - pageIndicator = currentCard - 1; - } else if ((self.previousContentOffsetX == NSNotFound || self.previousContentOffsetX >= cardEnd) && scrollView.contentOffset.x < cardEnd) { - - // We are passed the threshold and moving left, change to left card color. - needToShiftColors = YES; - nextCardIndex = currentCard - 1; - } - - if (needToShiftColors) { - // Only shift the page control if we are dragging still, otherwise end animation will control. - if (self.dragging) { - [self.pageControl setCurrentPage:pageIndicator]; - } - - // Get the current page color - NSString *colorString = [[self.feedModules objectAtIndex:currentCard] string:KeyPageIndicatorColor]; - UIColor *currentCardPageControlColor = colorString ? [UIColor mfGetColorForHex:colorString] : [UIColor blackColor]; - - // Get the next page color and set accordingly. - colorString = [[self.feedModules dictionaryAtIndex:nextCardIndex] string:KeyPageIndicatorColor]; - UIColor *nextCardPageControlColor = colorString ? [UIColor mfGetColorForHex:colorString] : [UIColor blackColor]; - - // Which color needs to be on top or bottom depends on which direction we are moving. - if (nextCardIndex > currentCard) { - [self setPageControlColor:nextCardPageControlColor progressColor:currentCardPageControlColor]; - } else { - [self setPageControlColor:currentCardPageControlColor progressColor:nextCardPageControlColor]; - } - } - } - - */ - func handleUserOnBufferCell() { guard loop else { return @@ -302,7 +258,6 @@ extension Carousel: UIScrollViewDelegate { let lastPageIndex = numberOfPages + 1 let goToIndex = {(index: Int) in self.currentIndex = index - self.previousContentOffsetX = CGFloat(NSNotFound) self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) self.collectionView.layoutIfNeeded() self.pagingView?.setPage(self.pageIndex) @@ -337,24 +292,17 @@ extension Carousel: UIScrollViewDelegate { } public func scrollViewDidScroll(_ scrollView: UIScrollView) { + // Check if the user is dragging the card even further past the next card. checkForDraggingOutOfBounds(scrollView) - // Set the page control direction colors if needed. - //[self setPageControlColorsBasedOnScrollView:scrollView]; - - // Set the percent of progress. - //self.pageControl.progressView.progress = [self getPageControlPercentBasedOnScrollView:scrollView]; - - previousContentOffsetX = scrollView.contentOffset.x; + // Let the pager know our progress if needed. + pagingView?.scrollViewDidScroll?(collectionView) } public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { dragging = true - -// // Hide coverview and arrow. -// FeedBaseCollectionViewCell *peakingCell = (FeedBaseCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.currentIndex + 1 inSection:0]]; -// [peakingCell setPeaking:NO animated:YES]; + showPeaking(false) } public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { @@ -363,7 +311,7 @@ extension Carousel: UIScrollViewDelegate { // This is for setting up smooth custom paging. (Since UICollectionView only handles paging based on collection view size and not cell size). guard let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else { - return + return } // We switch cards if we pass the velocity threshold or position threshold (currently 50%). @@ -381,11 +329,6 @@ extension Carousel: UIScrollViewDelegate { currentIndex = min(max(cellToSwipeTo, 0), lastCellIndex) collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: itemAlignment, animated: true) - - // Notify that card changed - /*if (self.cardChanged) { - self.cardChanged(self.currentIndex, [self.module string:[MFBaseHomeViewController getFeedContainerNameKey]]); - }*/ } // To give the illusion of endless scrolling. Since we are always calling scrollToItem we can assume finished paging in here. @@ -394,27 +337,7 @@ extension Carousel: UIScrollViewDelegate { handleUserOnBufferCell() pagingView?.setPage(pageIndex) - /* - // Update to the new page in the control if needed. - if (self.currentIndex - 1 != self.pageControl.currentPage) { - [self.pageControl setCurrentPage:self.currentIndex - 1]; - } - // Show overlay and arrow in next Cell - FeedBaseCollectionViewCell *nextCell = (FeedBaseCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.currentIndex + 1 inSection:0]]; - [nextCell setPeaking:YES animated:YES]; - FeedBaseCollectionViewCell *currentCell = (FeedBaseCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.currentIndex inSection:0]]; - if (currentCell) { - self.accessibilityElements = @[currentCell.containerView, self.pageControl]; - currentCell.containerView.isAccessibilityElement = YES; - currentCell.accessibilityElementsHidden = NO; - UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification,currentCell.containerView); - } - - // Set the page control again if pageControl is tapped or voice over is using. - [self setPageControlColorsBasedOnScrollView:scrollView]; - - // send adobe tracker action - [self sendAdobeTrackerAction];*/ + showPeaking(true) } } diff --git a/MVMCoreUI/Molecules/MVMCoreUIPagingProtocol.h b/MVMCoreUI/Molecules/MVMCoreUIPagingProtocol.h index ac8aee3c..89dd279b 100644 --- a/MVMCoreUI/Molecules/MVMCoreUIPagingProtocol.h +++ b/MVMCoreUI/Molecules/MVMCoreUIPagingProtocol.h @@ -18,5 +18,8 @@ typedef void (^PagingTouchBlock)(NSObject* _Nonnull sen - (void)setPage:(NSInteger)page; - (void)setPagingTouchBlock:(nullable PagingTouchBlock)pagingTouchBlock; + +@optional +- (void)scrollViewDidScroll:(nonnull UICollectionView *)collectionView; @end diff --git a/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift b/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift index 2eb61e68..23850e70 100644 --- a/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift @@ -11,6 +11,11 @@ import UIKit open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeViewProtocol, MoleculeListCellProtocol { open var molecule: (UIView & MVMCoreUIMoleculeViewProtocol)? + open var allowsPeaking = false + var peakingLeftArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) + var peakingRightArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) + var peakingCover = MVMCoreUICommonViewsUtility.commonView() + public override init(frame: CGRect) { super.init(frame: .zero) setupView() @@ -22,15 +27,49 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi } public func setupView() { + guard peakingCover.superview == nil else { + return + } + // Covers the card when peaking. + peakingCover.backgroundColor = .white + peakingCover.alpha = 0 + contentView.addSubview(peakingCover) + NSLayoutConstraint.constraintPinSubview(toSuperview: peakingCover) + + // A small arrow on the next card for when peaking. + let ratio: CGFloat = 0.015 + peakingLeftArrow.translatesAutoresizingMaskIntoConstraints = false + peakingLeftArrow.alpha = 0 + peakingLeftArrow.tintColor = .black + contentView.addSubview(peakingLeftArrow) + peakingLeftArrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true + NSLayoutConstraint.scalingPinViewLeft(toSuper: peakingLeftArrow, ratio: ratio, anchor: contentView.widthAnchor) + + peakingRightArrow.translatesAutoresizingMaskIntoConstraints = false + peakingRightArrow.transform = CGAffineTransform(scaleX: -1, y: 1) // Flip + peakingRightArrow.alpha = 0 + peakingRightArrow.tintColor = .black + contentView.addSubview(peakingRightArrow) + peakingRightArrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true + NSLayoutConstraint.scalingPinViewRight(toSuper: peakingRightArrow, ratio: ratio, anchor: contentView.widthAnchor) } public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - guard let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule) else { + + // Handles peaking. + allowsPeaking = json?.optionalBoolForKey("peakingUI") ?? true + if let peakingArrowColor = json?.optionalStringForKey("peakingArrowColor") { + let color = UIColor.mfGet(forHex: peakingArrowColor) + peakingLeftArrow.tintColor = color + peakingRightArrow.tintColor = color + } + + guard 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) + contentView.insertSubview(moleculeView, at: 0) let standardConstraints = (moleculeView as? MVMCoreUIViewConstrainingProtocol)?.useStandardConstraints?() ?? true NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: moleculeView, useMargins: standardConstraints).values)) if standardConstraints { @@ -61,4 +100,21 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi public func updateView(_ size: CGFloat) { molecule?.updateView(size) } + + public func setPeaking(_ peaking: Bool, animated: Bool) { + guard allowsPeaking else { + return + } + let animation = {() in + self.peakingRightArrow.alpha = peaking ? 1 : 0 + self.peakingLeftArrow.alpha = peaking ? 1 : 0 + self.peakingCover.alpha = peaking ? 0.5 : 0 + print("\(self.peakingCover.alpha)") + } + if animated { + UIView.animate(withDuration: 0.4, animations: animation) + } else { + animation() + } + } } diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/Contents.json b/MVMCoreUI/SupportingFiles/Media.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/MVMCoreUI/SupportingFiles/Media.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/Contents.json b/MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/Contents.json new file mode 100644 index 00000000..0851ce22 --- /dev/null +++ b/MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "E_UBI_003_G.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "E_UBI_003_G@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "E_UBI_003_G@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G.png b/MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G.png new file mode 100644 index 0000000000000000000000000000000000000000..8c789309b9b6158736aa92340a311bfe13a7e644 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&k#^NA%Cx&(BWL^R}NuDl_AsV8| z2?zKNH6A*8W9f!&BgWR}jD?zInQs;;_!c+pVA^d`7U;I%Lyp3_LwdcjB1gTe~DWM4fpzt|a literal 0 HcmV?d00001 diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G@2x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b6453e48a2bbeceeb317ec1000da984b4aa7b85c GIT binary patch literal 275 zcmV+u0qp*XP)Px#%}GQ-R7efgmfZ>gQ5Z!Tf8!ZEpd?9}lH7R;4?!Z4+`0BjSX;Srv%i|}!l^aQ z#oF!pJLgQR;g2Tj1_to(W4>9U-rxx%KNd6l=vPq5I2QG7-N^-BF!5tCQzd6m$ut)A z?H=k0R5J5pF|&()1eMHVQQua{0aQ}?v6$IG--AjPv8ZpWWCto)`mva)k}arYrDz-O zI;nA}L}Lv_@j3mY!_OnC^V(BivgShCXPP?EQkRO<#V&QZPTg=)w@h7m>ZbqwZ5c*p Z=Nk_lFH7J3S+M{B002ovPDHLkV1fd9cWnRw literal 0 HcmV?d00001 diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G@3x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..7fe147c7942353596fc01b9c9427daea9b2012e7 GIT binary patch literal 379 zcmV->0fhdEP)Px$HAzH4R9Fekm`REPK@dgN+Bvt{M{wbpM4S*25fNz+L3-2B_h=VhLs1l^6^HXU z{D4Xb3S0$L{eVwco1c$LD>5>?m_0mL{6R_RX1B9D{KDc1-JzP@&hGFDi!0=y>UK-J zLkSia1=X}$+8v6pIQvi!OgF#}W@5NieX#R9cbP!k5I6d;xg zYU-fk_)&HbA!q5I7Er}scZU2UKvExA;yb}Y;yCEG%z=fS-~Oq?29foo7SVk6RE3f1 z=#Uei=5VEIKd2fssuq^2$)~yoQC$>+SLv3N<)|V!Y-mIUBJ0wPW9SA}GSZtvRe4?3 ZcmYEIX$}#dZKD7H002ovPDHLkV1lz)oE888 literal 0 HcmV?d00001 From 39cb9302636ec7367d5f00d213a407eefe720b5c Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 12 Jul 2019 10:25:42 -0400 Subject: [PATCH 04/12] accessibility and page control fixes --- MVMCoreUI/Molecules/Carousel.swift | 36 +++++++++++++------ MVMCoreUI/Molecules/MVMCoreUIPageControl.m | 3 +- .../MoleculeCollectionViewCell.swift | 4 ++- .../Strings/en.lproj/Localizable.strings | 3 ++ .../Strings/es-MX.lproj/Localizable.strings | 3 ++ .../Strings/es.lproj/Localizable.strings | 3 ++ 6 files changed, 40 insertions(+), 12 deletions(-) diff --git a/MVMCoreUI/Molecules/Carousel.swift b/MVMCoreUI/Molecules/Carousel.swift index cb5d7243..27cc59f6 100644 --- a/MVMCoreUI/Molecules/Carousel.swift +++ b/MVMCoreUI/Molecules/Carousel.swift @@ -57,6 +57,7 @@ open class Carousel: ViewConstrainingView { collectionView.delegate = self collectionView.showsHorizontalScrollIndicator = false collectionView.backgroundColor = .clear + collectionView.isAccessibilityElement = false addSubview(collectionView) pinView(toSuperView: collectionView) @@ -117,7 +118,7 @@ open class Carousel: ViewConstrainingView { // Sets up the row data with buffer cells on each side (for illusion of endless scroll... also has one more buffer cell on each side in case we can peek that cell). loop = true molecules?.insert(newMolecules.last!, at: 0) - molecules?.insert(newMolecules[(newMolecules.count - 1)], at: 0) + molecules?.insert(newMolecules[(newMolecules.count - 2)], at: 0) molecules?.append(newMolecules.first!) molecules?.append(newMolecules[1]) } @@ -140,6 +141,7 @@ open class Carousel: ViewConstrainingView { var pagingView: (UIView & MVMCoreUIPagingProtocol)? = nil if let json = json { pagingView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: json, delegateObject: delegateObject, constrainIfNeeded: true) as? (UIView & MVMCoreUIPagingProtocol) + pagingView?.isAccessibilityElement = true } addPaging(view: pagingView, position: (json?.optionalCGFloatForKey("position") ?? 20)) } @@ -196,7 +198,7 @@ open class Carousel: ViewConstrainingView { } let currentPage = pager.currentPage() localSelf.pageIndex = currentPage - self?.collectionView.scrollToItem(at: IndexPath(row: localSelf.currentIndex, section: 0), at: (self?.itemAlignment ?? .left), animated: true) + localSelf.goTo(localSelf.currentIndex, animated: !UIAccessibility.isVoiceOverRunning) }) } self.pagingView = pagingView @@ -244,12 +246,27 @@ extension Carousel: UICollectionViewDataSource { protocolCell.setWithJSON(moleculeInfo.molecule, delegateObject: nil, additionalData: nil) protocolCell.updateView(collectionView.bounds.width) } + + if indexPath.row == currentIndex { + cell.accessibilityElementsHidden = false +// self.accessibilityElements = @[cell.containerView, self.pageControl]; + } else { + cell.accessibilityElementsHidden = true + } return cell } } extension Carousel: UIScrollViewDelegate { + + func goTo(_ index: Int, animated: Bool) { + collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0))?.accessibilityElementsHidden = true + self.currentIndex = index + collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0))?.accessibilityElementsHidden = false + self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: animated) + } + func handleUserOnBufferCell() { guard loop else { return @@ -257,8 +274,7 @@ extension Carousel: UIScrollViewDelegate { let lastPageIndex = numberOfPages + 1 let goToIndex = {(index: Int) in - self.currentIndex = index - self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) + self.goTo(index, animated: false) self.collectionView.layoutIfNeeded() self.pagingView?.setPage(self.pageIndex) } @@ -279,11 +295,11 @@ extension Carousel: UIScrollViewDelegate { // Checks if the user is not paging but attempting to drag endlessly and goes out of bounds. Caps the index. if let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing { let itemWidth = collectionView.bounds.width * itemWidthPercent - let index = Int(scrollView.contentOffset.x / (itemWidth + separatorWidth)) + let index = scrollView.contentOffset.x / (itemWidth + separatorWidth) let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1 - if index < 0 { + if index < 1 { self.currentIndex = 0 - } else if index > lastCellIndex { + } else if index > CGFloat(lastCellIndex - 1) { self.currentIndex = lastCellIndex } } @@ -326,9 +342,7 @@ extension Carousel: UIScrollViewDelegate { } // Cap the index. - currentIndex = min(max(cellToSwipeTo, 0), lastCellIndex) - - collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: itemAlignment, animated: true) + goTo(min(max(cellToSwipeTo, 0), lastCellIndex), animated: true) } // To give the illusion of endless scrolling. Since we are always calling scrollToItem we can assume finished paging in here. @@ -339,5 +353,7 @@ extension Carousel: UIScrollViewDelegate { pagingView?.setPage(pageIndex) showPeaking(true) + + //UIAccessibility.post(notification: .layoutChanged, argument: currentIndex) UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification,currentCell.containerView); } } diff --git a/MVMCoreUI/Molecules/MVMCoreUIPageControl.m b/MVMCoreUI/Molecules/MVMCoreUIPageControl.m index e5855f7d..cbfe0300 100644 --- a/MVMCoreUI/Molecules/MVMCoreUIPageControl.m +++ b/MVMCoreUI/Molecules/MVMCoreUIPageControl.m @@ -302,7 +302,7 @@ static CGFloat const IndicatorRectangleHeight = 4; #pragma mark - MVMCoreUIPagingProtocol -- (void)setPage:(NSUInteger)page { +- (void)setPage:(NSInteger)page { self.currentPage = page; } @@ -349,6 +349,7 @@ static CGFloat const IndicatorRectangleHeight = 4; if ((index < self.numberOfPages && index >= 0) || self.alwaysSendingControlEvent) { [self setCurrentPage:index animated:NO]; [self sendActionsForControlEvents:UIControlEventValueChanged]; + self.pagingTouchBlock(self); } } diff --git a/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift b/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift index 23850e70..82d7f4a2 100644 --- a/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift @@ -30,6 +30,9 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi guard peakingCover.superview == nil else { return } + isAccessibilityElement = false + contentView.isAccessibilityElement = false + // Covers the card when peaking. peakingCover.backgroundColor = .white peakingCover.alpha = 0 @@ -109,7 +112,6 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi self.peakingRightArrow.alpha = peaking ? 1 : 0 self.peakingLeftArrow.alpha = peaking ? 1 : 0 self.peakingCover.alpha = peaking ? 0.5 : 0 - print("\(self.peakingCover.alpha)") } if animated { UIView.animate(withDuration: 0.4, animations: animation) diff --git a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings index 96088cab..2e7677f3 100644 --- a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings @@ -40,6 +40,9 @@ "AccOn" = "on"; "AccOff" = "off"; "AccToggleHint" = "double tap to toggle"; +// Carousel +"MVMCoreUIPageControl_currentpage_index" = "page %ld of %ld"; +"MVMCoreUIPageControlslides_currentpage_index" = "slide %ld of %ld"; //Styler "CountDownDay" = " day"; "CountDownHour" = " hour"; diff --git a/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings index a006b218..bf4f4e47 100644 --- a/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings @@ -39,6 +39,9 @@ "AccOn" = "encendido"; "AccOff" = "apagado"; "AccToggleHint" = "toca dos veces para alternar"; +// Carousel +"MVMCoreUIPageControl_currentpage_index" = "página %ld de %ld"; +"MVMCoreUIPageControlslides_currentpage_index" = "diapositiva %ld of %ld"; //Styler "CountDownDay" = " día"; "CountDownHour" = " hora"; diff --git a/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings index a006b218..bf4f4e47 100644 --- a/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings @@ -39,6 +39,9 @@ "AccOn" = "encendido"; "AccOff" = "apagado"; "AccToggleHint" = "toca dos veces para alternar"; +// Carousel +"MVMCoreUIPageControl_currentpage_index" = "página %ld de %ld"; +"MVMCoreUIPageControlslides_currentpage_index" = "diapositiva %ld of %ld"; //Styler "CountDownDay" = " día"; "CountDownHour" = " hora"; From 5e2d0093130a683ff50f881cc1479b2cca67dac2 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 12 Jul 2019 14:49:16 -0400 Subject: [PATCH 05/12] accessibility --- MVMCoreUI/Atoms/Views/ViewConstrainingView.h | 3 ++ MVMCoreUI/Atoms/Views/ViewConstrainingView.m | 13 ++++++ MVMCoreUI/Molecules/Carousel.swift | 45 ++++++++++++------- MVMCoreUI/Molecules/MVMCoreUIPageControl.m | 2 + MVMCoreUI/Molecules/ModuleMolecule.swift | 7 +++ .../MoleculeCollectionViewCell.swift | 9 +--- MVMCoreUI/Molecules/MoleculeStackView.swift | 9 +--- 7 files changed, 59 insertions(+), 29 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h index 9ec6c193..acf451a8 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h @@ -85,4 +85,7 @@ /// Convenience function for getting the alignment from a map. + (UIStackViewAlignment)getAlignmentForString:(nullable NSString *)alignmentString defaultAlignment:(UIStackViewAlignment)defaultAlignment; +/// Makes the view isAccessibilityElement false and adds molecule elements to accessbilityElements. +- (void)setMoleculeAccessibility; + @end diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m index 093e1ecc..c6152bcc 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m @@ -27,6 +27,7 @@ [self addConstrainedView:molecule alignment:alignment]; [self setAsMolecule]; } + [self setMoleculeAccessibility]; self.molecule = molecule; } return self; @@ -327,6 +328,17 @@ #pragma mark - MVMCoreUIMoleculeViewProtocol +- (void)setMoleculeAccessibility { + if (self.molecule) { + self.isAccessibilityElement = NO; + if (self.molecule.accessibilityElements) { + self.accessibilityElements = self.molecule.accessibilityElements; + } else { + self.accessibilityElements = @[self.molecule]; + } + } +} + - (void)reset { if ([self.molecule respondsToSelector:@selector(reset)]) { [self.molecule performSelector:@selector(reset)]; @@ -350,6 +362,7 @@ [self pinViewToSuperView:molecule]; } } + [self setMoleculeAccessibility]; } if (self.molecule) { if ([self.molecule respondsToSelector:@selector(copyBackgroundColor)] && [self.molecule performSelector:@selector(copyBackgroundColor)]) { diff --git a/MVMCoreUI/Molecules/Carousel.swift b/MVMCoreUI/Molecules/Carousel.swift index 27cc59f6..082fcd6d 100644 --- a/MVMCoreUI/Molecules/Carousel.swift +++ b/MVMCoreUI/Molecules/Carousel.swift @@ -68,7 +68,8 @@ open class Carousel: ViewConstrainingView { open override func updateView(_ size: CGFloat) { super.updateView(size) collectionView.collectionViewLayout.invalidateLayout() - + showPeaking(false) + // Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled. DispatchQueue.main.async { self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) @@ -141,7 +142,6 @@ open class Carousel: ViewConstrainingView { var pagingView: (UIView & MVMCoreUIPagingProtocol)? = nil if let json = json { pagingView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: json, delegateObject: delegateObject, constrainIfNeeded: true) as? (UIView & MVMCoreUIPagingProtocol) - pagingView?.isAccessibilityElement = true } addPaging(view: pagingView, position: (json?.optionalCGFloatForKey("position") ?? 20)) } @@ -205,7 +205,7 @@ open class Carousel: ViewConstrainingView { } open func showPeaking(_ peaking: Bool) { - if peaking { + if peaking && !UIAccessibility.isVoiceOverRunning { // Show overlay and arrow in peaking Cell let visibleItemsPaths = collectionView.indexPathsForVisibleItems.sorted { $0.row < $1.row } if let firstItem = visibleItemsPaths.first, firstItem.row != currentIndex { @@ -221,6 +221,26 @@ open class Carousel: ViewConstrainingView { } } } + + func setAccessiblity(_ cell: UICollectionViewCell?, index: Int) { + guard let cell = cell else { + return + } + if index == currentIndex { + cell.accessibilityElementsHidden = false + var array = cell.accessibilityElements + + if let acc = pagingView?.accessibilityElements { + array?.append(contentsOf: acc) + } else { + array?.append(pagingView!) + } + + self.accessibilityElements = array + } else { + cell.accessibilityElementsHidden = true + } + } } extension Carousel: UICollectionViewDelegateFlowLayout { @@ -246,14 +266,7 @@ extension Carousel: UICollectionViewDataSource { protocolCell.setWithJSON(moleculeInfo.molecule, delegateObject: nil, additionalData: nil) protocolCell.updateView(collectionView.bounds.width) } - - if indexPath.row == currentIndex { - cell.accessibilityElementsHidden = false -// self.accessibilityElements = @[cell.containerView, self.pageControl]; - } else { - cell.accessibilityElementsHidden = true - } - + setAccessiblity(cell, index: indexPath.row) return cell } } @@ -261,10 +274,14 @@ extension Carousel: UICollectionViewDataSource { extension Carousel: UIScrollViewDelegate { func goTo(_ index: Int, animated: Bool) { - collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0))?.accessibilityElementsHidden = true + showPeaking(false) + setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index) self.currentIndex = index - collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0))?.accessibilityElementsHidden = false self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: animated) + if let cell = collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)) { + setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index) + UIAccessibility.post(notification: .layoutChanged, argument: cell) + } } func handleUserOnBufferCell() { @@ -353,7 +370,5 @@ extension Carousel: UIScrollViewDelegate { pagingView?.setPage(pageIndex) showPeaking(true) - - //UIAccessibility.post(notification: .layoutChanged, argument: currentIndex) UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification,currentCell.containerView); } } diff --git a/MVMCoreUI/Molecules/MVMCoreUIPageControl.m b/MVMCoreUI/Molecules/MVMCoreUIPageControl.m index cbfe0300..16783ca9 100644 --- a/MVMCoreUI/Molecules/MVMCoreUIPageControl.m +++ b/MVMCoreUI/Molecules/MVMCoreUIPageControl.m @@ -241,6 +241,8 @@ static CGFloat const IndicatorRectangleHeight = 4; - (void)setupView { if (!self.containerView) { + self.isAccessibilityElement = YES; + // Create a container view that keeps everything centered UIView *containerView = [MVMCoreUICommonViewsUtility commonView]; [self addSubview:containerView]; diff --git a/MVMCoreUI/Molecules/ModuleMolecule.swift b/MVMCoreUI/Molecules/ModuleMolecule.swift index 7781021f..c993b0c2 100644 --- a/MVMCoreUI/Molecules/ModuleMolecule.swift +++ b/MVMCoreUI/Molecules/ModuleMolecule.swift @@ -31,6 +31,13 @@ open class ModuleMolecule: ViewConstrainingView { addSubview(moleculeView) NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: moleculeView, useMargins: false).values)) moduleMolecule = moleculeView + + isAccessibilityElement = false + if moleculeView.accessibilityElements != nil { + accessibilityElements = moleculeView.accessibilityElements + } else { + accessibilityElements = [moleculeView] + } } } else { moduleMolecule?.setWithJSON(module, delegateObject: delegateObject, additionalData: additionalData) diff --git a/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift b/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift index 82d7f4a2..5f18d454 100644 --- a/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift @@ -73,13 +73,7 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi if molecule == nil { if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) { contentView.insertSubview(moleculeView, at: 0) - 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 - } + NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: moleculeView, useMargins: false).values)) molecule = moleculeView } } else { @@ -91,6 +85,7 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi castView.shouldSetVerticalMargins?(standardConstraints) } backgroundColor = molecule?.backgroundColor + accessibilityElements = molecule?.subviews } public static func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { diff --git a/MVMCoreUI/Molecules/MoleculeStackView.swift b/MVMCoreUI/Molecules/MoleculeStackView.swift index 3183800f..f769bb05 100644 --- a/MVMCoreUI/Molecules/MoleculeStackView.swift +++ b/MVMCoreUI/Molecules/MoleculeStackView.swift @@ -175,13 +175,8 @@ public class MoleculeStackView: ViewConstrainingView { spacing = json?.optionalCGFloatForKey("spacing") ?? 16 // Set the alignment for the stack in the containing view. The json driven value is for the axis direction alignment. - if axis == .vertical { - alignHorizontal(.fill) - alignVertical(ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("alignment"), defaultAlignment: .fill)) - } else { - alignHorizontal(ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("alignment"), defaultAlignment: .fill)) - alignVertical(.fill) - } + alignHorizontal(ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("horizontalAlignment"), defaultAlignment: .fill)) + alignVertical(ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("verticalAlignment"), defaultAlignment: .fill)) // Adds the molecules and sets the json. for (index, map) in molecules.enumerated() { From 3fefff2d518e355e3a997024ed4af28b469caf11 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Mon, 15 Jul 2019 10:41:41 -0400 Subject: [PATCH 06/12] still need to workout how to know when to use margins... --- MVMCoreUI/Molecules/MoleculeStackView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/MVMCoreUI/Molecules/MoleculeStackView.swift b/MVMCoreUI/Molecules/MoleculeStackView.swift index f769bb05..74178f5f 100644 --- a/MVMCoreUI/Molecules/MoleculeStackView.swift +++ b/MVMCoreUI/Molecules/MoleculeStackView.swift @@ -140,6 +140,7 @@ public class MoleculeStackView: ViewConstrainingView { public override func shouldSetHorizontalMargins(_ shouldSet: Bool) { super.shouldSetHorizontalMargins(shouldSet) + updateViewHorizontalDefaults = false moleculesShouldSetHorizontalMargins = (shouldSet && axis == .vertical) for item in items { (item.view as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(moleculesShouldSetHorizontalMargins) From b8b1e0ddba0cc7ad1976380c0cdfa0fb0242b4df Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Mon, 15 Jul 2019 11:43:42 -0400 Subject: [PATCH 07/12] Expose status bar color setting. --- MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h index 5f49eb13..9850cca3 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h @@ -50,4 +50,7 @@ // Get the background color based on the type - (nonnull UIColor *)getBackgroundColorForType:(nullable NSString *)type; +// Set the status bar color. Used for updating the status bar when the view changes. +- (void)setStatusBarColor:(nullable UIColor *)statusBarColor statusBarStyle:(UIStatusBarStyle)style; + @end From 5b1fab39a7960e11a743839f2cbd803a3de8ed4a Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 19 Jul 2019 10:14:51 -0400 Subject: [PATCH 08/12] alignment defaults change --- MVMCoreUI/Atoms/Views/MFView.m | 2 +- MVMCoreUI/Atoms/Views/ViewConstrainingView.h | 3 +- MVMCoreUI/Atoms/Views/ViewConstrainingView.m | 75 +++++++++++-------- .../MoleculeCollectionViewCell.swift | 35 +++++++-- MVMCoreUI/Molecules/MoleculeStackView.swift | 32 ++------ .../Molecules/MoleculeTableViewCell.swift | 56 +++++++++----- MVMCoreUI/Molecules/StandardFooterView.swift | 6 +- MVMCoreUI/Molecules/StandardHeaderView.swift | 16 ++-- MVMCoreUI/Styles/MFStyler.m | 6 +- MVMCoreUI/Utility/MVMCoreUIUtility.h | 7 ++ MVMCoreUI/Utility/MVMCoreUIUtility.m | 18 +++++ 11 files changed, 158 insertions(+), 98 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/MFView.m b/MVMCoreUI/Atoms/Views/MFView.m index 28bcdd81..9eeb673a 100644 --- a/MVMCoreUI/Atoms/Views/MFView.m +++ b/MVMCoreUI/Atoms/Views/MFView.m @@ -39,7 +39,7 @@ } - (void)setupView { - self.preservesSuperviewLayoutMargins = YES; + self.preservesSuperviewLayoutMargins = NO; } - (void)updateView:(CGFloat)size { diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h index acf451a8..55280666 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h @@ -30,8 +30,9 @@ @property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *topPinLow; @property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *bottomPinLow; -/// In updateView, will set horizontal padding to default if set to YES. +/// In updateView, will set horizontal padding to default if set to NO. @property (nonatomic) BOOL updateViewHorizontalDefaults; +@property (nonatomic) BOOL updateViewVerticalDefaults; /// A molecule if we constrain one. @property (weak, nullable, nonatomic) UIView *molecule; diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m index c6152bcc..73e545c5 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m @@ -14,6 +14,8 @@ #import "MFStyler.h" #import "MVMCoreUIConstants.h" #import "MVMCoreUIMoleculeMappingObject.h" +#import "MVMCoreUIUtility.h" +#import "MVMCoreUIViewConstrainingProtocol.h" @interface ViewConstrainingView () @property (weak, nullable, nonatomic) UIView *constrainedView; @@ -27,8 +29,8 @@ [self addConstrainedView:molecule alignment:alignment]; [self setAsMolecule]; } - [self setMoleculeAccessibility]; self.molecule = molecule; + [self setMoleculeAccessibility]; } return self; } @@ -280,21 +282,14 @@ } - (void)shouldSetHorizontalMargins:(BOOL)shouldSet { - self.updateViewHorizontalDefaults = shouldSet; + if (![self.json optionalNumberForKey:@"useHorizontalMargins"]) { + self.updateViewHorizontalDefaults = shouldSet; + } } - (void)shouldSetVerticalMargins:(BOOL)shouldSet { - BOOL useStandardSpacing = shouldSet; - if (shouldSet && [self.molecule respondsToSelector:@selector(useStandardConstraints)]) { - useStandardSpacing = [((UIView *)self.molecule) useStandardConstraints]; - } - - if (useStandardSpacing) { - [self setTopPinConstant:PaddingDefaultVerticalSpacing]; - [self setBottomPinConstant:PaddingDefaultVerticalSpacing]; - } else { - [self setTopPinConstant:0]; - [self setBottomPinConstant:0]; + if (![self.json optionalNumberForKey:@"useVerticalMargins"]) { + self.updateViewVerticalDefaults = shouldSet; } } @@ -304,11 +299,7 @@ [super setupView]; self.translatesAutoresizingMaskIntoConstraints = NO; self.backgroundColor = [UIColor clearColor]; - if (@available(iOS 11.0, *)) { - self.directionalLayoutMargins = NSDirectionalEdgeInsetsZero; - } else { - self.layoutMargins = UIEdgeInsetsZero; - } + [MVMCoreUIUtility setMarginsForView:self leading:0 top:0 trailing:0 bottom:0]; } - (void)updateView:(CGFloat)size { @@ -316,14 +307,12 @@ if ([self.constrainedView respondsToSelector:@selector(updateView:)]) { [((id)self.constrainedView) updateView:size]; } - if (self.updateViewHorizontalDefaults) { - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - CGFloat padding = [MFStyler defaultHorizontalPaddingForSize:size]; - [self setLeftPinConstant:padding]; - [self setRightPinConstant:padding]; - [MFStyler setDefaultMarginsForView:self size:size]; - }]; - } + [MFStyler setDefaultMarginsForView:self size:size horizontal:self.updateViewHorizontalDefaults vertical:self.updateViewVerticalDefaults]; + UIEdgeInsets margins = [MVMCoreUIUtility getMarginsForView:self]; + [self setLeftPinConstant:margins.left]; + [self setRightPinConstant:margins.right]; + [self setTopPinConstant:margins.top]; + [self setBottomPinConstant:margins.bottom]; } #pragma mark - MVMCoreUIMoleculeViewProtocol @@ -340,13 +329,18 @@ } - (void)reset { + self.updateViewHorizontalDefaults = NO; + self.updateViewVerticalDefaults = NO; + if ([self.molecule respondsToSelector:@selector(alignment)]) { + [self alignHorizontal:[(UIView *)self.molecule alignment]]; + } + [self alignVertical:UIStackViewAlignmentFill]; if ([self.molecule respondsToSelector:@selector(reset)]) { [self.molecule performSelector:@selector(reset)]; } } - (void)setAsMolecule { - self.updateViewHorizontalDefaults = YES; } - (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData { @@ -361,13 +355,32 @@ [self insertSubview:molecule atIndex:0]; [self pinViewToSuperView:molecule]; } + self.molecule = molecule; } [self setMoleculeAccessibility]; } - if (self.molecule) { - if ([self.molecule respondsToSelector:@selector(copyBackgroundColor)] && [self.molecule performSelector:@selector(copyBackgroundColor)]) { - self.backgroundColor = self.molecule.backgroundColor; - } + + NSNumber *useHorizontalMargins = [json optionalNumberForKey:@"useHorizontalMargins"]; + if (useHorizontalMargins) { + self.updateViewHorizontalDefaults = [useHorizontalMargins boolValue]; + } + NSNumber *useVerticalMargins = [json optionalNumberForKey:@"useVerticalMargins"]; + if (useVerticalMargins) { + self.updateViewVerticalDefaults = [useVerticalMargins boolValue]; + } + + // Set the alignment for the stack in the containing view. The json driven value is for the axis direction alignment. + NSString *alignment = [json string:@"horizontalAlignment"]; + if (alignment) { + [self alignHorizontal:[ViewConstrainingView getAlignmentForString:alignment defaultAlignment:UIStackViewAlignmentFill]]; + } + alignment = [json string:@"verticalAlignment"]; + if (alignment) { + [self alignVertical:[ViewConstrainingView getAlignmentForString:alignment defaultAlignment:UIStackViewAlignmentFill]]; + } + + if ([self.molecule respondsToSelector:@selector(copyBackgroundColor)] && [self.molecule performSelector:@selector(copyBackgroundColor)]) { + self.backgroundColor = self.molecule.backgroundColor; } } diff --git a/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift b/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift index 5f18d454..6afec1bc 100644 --- a/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift @@ -10,7 +10,12 @@ import UIKit open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeViewProtocol, MoleculeListCellProtocol { open var molecule: (UIView & MVMCoreUIMoleculeViewProtocol)? + open var json: [AnyHashable: Any]? + // In updateView, will set padding to default. + open var updateViewHorizontalDefaults = true + open var updateViewVerticalDefaults = true + open var allowsPeaking = false var peakingLeftArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) var peakingRightArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) @@ -58,6 +63,14 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi } public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + self.json = json + + if let useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") { + updateViewHorizontalDefaults = useHorizontalMargins + } + if let useVerticalMargins = json?.optionalBoolForKey("useVerticalMargins") { + updateViewVerticalDefaults = useVerticalMargins + } // Handles peaking. allowsPeaking = json?.optionalBoolForKey("peakingUI") ?? true @@ -67,27 +80,38 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi peakingRightArrow.tintColor = color } + if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) { + backgroundColor = UIColor.mfGet(forHex: backgroundColorString) + } + guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule) else { return } if molecule == nil { if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) { contentView.insertSubview(moleculeView, at: 0) - NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: moleculeView, useMargins: false).values)) + NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: moleculeView, useMargins: true).values)) molecule = moleculeView } } else { molecule?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) } + // This molecule will handle spacing by default. if let castView = molecule as? MVMCoreUIViewConstrainingProtocol { - let standardConstraints = castView.useStandardConstraints?() ?? true - castView.shouldSetHorizontalMargins?(standardConstraints) - castView.shouldSetVerticalMargins?(standardConstraints) + castView.shouldSetHorizontalMargins?(false) + castView.shouldSetVerticalMargins?(false) } - backgroundColor = molecule?.backgroundColor + accessibilityElements = molecule?.subviews } + public func reset() { + molecule?.reset?() + updateViewVerticalDefaults = true + updateViewHorizontalDefaults = true + backgroundColor = .white + } + public static func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { guard let molecule = molecule?.optionalDictionaryForKey(KeyMolecule) else { return nil @@ -97,6 +121,7 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi public func updateView(_ size: CGFloat) { molecule?.updateView(size) + MFStyler.setDefaultMarginsFor(contentView, size: size, horizontal: updateViewHorizontalDefaults, vertical: updateViewVerticalDefaults) } public func setPeaking(_ peaking: Bool, animated: Bool) { diff --git a/MVMCoreUI/Molecules/MoleculeStackView.swift b/MVMCoreUI/Molecules/MoleculeStackView.swift index 74178f5f..ad395eec 100644 --- a/MVMCoreUI/Molecules/MoleculeStackView.swift +++ b/MVMCoreUI/Molecules/MoleculeStackView.swift @@ -41,13 +41,12 @@ public class MoleculeStackView: ViewConstrainingView { var items: [StackItem] = [] var useStackSpacingBeforeFirstItem = false - private var moleculesShouldSetHorizontalMargins = true + private var moleculesShouldSetHorizontalMargins = false private var moleculesShouldSetVerticalMargins = false /// For setting the direction of the stack var axis: NSLayoutConstraint.Axis = .vertical { didSet { - moleculesShouldSetHorizontalMargins = (moleculesShouldSetHorizontalMargins && axis == .vertical) if axis != oldValue { restack() } @@ -109,6 +108,8 @@ public class MoleculeStackView: ViewConstrainingView { guard contentView.superview == nil else { return } + MVMCoreUIUtility.setMarginsFor(contentView, leading: 0, top: 0, trailing: 0, bottom: 0) + updateViewHorizontalDefaults = true translatesAutoresizingMaskIntoConstraints = false backgroundColor = .clear addSubview(contentView) @@ -125,12 +126,10 @@ public class MoleculeStackView: ViewConstrainingView { } // MARK: - MVMCoreUIMoleculeViewProtocol - public override func setAsMolecule() { - updateViewHorizontalDefaults = false - } - public override func reset() { + super.reset() backgroundColor = .clear + updateViewHorizontalDefaults = true for item in items { if let view = item.view as? MVMCoreUIMoleculeViewProtocol { view.reset?() @@ -138,23 +137,6 @@ public class MoleculeStackView: ViewConstrainingView { } } - public override func shouldSetHorizontalMargins(_ shouldSet: Bool) { - super.shouldSetHorizontalMargins(shouldSet) - updateViewHorizontalDefaults = false - moleculesShouldSetHorizontalMargins = (shouldSet && axis == .vertical) - for item in items { - (item.view as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(moleculesShouldSetHorizontalMargins) - } - } - - public override func shouldSetVerticalMargins(_ shouldSet: Bool) { - super.shouldSetVerticalMargins(shouldSet) - moleculesShouldSetVerticalMargins = false - for item in items { - (item.view as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(moleculesShouldSetVerticalMargins) - } - } - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { let previousJSON = self.json super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) @@ -175,10 +157,6 @@ public class MoleculeStackView: ViewConstrainingView { setAxisWithJSON(json) spacing = json?.optionalCGFloatForKey("spacing") ?? 16 - // Set the alignment for the stack in the containing view. The json driven value is for the axis direction alignment. - alignHorizontal(ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("horizontalAlignment"), defaultAlignment: .fill)) - alignVertical(ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("verticalAlignment"), defaultAlignment: .fill)) - // Adds the molecules and sets the json. for (index, map) in molecules.enumerated() { if let moleculeJSON = map.optionalDictionaryForKey(KeyMolecule) { diff --git a/MVMCoreUI/Molecules/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/MoleculeTableViewCell.swift index fc681c12..eff1e5c5 100644 --- a/MVMCoreUI/Molecules/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/MoleculeTableViewCell.swift @@ -13,6 +13,10 @@ import UIKit open var molecule: (UIView & MVMCoreUIMoleculeViewProtocol)? open var json: [AnyHashable: Any]? + // In updateView, will set padding to default. + open var updateViewHorizontalDefaults = true + open var updateViewVerticalDefaults = true + // For the accessory view convenience. public var caretView: CaretView? private var caretViewWidthSizeObject: MFSizeObject? @@ -41,7 +45,7 @@ import UIKit // MARK: - MFViewProtocol public func updateView(_ size: CGFloat) { - MFStyler.setDefaultMarginsFor(self, size: size, horizontal: true, vertical: true) + MFStyler.setDefaultMarginsFor(self, size: size, horizontal: updateViewHorizontalDefaults, vertical: updateViewVerticalDefaults) if #available(iOS 11.0, *) { if accessoryView != nil { // Smaller left margin if accessory view. @@ -83,6 +87,31 @@ import UIKit // MARK: - MVMCoreUIMoleculeViewProtocol public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { self.json = json; + + if let useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") { + updateViewHorizontalDefaults = useHorizontalMargins + } + if let useVerticalMargins = json?.optionalBoolForKey("useVerticalMargins") { + updateViewVerticalDefaults = useVerticalMargins + } + + if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) { + backgroundColor = UIColor.mfGet(forHex: backgroundColorString) + } + + // Add the caret if there is an action and it's not declared hidden. + if let _ = json?.optionalDictionaryForKey("actionMap"), json!.boolForKey("hideArrow") { + addCaretViewAccessory() + } else { + accessoryView = nil + } + + // override the separator + if let separator = json?.optionalDictionaryForKey("separator") { + addSeparatorsIfNeeded() + bottomSeparatorView?.setWithJSON(separator, delegateObject: delegateObject, additionalData: additionalData) + } + guard let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule) else { return } @@ -101,30 +130,19 @@ import UIKit } else { molecule?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) } + + // This molecule will by default handle margins. if let castView = molecule as? MVMCoreUIViewConstrainingProtocol { - let standardConstraints = castView.useStandardConstraints?() ?? true - castView.shouldSetHorizontalMargins?(!standardConstraints) - castView.shouldSetVerticalMargins?(!standardConstraints) - } - - backgroundColor = molecule?.backgroundColor - - // Add the caret if there is an action and it's not declared hidden. - if let _ = json.optionalDictionaryForKey("actionMap"), !json.boolForKey("hideArrow") { - addCaretViewAccessory() - } else { - accessoryView = nil - } - - // override the separator - if let separator = json.optionalDictionaryForKey("separator") { - addSeparatorsIfNeeded() - bottomSeparatorView?.setWithJSON(separator, delegateObject: delegateObject, additionalData: additionalData) + castView.shouldSetHorizontalMargins?(false) + castView.shouldSetVerticalMargins?(false) } } public func reset() { molecule?.reset?() + updateViewVerticalDefaults = true + updateViewHorizontalDefaults = true + backgroundColor = .white } public static func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { diff --git a/MVMCoreUI/Molecules/StandardFooterView.swift b/MVMCoreUI/Molecules/StandardFooterView.swift index a135ccc0..8f4ca0b5 100644 --- a/MVMCoreUI/Molecules/StandardFooterView.swift +++ b/MVMCoreUI/Molecules/StandardFooterView.swift @@ -12,14 +12,16 @@ open class StandardFooterView: ViewConstrainingView { open override func setupView() { super.setupView() setupMoleculeFromJSON = true + updateViewVerticalDefaults = true + updateViewHorizontalDefaults = true } open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + // This molecule will by default handle margins. (molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(false) (molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(false) - shouldSetVerticalMargins(true) - shouldSetHorizontalMargins(true) } public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { diff --git a/MVMCoreUI/Molecules/StandardHeaderView.swift b/MVMCoreUI/Molecules/StandardHeaderView.swift index ff5b8538..fbaa5518 100644 --- a/MVMCoreUI/Molecules/StandardHeaderView.swift +++ b/MVMCoreUI/Molecules/StandardHeaderView.swift @@ -15,11 +15,18 @@ public class StandardHeaderView: ViewConstrainingView { open override func updateView(_ size: CGFloat) { super.updateView(size) separatorView?.updateView(size) + if separatorView?.isHidden ?? true { + let margins = MVMCoreUIUtility.getMarginsFor(self) + MVMCoreUIUtility.setMarginsFor(self, leading: margins.left, top: margins.top, trailing: margins.right, bottom: 0) + bottomPin?.constant = 0 + } } public override func setupView() { super.setupView() setupMoleculeFromJSON = true + updateViewVerticalDefaults = true + updateViewHorizontalDefaults = true if separatorView == nil, let separatorView = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot, withHorizontalPadding: 0) { separatorView.setAsHeavy() addSubview(separatorView) @@ -40,19 +47,14 @@ public class StandardHeaderView: ViewConstrainingView { // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + // This molecule will by default handle margins. (molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(false) (molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(false) - shouldSetVerticalMargins(true) - shouldSetHorizontalMargins(true) if let separatorJSON = json?.optionalDictionaryForKey("separator") { separatorView?.setWithJSON(separatorJSON, delegateObject: delegateObject, additionalData: additionalData) } - if separatorView?.isHidden ?? true { - bottomPin?.constant = 0 - } else { - bottomPin?.constant = PaddingDefaultVerticalSpacing - } } open override func reset() { diff --git a/MVMCoreUI/Styles/MFStyler.m b/MVMCoreUI/Styles/MFStyler.m index dc9288d8..2b0d0a0a 100644 --- a/MVMCoreUI/Styles/MFStyler.m +++ b/MVMCoreUI/Styles/MFStyler.m @@ -98,11 +98,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; [MVMCoreDispatchUtility performBlockOnMainThread:^{ CGFloat horizontalPadding = horizontal ? [MFStyler defaultHorizontalPaddingForSize:size] : 0; CGFloat verticalPadding = vertical ? PaddingDefaultVerticalSpacing : 0; - if (@available(iOS 11.0, *)) { - view.directionalLayoutMargins = NSDirectionalEdgeInsetsMake(verticalPadding, horizontalPadding, verticalPadding, horizontalPadding); - } else { - view.layoutMargins = UIEdgeInsetsMake(verticalPadding, horizontalPadding, verticalPadding, horizontalPadding); - } + [MVMCoreUIUtility setMarginsForView:view leading:horizontalPadding top:verticalPadding trailing:horizontalPadding bottom:verticalPadding]; }]; } diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.h b/MVMCoreUI/Utility/MVMCoreUIUtility.h index 81545583..b6698537 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility.h +++ b/MVMCoreUI/Utility/MVMCoreUIUtility.h @@ -28,6 +28,13 @@ NS_ASSUME_NONNULL_BEGIN // Returns an image from this framework's bundle + (nullable UIImage *)imageNamed:(nullable NSString *)imageName; +// Returns the margins for a view. ++ (UIEdgeInsets)getMarginsForView:(nullable UIView *)view; + +#pragma mark - Setters + ++ (void)setMarginsForView:(nullable UIView *)view leading:(CGFloat)leading top:(CGFloat)top trailing:(CGFloat)trailing bottom:(CGFloat)bottom; + #pragma mark - Formatting // Formats the given mdn. diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.m b/MVMCoreUI/Utility/MVMCoreUIUtility.m index 1afabeab..8201e999 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUIUtility.m @@ -57,6 +57,24 @@ return image; } ++ (UIEdgeInsets)getMarginsForView:(nullable UIView *)view { + if (@available(iOS 11.0, *)) { + return UIEdgeInsetsMake(view.directionalLayoutMargins.top, view.directionalLayoutMargins.leading, view.directionalLayoutMargins.bottom, view.directionalLayoutMargins.trailing); + } else { + return view.layoutMargins; + } +} + +#pragma mark - Setters + ++ (void)setMarginsForView:(nullable UIView *)view leading:(CGFloat)leading top:(CGFloat)top trailing:(CGFloat)trailing bottom:(CGFloat)bottom { + if (@available(iOS 11.0, *)) { + view.directionalLayoutMargins = NSDirectionalEdgeInsetsMake(top, leading, bottom, trailing); + } else { + view.layoutMargins = UIEdgeInsetsMake(top, leading, bottom, trailing); + } +} + #pragma mark - Formatting // As per business confirmation format should be xxx.xxx.xxxx same across application From 5c619aa8430db261f327be6c9cdfcea43563dc28 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 19 Jul 2019 10:48:01 -0400 Subject: [PATCH 09/12] remove preserves --- MVMCoreUI/Atoms/Views/MFView.m | 1 - MVMCoreUI/Molecules/MoleculeTableViewCell.swift | 2 -- 2 files changed, 3 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/MFView.m b/MVMCoreUI/Atoms/Views/MFView.m index 9eeb673a..937da3f2 100644 --- a/MVMCoreUI/Atoms/Views/MFView.m +++ b/MVMCoreUI/Atoms/Views/MFView.m @@ -39,7 +39,6 @@ } - (void)setupView { - self.preservesSuperviewLayoutMargins = NO; } - (void)updateView:(CGFloat)size { diff --git a/MVMCoreUI/Molecules/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/MoleculeTableViewCell.swift index eff1e5c5..492e7d4d 100644 --- a/MVMCoreUI/Molecules/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/MoleculeTableViewCell.swift @@ -79,8 +79,6 @@ import UIKit } public func setupView() { - preservesSuperviewLayoutMargins = false - contentView.preservesSuperviewLayoutMargins = false selectionStyle = .none } From 8400c0eae86a39ed34f39fc081c3d6174273c558 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 19 Jul 2019 10:53:16 -0400 Subject: [PATCH 10/12] name change --- MVMCoreUI/Atoms/Views/ViewConstrainingView.h | 2 +- MVMCoreUI/Atoms/Views/ViewConstrainingView.m | 2 +- MVMCoreUI/Molecules/StandardFooterView.swift | 2 +- MVMCoreUI/Molecules/StandardHeaderView.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h index 55280666..995753b1 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h @@ -38,7 +38,7 @@ @property (weak, nullable, nonatomic) UIView *molecule; /// A flag for if we should add a molecule from json. -@property (nonatomic) BOOL setupMoleculeFromJSON; +@property (nonatomic) BOOL shouldSetupMoleculeFromJSON; // Returns an empty view + (nonnull ViewConstrainingView *)emptyView; diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m index 73e545c5..c3254678 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m @@ -347,7 +347,7 @@ [super setWithJSON:json delegateObject:delegateObject additionalData:additionalData]; [self.molecule setWithJSON:json delegateObject:delegateObject additionalData:additionalData]; - if (self.setupMoleculeFromJSON && !self.molecule) { + if (self.shouldSetupMoleculeFromJSON && !self.molecule) { NSDictionary *moleculeJSON = [json dict:KeyMolecule]; if (moleculeJSON) { UIView *molecule = [[MVMCoreUIMoleculeMappingObject sharedMappingObject] createMoleculeForJSON:moleculeJSON delegateObject:delegateObject constrainIfNeeded:true]; diff --git a/MVMCoreUI/Molecules/StandardFooterView.swift b/MVMCoreUI/Molecules/StandardFooterView.swift index 8f4ca0b5..f00af028 100644 --- a/MVMCoreUI/Molecules/StandardFooterView.swift +++ b/MVMCoreUI/Molecules/StandardFooterView.swift @@ -11,7 +11,7 @@ import UIKit open class StandardFooterView: ViewConstrainingView { open override func setupView() { super.setupView() - setupMoleculeFromJSON = true + shouldSetupMoleculeFromJSON = true updateViewVerticalDefaults = true updateViewHorizontalDefaults = true } diff --git a/MVMCoreUI/Molecules/StandardHeaderView.swift b/MVMCoreUI/Molecules/StandardHeaderView.swift index fbaa5518..0e6c6776 100644 --- a/MVMCoreUI/Molecules/StandardHeaderView.swift +++ b/MVMCoreUI/Molecules/StandardHeaderView.swift @@ -24,7 +24,7 @@ public class StandardHeaderView: ViewConstrainingView { public override func setupView() { super.setupView() - setupMoleculeFromJSON = true + shouldSetupMoleculeFromJSON = true updateViewVerticalDefaults = true updateViewHorizontalDefaults = true if separatorView == nil, let separatorView = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot, withHorizontalPadding: 0) { From c5f8d276367003fb16c764c555240daf9d197e2d Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 19 Jul 2019 11:34:22 -0400 Subject: [PATCH 11/12] fix prod break defect --- MVMCoreUI/Atoms/Views/ViewConstrainingView.m | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m index c3254678..6295a218 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m @@ -309,10 +309,14 @@ } [MFStyler setDefaultMarginsForView:self size:size horizontal:self.updateViewHorizontalDefaults vertical:self.updateViewVerticalDefaults]; UIEdgeInsets margins = [MVMCoreUIUtility getMarginsForView:self]; - [self setLeftPinConstant:margins.left]; - [self setRightPinConstant:margins.right]; - [self setTopPinConstant:margins.top]; - [self setBottomPinConstant:margins.bottom]; + if (self.updateViewHorizontalDefaults) { + [self setLeftPinConstant:margins.left]; + [self setRightPinConstant:margins.right]; + } + if (self.updateViewVerticalDefaults) { + [self setTopPinConstant:margins.top]; + [self setBottomPinConstant:margins.bottom]; + } } #pragma mark - MVMCoreUIMoleculeViewProtocol From f12805ba2a659d90da24989c3895b2d1daaed138 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Mon, 22 Jul 2019 09:53:22 -0400 Subject: [PATCH 12/12] update comment --- MVMCoreUI/Atoms/Views/ViewConstrainingView.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h index 995753b1..b07fd6bb 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h @@ -30,7 +30,7 @@ @property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *topPinLow; @property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *bottomPinLow; -/// In updateView, will set horizontal padding to default if set to NO. +/// In updateView, will set horizontal padding to default. @property (nonatomic) BOOL updateViewHorizontalDefaults; @property (nonatomic) BOOL updateViewVerticalDefaults;