Add action to collection cells
make paging configurable for carousel. add insets to carousel use button helper function for collection and table cell.
This commit is contained in:
parent
ce5b5a77ca
commit
57d7a5f7cc
@ -33,7 +33,7 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
|
|||||||
public var disabledIndicatorColor: Color = Color(uiColor: .mvmCoolGray3)
|
public var disabledIndicatorColor: Color = Color(uiColor: .mvmCoolGray3)
|
||||||
public var indicatorColor: Color = Color(uiColor: .mvmBlack)
|
public var indicatorColor: Color = Color(uiColor: .mvmBlack)
|
||||||
public var indicatorColor_inverted: Color = Color(uiColor: .mvmWhite)
|
public var indicatorColor_inverted: Color = Color(uiColor: .mvmWhite)
|
||||||
public var position: Float?
|
public var position: CGFloat?
|
||||||
|
|
||||||
/// Allows sendActions() to trigger even if index is already at min/max index.
|
/// Allows sendActions() to trigger even if index is already at min/max index.
|
||||||
public var alwaysSendAction = false
|
public var alwaysSendAction = false
|
||||||
@ -79,7 +79,7 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
|
|||||||
self.inverted = inverted
|
self.inverted = inverted
|
||||||
}
|
}
|
||||||
|
|
||||||
if let position = try typeContainer.decodeIfPresent(Float.self, forKey: .position) {
|
if let position = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .position) {
|
||||||
self.position = position
|
self.position = position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,12 @@ import Foundation
|
|||||||
return "collectionItem"
|
return "collectionItem"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var action: ActionModelProtocol?
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case action
|
||||||
|
}
|
||||||
|
|
||||||
/// Defaults to set
|
/// Defaults to set
|
||||||
public override func setDefaults() {
|
public override func setDefaults() {
|
||||||
if useHorizontalMargins == nil {
|
if useHorizontalMargins == nil {
|
||||||
@ -35,10 +41,14 @@ import Foundation
|
|||||||
}
|
}
|
||||||
|
|
||||||
required public init(from decoder: Decoder) throws {
|
required public init(from decoder: Decoder) throws {
|
||||||
|
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
|
||||||
try super.init(from: decoder)
|
try super.init(from: decoder)
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func encode(to encoder: Encoder) throws {
|
public override func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
try container.encodeModelIfPresent(action, forKey: .action)
|
||||||
try super.encode(to: encoder)
|
try super.encode(to: encoder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,13 @@ public protocol CarouselPageControlProtocol {
|
|||||||
|
|
||||||
open class Carousel: View {
|
open class Carousel: View {
|
||||||
|
|
||||||
public let collectionView = CollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
|
public let collectionView: CollectionView = {
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.scrollDirection = .horizontal
|
||||||
|
layout.minimumInteritemSpacing = 0
|
||||||
|
layout.minimumLineSpacing = 0
|
||||||
|
return CollectionView(frame: .zero, collectionViewLayout: layout)
|
||||||
|
}()
|
||||||
|
|
||||||
/// The current index of the collection view. Includes dummy cells when looping.
|
/// The current index of the collection view. Includes dummy cells when looping.
|
||||||
public var currentIndex = 0
|
public var currentIndex = 0
|
||||||
@ -36,13 +42,13 @@ open class Carousel: View {
|
|||||||
open var numberOfPages = 0
|
open var numberOfPages = 0
|
||||||
|
|
||||||
/// The models for the molecules.
|
/// The models for the molecules.
|
||||||
var molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]?
|
public var molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]?
|
||||||
|
|
||||||
/// The horizontal alignment of the cell in the collection view. Only noticeable if the itemWidthPercent is less than 100%.
|
/// The horizontal alignment of the cell in the collection view. Only noticeable if the itemWidthPercent is less than 100%.
|
||||||
public var itemAlignment = UICollectionView.ScrollPosition.left
|
public var itemAlignment = UICollectionView.ScrollPosition.left
|
||||||
|
|
||||||
/// From 0-1. The item width as a percent of the carousel width.
|
/// From 0-1. The item width as a percent of the carousel width.
|
||||||
public var itemWidthPercent: Float = 1
|
public var itemWidthPercent: CGFloat = 1
|
||||||
|
|
||||||
/// The height of the carousel. Default is 300.
|
/// The height of the carousel. Default is 300.
|
||||||
public var collectionViewHeight: NSLayoutConstraint?
|
public var collectionViewHeight: NSLayoutConstraint?
|
||||||
@ -51,7 +57,7 @@ open class Carousel: View {
|
|||||||
public var pagingView: (UIView & CarouselPageControlProtocol)?
|
public var pagingView: (UIView & CarouselPageControlProtocol)?
|
||||||
|
|
||||||
/// If the carousel should loop after scrolling past the first and final cells.
|
/// If the carousel should loop after scrolling past the first and final cells.
|
||||||
var loop = false
|
public var loop = false
|
||||||
|
|
||||||
private var dragging = false
|
private var dragging = false
|
||||||
|
|
||||||
@ -81,6 +87,8 @@ open class Carousel: View {
|
|||||||
showPeaking(false)
|
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.
|
// 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.
|
||||||
|
guard let model = model as? CarouselModel,
|
||||||
|
(model.paging == true || model.loop == true) else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false)
|
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false)
|
||||||
self.collectionView.layoutIfNeeded()
|
self.collectionView.layoutIfNeeded()
|
||||||
@ -98,15 +106,23 @@ open class Carousel: View {
|
|||||||
collectionView.delegate = self
|
collectionView.delegate = self
|
||||||
addSubview(collectionView)
|
addSubview(collectionView)
|
||||||
bottomPin = NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView)?[ConstraintBot] as? NSLayoutConstraint
|
bottomPin = NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView)?[ConstraintBot] as? NSLayoutConstraint
|
||||||
|
|
||||||
collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300)
|
collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300)
|
||||||
collectionViewHeight?.isActive = false
|
collectionViewHeight?.isActive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func updateView(_ size: CGFloat) {
|
open override func updateView(_ size: CGFloat) {
|
||||||
super.updateView(size)
|
super.updateView(size)
|
||||||
self.size = size
|
self.size = size
|
||||||
|
|
||||||
|
// Set insets for the carousel.
|
||||||
|
var inset = UIEdgeInsets.zero
|
||||||
|
let carouselModel = model as? CarouselModel
|
||||||
|
if carouselModel?.useHorizontalMargins ?? false {
|
||||||
|
inset.left = carouselModel?.leftPadding ?? Padding.Component.horizontalPaddingForSize(size)
|
||||||
|
inset.right = carouselModel?.rightPadding ?? Padding.Component.horizontalPaddingForSize(size)
|
||||||
|
}
|
||||||
|
(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionInset = inset
|
||||||
|
|
||||||
// Update cells and re-layout.
|
// Update cells and re-layout.
|
||||||
for cell in collectionView.visibleCells {
|
for cell in collectionView.visibleCells {
|
||||||
(cell as? MVMCoreViewProtocol)?.updateView(size)
|
(cell as? MVMCoreViewProtocol)?.updateView(size)
|
||||||
@ -128,20 +144,20 @@ open class Carousel: View {
|
|||||||
collectionView.layer.borderColor = backgroundColor?.cgColor
|
collectionView.layer.borderColor = backgroundColor?.cgColor
|
||||||
collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0
|
collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0
|
||||||
backgroundColor = .white
|
backgroundColor = .white
|
||||||
|
(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing = carouselModel.spacing ?? 0
|
||||||
|
|
||||||
registerCells(with: carouselModel, delegateObject: delegateObject)
|
itemWidthPercent = carouselModel.itemWidthPercent / 100.0
|
||||||
setupLayout(with: carouselModel)
|
|
||||||
prepareMolecules(with: carouselModel)
|
|
||||||
itemWidthPercent = (carouselModel.itemWidthPercent ?? 100) / 100
|
|
||||||
if let alignment = carouselModel.itemAlignment {
|
if let alignment = carouselModel.itemAlignment {
|
||||||
itemAlignment = alignment
|
itemAlignment = alignment
|
||||||
}
|
}
|
||||||
|
|
||||||
if let height = carouselModel.height {
|
if let height = carouselModel.height {
|
||||||
collectionViewHeight?.constant = CGFloat(height)
|
collectionViewHeight?.constant = height
|
||||||
collectionViewHeight?.isActive = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerCells(with: carouselModel, delegateObject: delegateObject)
|
||||||
|
prepareMolecules(with: carouselModel)
|
||||||
|
|
||||||
setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject)
|
setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject)
|
||||||
|
|
||||||
pageIndex = carouselModel.index
|
pageIndex = carouselModel.index
|
||||||
@ -153,16 +169,6 @@ open class Carousel: View {
|
|||||||
// MARK: - JSON Setters
|
// MARK: - JSON Setters
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
/// Updates the layout being used
|
|
||||||
func setupLayout(with carouselModel: CarouselModel?) {
|
|
||||||
|
|
||||||
let layout = UICollectionViewFlowLayout()
|
|
||||||
layout.scrollDirection = .horizontal
|
|
||||||
layout.minimumLineSpacing = CGFloat(carouselModel?.spacing ?? 1)
|
|
||||||
layout.minimumInteritemSpacing = 0
|
|
||||||
collectionView.collectionViewLayout = layout
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareMolecules(with carouselModel: CarouselModel?) {
|
func prepareMolecules(with carouselModel: CarouselModel?) {
|
||||||
guard let newMolecules = carouselModel?.molecules else {
|
guard let newMolecules = carouselModel?.molecules else {
|
||||||
numberOfPages = 0
|
numberOfPages = 0
|
||||||
@ -191,7 +197,7 @@ open class Carousel: View {
|
|||||||
pagingView = MoleculeObjectMapping.shared()?.createMolecule(molecule, delegateObject: delegateObject) as? (UIView & CarouselPageControlProtocol)
|
pagingView = MoleculeObjectMapping.shared()?.createMolecule(molecule, delegateObject: delegateObject) as? (UIView & CarouselPageControlProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
addPaging(view: pagingView, position: (CGFloat(molecule?.position ?? 20)))
|
addPaging(view: pagingView, position: molecule?.position ?? 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers the cells with the collection view
|
/// Registers the cells with the collection view
|
||||||
@ -294,7 +300,7 @@ open class Carousel: View {
|
|||||||
|
|
||||||
extension Carousel: UICollectionViewDelegateFlowLayout {
|
extension Carousel: UICollectionViewDelegateFlowLayout {
|
||||||
open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||||
let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent)
|
let itemWidth = collectionView.bounds.width * itemWidthPercent
|
||||||
return CGSize(width: itemWidth, height: collectionView.bounds.height)
|
return CGSize(width: itemWidth, height: collectionView.bounds.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,8 +330,15 @@ extension Carousel: UICollectionViewDataSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Carousel: UICollectionViewDelegate {
|
||||||
|
open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
(collectionView.cellForItem(at: indexPath) as? CollectionTemplateItemProtocol)?.didSelectCell(at: indexPath, delegateObject: delegateObject, additionalData: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension Carousel: UIScrollViewDelegate {
|
extension Carousel: UIScrollViewDelegate {
|
||||||
|
|
||||||
|
/// Go to the cell at the specified index.
|
||||||
func goTo(_ index: Int, animated: Bool) {
|
func goTo(_ index: Int, animated: Bool) {
|
||||||
|
|
||||||
showPeaking(false)
|
showPeaking(false)
|
||||||
@ -339,51 +352,33 @@ extension Carousel: UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUserOnBufferCell() {
|
/// Adjusts the current contentOffset if we are going onto buffer cells while looping to help with the endless scrolling appearance.
|
||||||
guard loop else { return }
|
func adjustOffsetForLooping(_ scrollView: UIScrollView) {
|
||||||
|
let translatedPoint = scrollView.panGestureRecognizer.translation(in: scrollView.superview).x
|
||||||
let lastPageIndex = numberOfPages + 1
|
if translatedPoint > 0 {
|
||||||
let goToIndex = { (index: Int) in
|
// Moving left, see if we are moving passed the first left buffer card and adjust
|
||||||
self.goTo(index, animated: false)
|
if let threshold = collectionView.layoutAttributesForItem(at: IndexPath(item: 1, section: 0))?.frame.minX,
|
||||||
self.collectionView.layoutIfNeeded()
|
scrollView.contentOffset.x < threshold,
|
||||||
self.pagingView?.currentIndex = self.pageIndex
|
let newOffset = collectionView.layoutAttributesForItem(at: IndexPath(item: numberOfPages + 1, section: 0))?.frame.minX {
|
||||||
}
|
scrollView.contentOffset.x = newOffset
|
||||||
|
}
|
||||||
if currentIndex < 2 {
|
} else if translatedPoint < 0 {
|
||||||
// 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.
|
// Moving right, see if we are moving passed the first right buffer card and adjust
|
||||||
goToIndex(lastPageIndex)
|
if let threshold = collectionView.layoutAttributesForItem(at: IndexPath(item: numberOfPages + 2, section: 0))?.frame.maxX,
|
||||||
} else if currentIndex > lastPageIndex {
|
scrollView.contentOffset.x + scrollView.bounds.width > threshold,
|
||||||
// If on the "buffer" first row (which is the index after the real last row), go to the real first row secretly.
|
let newEndOffset = collectionView.layoutAttributesForItem(at: IndexPath(item: 2, section: 0))?.frame.maxX {
|
||||||
goToIndex(2)
|
scrollView.contentOffset.x = newEndOffset - scrollView.bounds.width
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 * CGFloat(itemWidthPercent)
|
|
||||||
let index = scrollView.contentOffset.x / (itemWidth + separatorWidth)
|
|
||||||
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
|
||||||
|
|
||||||
if index < 1 {
|
|
||||||
currentIndex = 0
|
|
||||||
updateModelIndex()
|
|
||||||
} else if index > CGFloat(lastCellIndex - 1) {
|
|
||||||
currentIndex = lastCellIndex
|
|
||||||
updateModelIndex()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUserOnBufferCell()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
open func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
|
||||||
// Check if the user is dragging the card even further past the next card.
|
// Adjust for looping
|
||||||
//checkForDraggingOutOfBounds(scrollView)
|
if let model = model as? CarouselModel,
|
||||||
|
model.loop == true {
|
||||||
|
adjustOffsetForLooping(scrollView)
|
||||||
|
}
|
||||||
|
|
||||||
// Let the pager know our progress if needed.
|
// Let the pager know our progress if needed.
|
||||||
pagingView?.scrollViewDidScroll(collectionView)
|
pagingView?.scrollViewDidScroll(collectionView)
|
||||||
@ -391,6 +386,7 @@ extension Carousel: UIScrollViewDelegate {
|
|||||||
|
|
||||||
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
|
|
||||||
|
// Disable peaking when dragging.
|
||||||
dragging = true
|
dragging = true
|
||||||
showPeaking(false)
|
showPeaking(false)
|
||||||
}
|
}
|
||||||
@ -398,32 +394,63 @@ extension Carousel: UIScrollViewDelegate {
|
|||||||
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||||
|
|
||||||
dragging = false
|
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).
|
// This is for setting up smooth custom paging. (Since UICollectionView only handles paging based on collection view size and not cell size). Math requires that we are using UICollectionViewFlowLayout.
|
||||||
guard let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else { return }
|
guard (model as? CarouselModel)?.paging == true,
|
||||||
|
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
|
||||||
|
|
||||||
// We switch cards if we pass the velocity threshold or position threshold (currently 50%).
|
let separatorWidth = layout.minimumLineSpacing
|
||||||
let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent)
|
let itemWidth = collectionView.bounds.width * itemWidthPercent
|
||||||
var cellToSwipeTo = Int(scrollView.contentOffset.x / (itemWidth + separatorWidth) + 0.5)
|
let width = itemWidth + separatorWidth
|
||||||
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
|
||||||
|
// Adjusts the offset for the contentInset. Adds imaginary half separator to the left of the first card, which is necessary for determining the percent of a given card we are currently at.
|
||||||
|
let adjustedOffset = scrollView.contentOffset.x - layout.sectionInset.left + (separatorWidth / 2)
|
||||||
|
|
||||||
|
// Calculates the offset per card depending on the alignment.
|
||||||
|
var offsetByCard: CGFloat
|
||||||
|
switch itemAlignment {
|
||||||
|
case .right:
|
||||||
|
offsetByCard = ((adjustedOffset + scrollView.bounds.width) / width) - 1
|
||||||
|
case .centeredHorizontally:
|
||||||
|
offsetByCard = ((adjustedOffset + (scrollView.bounds.width / 2)) / width) - 0.5
|
||||||
|
default:
|
||||||
|
offsetByCard = adjustedOffset / width
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust card for velocity impact.
|
||||||
let velocityThreshold: CGFloat = 1.1
|
let velocityThreshold: CGFloat = 1.1
|
||||||
|
var cellToSwipeTo: Int
|
||||||
if velocity.x > velocityThreshold {
|
if velocity.x > velocityThreshold {
|
||||||
cellToSwipeTo = currentIndex + 1
|
cellToSwipeTo = Int(ceil(offsetByCard))
|
||||||
|
|
||||||
} else if velocity.x < -velocityThreshold {
|
} else if velocity.x < -velocityThreshold {
|
||||||
cellToSwipeTo = currentIndex - 1
|
cellToSwipeTo = Int(floor(offsetByCard))
|
||||||
|
} else {
|
||||||
|
cellToSwipeTo = Int(round(offsetByCard))
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are swiping to a buffer cell, change to real cell before beginning animation so we don't go out of bounds.
|
||||||
|
if cellToSwipeTo < 2 {
|
||||||
|
let newOffset = scrollView.contentOffset.x + (width * CGFloat(numberOfPages))
|
||||||
|
scrollView.contentOffset.x = newOffset
|
||||||
|
targetContentOffset.pointee.x = newOffset
|
||||||
|
cellToSwipeTo = cellToSwipeTo + numberOfPages
|
||||||
|
} else if cellToSwipeTo > numberOfPages + 1 {
|
||||||
|
let newOffset = scrollView.contentOffset.x - (width * CGFloat(numberOfPages))
|
||||||
|
scrollView.contentOffset.x = newOffset
|
||||||
|
targetContentOffset.pointee.x = newOffset
|
||||||
|
cellToSwipeTo = cellToSwipeTo - numberOfPages
|
||||||
|
} else {
|
||||||
|
targetContentOffset.pointee = scrollView.contentOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cap the index.
|
// Cap the index.
|
||||||
|
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
||||||
goTo(min(max(cellToSwipeTo, 0), lastCellIndex), 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.
|
// 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) {
|
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
|
||||||
// Cycle to other end if on buffer cell.
|
// Cycle to other end if on buffer cell.
|
||||||
handleUserOnBufferCell()
|
|
||||||
pagingView?.currentIndex = pageIndex
|
pagingView?.currentIndex = pageIndex
|
||||||
showPeaking(true)
|
showPeaking(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,13 +21,17 @@ import UIKit
|
|||||||
public var backgroundColor: Color?
|
public var backgroundColor: Color?
|
||||||
public var molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]
|
public var molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]
|
||||||
public var index: Int = 0
|
public var index: Int = 0
|
||||||
public var spacing: Float?
|
public var spacing: CGFloat?
|
||||||
public var border: Bool?
|
public var border: Bool?
|
||||||
public var loop: Bool?
|
public var loop: Bool?
|
||||||
public var height: Float?
|
public var height: CGFloat?
|
||||||
public var itemWidthPercent: Float?
|
@Percent public var itemWidthPercent = 100
|
||||||
public var itemAlignment: UICollectionView.ScrollPosition?
|
public var itemAlignment: UICollectionView.ScrollPosition?
|
||||||
public var pagingMolecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)?
|
public var pagingMolecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)?
|
||||||
|
public var paging: Bool = true
|
||||||
|
public var useHorizontalMargins: Bool?
|
||||||
|
public var leftPadding: CGFloat?
|
||||||
|
public var rightPadding: CGFloat?
|
||||||
|
|
||||||
public init(molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]) {
|
public init(molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]) {
|
||||||
self.molecules = molecules
|
self.molecules = molecules
|
||||||
@ -49,6 +53,10 @@ import UIKit
|
|||||||
case itemWidthPercent
|
case itemWidthPercent
|
||||||
case itemAlignment
|
case itemAlignment
|
||||||
case pagingMolecule
|
case pagingMolecule
|
||||||
|
case paging
|
||||||
|
case useHorizontalMargins
|
||||||
|
case leftPadding
|
||||||
|
case rightPadding
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -60,13 +68,21 @@ import UIKit
|
|||||||
molecules = try typeContainer.decodeModels(codingKey: .molecules)
|
molecules = try typeContainer.decodeModels(codingKey: .molecules)
|
||||||
index = try typeContainer.decodeIfPresent(Int.self, forKey: .index) ?? 0
|
index = try typeContainer.decodeIfPresent(Int.self, forKey: .index) ?? 0
|
||||||
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
||||||
spacing = try typeContainer.decodeIfPresent(Float.self, forKey: .spacing)
|
spacing = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .spacing)
|
||||||
border = try typeContainer.decodeIfPresent(Bool.self, forKey: .border)
|
border = try typeContainer.decodeIfPresent(Bool.self, forKey: .border)
|
||||||
loop = try typeContainer.decodeIfPresent(Bool.self, forKey: .loop)
|
loop = try typeContainer.decodeIfPresent(Bool.self, forKey: .loop)
|
||||||
height = try typeContainer.decodeIfPresent(Float.self, forKey: .height)
|
height = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .height)
|
||||||
itemWidthPercent = try typeContainer.decodeIfPresent(Float.self, forKey: .itemWidthPercent)
|
if let itemWidthPercent = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .itemWidthPercent) {
|
||||||
|
self.itemWidthPercent = itemWidthPercent
|
||||||
|
}
|
||||||
itemAlignment = try typeContainer.decodeIfPresent(UICollectionView.ScrollPosition.self, forKey: .itemAlignment)
|
itemAlignment = try typeContainer.decodeIfPresent(UICollectionView.ScrollPosition.self, forKey: .itemAlignment)
|
||||||
pagingMolecule = try typeContainer.decodeModelIfPresent(codingKey: .pagingMolecule)
|
pagingMolecule = try typeContainer.decodeModelIfPresent(codingKey: .pagingMolecule)
|
||||||
|
if let paging = try typeContainer.decodeIfPresent(Bool.self, forKey: .paging) {
|
||||||
|
self.paging = paging
|
||||||
|
}
|
||||||
|
useHorizontalMargins = try typeContainer.decodeIfPresent(Bool.self, forKey: .useHorizontalMargins)
|
||||||
|
leftPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .leftPadding)
|
||||||
|
rightPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .rightPadding)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -74,12 +90,16 @@ import UIKit
|
|||||||
try container.encode(moleculeName, forKey: .moleculeName)
|
try container.encode(moleculeName, forKey: .moleculeName)
|
||||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||||
try container.encodeModels(molecules, forKey: .molecules)
|
try container.encodeModels(molecules, forKey: .molecules)
|
||||||
try container.encode(spacing, forKey: .spacing)
|
try container.encodeIfPresent(spacing, forKey: .spacing)
|
||||||
try container.encode(border, forKey: .border)
|
try container.encodeIfPresent(border, forKey: .border)
|
||||||
try container.encode(loop, forKey: .loop)
|
try container.encodeIfPresent(loop, forKey: .loop)
|
||||||
try container.encode(height, forKey: .height)
|
try container.encodeIfPresent(height, forKey: .height)
|
||||||
try container.encode(itemWidthPercent, forKey: .itemWidthPercent)
|
try container.encode(itemWidthPercent, forKey: .itemWidthPercent)
|
||||||
try container.encode(itemAlignment, forKey: .itemAlignment)
|
try container.encodeIfPresent(itemAlignment, forKey: .itemAlignment)
|
||||||
try container.encodeModelIfPresent(pagingMolecule, forKey: .pagingMolecule)
|
try container.encodeModelIfPresent(pagingMolecule, forKey: .pagingMolecule)
|
||||||
|
try container.encode(paging, forKey: .paging)
|
||||||
|
try container.encodeIfPresent(useHorizontalMargins, forKey: .useHorizontalMargins)
|
||||||
|
try container.encodeIfPresent(leftPadding, forKey: .leftPadding)
|
||||||
|
try container.encodeIfPresent(rightPadding, forKey: .rightPadding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,5 +10,5 @@ import Foundation
|
|||||||
|
|
||||||
|
|
||||||
public protocol CarouselPagingModelProtocol {
|
public protocol CarouselPagingModelProtocol {
|
||||||
var position: Float? { get }
|
var position: CGFloat? { get }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -131,7 +131,7 @@ import Foundation
|
|||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
public override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
(collectionView.cellForItem(at: indexPath) as? CollectionTemplateItemProtocol)?.didSelectCell(at: indexPath, delegateObject: delegateObjectIVar, additionalData: nil)
|
(collectionView.cellForItem(at: indexPath) as? CollectionTemplateItemProtocol)?.didSelectCell(at: indexPath, delegateObject: delegateObjectIVar, additionalData: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import Foundation
|
|||||||
|
|
||||||
|
|
||||||
/// A base collection view cell with basic mvm functionality.
|
/// A base collection view cell with basic mvm functionality.
|
||||||
open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCoreViewProtocol, CollectionTemplateItemProtocol {
|
open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCoreViewProtocol, CollectionTemplateItemProtocol, MFButtonProtocol {
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -25,10 +25,6 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo
|
|||||||
|
|
||||||
private var initialSetupPerformed = false
|
private var initialSetupPerformed = false
|
||||||
|
|
||||||
//--------------------------------------------------
|
|
||||||
// MARK: - Properties
|
|
||||||
//--------------------------------------------------
|
|
||||||
|
|
||||||
// MARK: - Inits
|
// MARK: - Inits
|
||||||
public override init(frame: CGRect) {
|
public override init(frame: CGRect) {
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
@ -47,10 +43,6 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
|
||||||
// MARK: - Properties
|
|
||||||
//--------------------------------------------------
|
|
||||||
|
|
||||||
// MARK: - MVMCoreViewProtocol
|
// MARK: - MVMCoreViewProtocol
|
||||||
open func setupView() {
|
open func setupView() {
|
||||||
isAccessibilityElement = false
|
isAccessibilityElement = false
|
||||||
@ -68,16 +60,6 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo
|
|||||||
(molecule as? MVMCoreViewProtocol)?.updateView(size)
|
(molecule as? MVMCoreViewProtocol)?.updateView(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
open func reset() {
|
|
||||||
molecule?.reset()
|
|
||||||
backgroundColor = .mvmWhite
|
|
||||||
width = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------------------
|
|
||||||
// MARK: - Properties
|
|
||||||
//--------------------------------------------------
|
|
||||||
|
|
||||||
// MARK: - MoleculeViewProtocol
|
// MARK: - MoleculeViewProtocol
|
||||||
open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||||
guard let model = model as? CollectionItemModelProtocol else { return }
|
guard let model = model as? CollectionItemModelProtocol else { return }
|
||||||
@ -94,6 +76,12 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func reset() {
|
||||||
|
molecule?.reset()
|
||||||
|
backgroundColor = .mvmWhite
|
||||||
|
width = nil
|
||||||
|
}
|
||||||
|
|
||||||
/// Convenience function. Adds a molecule to the view.
|
/// Convenience function. Adds a molecule to the view.
|
||||||
open func addMolecule(_ molecule: MoleculeViewProtocol) {
|
open func addMolecule(_ molecule: MoleculeViewProtocol) {
|
||||||
contentView.addSubview(molecule)
|
contentView.addSubview(molecule)
|
||||||
@ -109,6 +97,12 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo
|
|||||||
self.width = width
|
self.width = width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Override
|
||||||
|
public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) {
|
||||||
|
guard let action = model?.action else { return }
|
||||||
|
Button.performButtonAction(with: action, button: self, delegateObject: delegateObject, additionalData: additionalData)
|
||||||
|
}
|
||||||
|
|
||||||
// Column logic, set width.
|
// Column logic, set width.
|
||||||
override open func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
|
override open func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
|
||||||
let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
|
let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
|
||||||
|
|||||||
@ -9,5 +9,13 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public protocol CollectionItemModelProtocol {
|
public protocol CollectionItemModelProtocol {
|
||||||
|
var action: ActionModelProtocol? { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a strict requirement.
|
||||||
|
public extension CollectionItemModelProtocol {
|
||||||
|
var action: ActionModelProtocol? {
|
||||||
|
get { return nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@objcMembers open class TableViewCell: UITableViewCell, MoleculeViewProtocol, MoleculeListCellProtocol, MVMCoreViewProtocol {
|
@objcMembers open class TableViewCell: UITableViewCell, MoleculeViewProtocol, MoleculeListCellProtocol, MVMCoreViewProtocol, MFButtonProtocol {
|
||||||
|
|
||||||
open var molecule: MoleculeViewProtocol?
|
open var molecule: MoleculeViewProtocol?
|
||||||
open var listItemModel: ListItemModelProtocol?
|
open var listItemModel: ListItemModelProtocol?
|
||||||
@ -267,10 +267,8 @@ import UIKit
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||||
//TODO: Use object when handleAction is rewrote to handle action model
|
guard let action = listItemModel?.action else { return }
|
||||||
if let actionMap = self.listItemModel?.action?.toJSON() {
|
Button.performButtonAction(with: action, button: self, delegateObject: delegateObject, additionalData: additionalData)
|
||||||
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func willDisplay() {
|
public func willDisplay() {
|
||||||
|
|||||||
@ -233,4 +233,8 @@ import Foundation
|
|||||||
}
|
}
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
(collectionView.cellForItem(at: indexPath) as? CollectionTemplateItemProtocol)?.didSelectCell(at: indexPath, delegateObject: delegateObjectIVar, additionalData: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user