latest carousel state

This commit is contained in:
Kevin G Christiano 2020-02-17 13:33:25 -05:00
parent 5558cc8324
commit faa9c94f90
10 changed files with 193 additions and 103 deletions

View File

@ -15,15 +15,6 @@ public protocol IndicatorViewProtocol {
var isEnabled: Bool { get set }
}
/// Contracts behavior between carousel and its page control.
public protocol CarouselPageControlProtocol {
typealias PagingTouchBlock = ((CarouselPageControlProtocol)) -> ()
var currentIndex: Int { get set }
var numberOfPages: Int { get set }
var indicatorTouchAction: PagingTouchBlock? { get set }
func scrollViewDidScroll(_ collectionView: UICollectionView)
}
open class CarouselIndicator: Control, CarouselPageControlProtocol {
//--------------------------------------------------
@ -83,7 +74,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
/// Set this closure to perform an action when a different indicator was selected.
/// Passes through oldInde and newIndex, respectively.
public var indicatorTouchAction: CarouselIndicator.PagingTouchBlock?
public var indicatorTouchAction: ((CarouselPageControlProtocol) -> ())?
/// Allows sendActions() to trigger even if index is already at min/max index.
public var alwaysSendAction = false
@ -107,7 +98,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
indicatorView?.isEnabled = isEnabled
}
}
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
@ -160,7 +151,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
_indicatorTintColor = newColor
if isBarIndicator(), let barIndicator = indicatorView as? BarsIndicatorView {
for (i, barTuple) in barIndicator.barsReference.enumerated() where i != currentIndex {
for (i, barTuple) in barIndicator.barReferences.enumerated() where i != currentIndex {
barTuple.view.backgroundColor = newColor
}
}
@ -177,7 +168,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
if isBarIndicator() {
if let barIndicator = indicatorView as? BarsIndicatorView {
barIndicator.barsReference[currentIndex].view.backgroundColor = newColor
barIndicator.barReferences[currentIndex].view.backgroundColor = newColor
}
}
}
@ -265,9 +256,11 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
let touchPoint = tapGesture?.location(in: self)
let touchPoint_X = touchPoint?.x ?? 0.0
if isEnabled, indicatorType == .bar, let bars = (indicatorView as? BarsIndicatorView)?.barsReference {
if isBarIndicator(), let bars = (indicatorView as? BarsIndicatorView)?.barReferences {
currentIndex = bars.firstIndex { $0.0.frame.maxX >= touchPoint_X && $0.0.frame.minX <= touchPoint_X } ?? 0
} else {
// Determine which half of the view was touched.
if touchPoint_X > bounds.width / 2 {
incrementCurrentIndex()
} else {
@ -289,7 +282,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
}
public func performAction() {
sendActions(for: .valueChanged)
indicatorTouchAction?(self)
}
@ -311,13 +304,11 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
/// Convenience to determine if current view is displaying bars.
func isBarIndicator() -> Bool {
return indicatorType != .bar && numberOfPages > hybridThreshold
}
public func scrollViewDidScroll(_ collectionView: UICollectionView) {
}
public func scrollViewDidScroll(_ collectionView: UICollectionView) { }
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------

View File

@ -15,7 +15,7 @@ public class CarouselIndicatorModel: MoleculeModelProtocol {
//--------------------------------------------------
public var backgroundColor: Color?
public static var identifier: String {
return "carouselIndicator"
}
@ -33,8 +33,9 @@ public class CarouselIndicatorModel: MoleculeModelProtocol {
public var accessibilityHasSlidesInsteadOfPage: Bool? = false
public var isEnabled: Bool? = false
public var disabledIndicatorColor: Color? = Color(uiColor: .mvmCoolGray3)
public var indicatorTintColor: Color? = Color(uiColor: .black)
public var currentIndicatorColor: Color? = Color(uiColor: .black)
public var indicatorTintColor: Color? = Color(uiColor: .mvmBlack)
public var currentIndicatorColor: Color? = Color(uiColor: .mvmBlack)
// public var position: Float?
//--------------------------------------------------
// MARK: - Keys
@ -44,8 +45,19 @@ public class CarouselIndicatorModel: MoleculeModelProtocol {
case moleculeName
case backgroundColor
case type
case hybridThreshold
case barsColor
case currentBarColor
case currentIndex
case numberOfPages
case alwaysSendEvent
case isAnimated
case hidesForSinglePage
case accessibilityHasSlidesInsteadOfPage
case isEnabled
case disabledIndicatorColor
case indicatorTintColor
case currentIndicatorColor
}
//--------------------------------------------------
@ -54,18 +66,45 @@ public class CarouselIndicatorModel: MoleculeModelProtocol {
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
moleculeName = try typeContainer.decodeIfPresent(String.self, forKey: .moleculeName)
currentBarColor = try typeContainer.decodeIfPresent(Color.self, forKey: .currentBarColor)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
barsColor = try typeContainer.decodeIfPresent(Color.self, forKey: .barsColor)
type = try typeContainer.decodeIfPresent(String.self, forKey: .type) ?? "hybrid"
hybridThreshold = try typeContainer.decodeIfPresent(Int.self, forKey: .hybridThreshold) ?? 5
barsColor = try typeContainer.decodeIfPresent(Color.self, forKey: .barsColor)
currentBarColor = try typeContainer.decodeIfPresent(Color.self, forKey: .currentBarColor)
currentIndex = try typeContainer.decodeIfPresent(Int.self, forKey: .currentIndex) ?? 0
numberOfPages = try typeContainer.decodeIfPresent(Int.self, forKey: .numberOfPages) ?? 0
alwaysSendEvent = try typeContainer.decodeIfPresent(Bool.self, forKey: .alwaysSendEvent) ?? false
isAnimated = try typeContainer.decodeIfPresent(Bool.self, forKey: .isAnimated) ?? true
hidesForSinglePage = try typeContainer.decodeIfPresent(Bool.self, forKey: .hidesForSinglePage) ?? false
accessibilityHasSlidesInsteadOfPage = try typeContainer.decodeIfPresent(Bool.self, forKey: .accessibilityHasSlidesInsteadOfPage) ?? false
isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .isEnabled) ?? false
disabledIndicatorColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledIndicatorColor) ?? Color(uiColor: .mvmCoolGray3)
indicatorTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .indicatorTintColor) ?? Color(uiColor: .mvmBlack)
currentIndicatorColor = try typeContainer.decodeIfPresent(Color.self, forKey: .currentIndicatorColor) ?? Color(uiColor: .mvmBlack)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(barsColor, forKey: .barsColor)
try container.encodeIfPresent(currentBarColor, forKey: .currentBarColor)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(type, forKey: .type)
try container.encodeIfPresent(hybridThreshold, forKey: .hybridThreshold)
try container.encodeIfPresent(barsColor, forKey: .barsColor)
try container.encodeIfPresent(currentBarColor, forKey: .currentBarColor)
try container.encodeIfPresent(currentIndex, forKey: .currentIndex)
try container.encodeIfPresent(numberOfPages, forKey: .numberOfPages)
try container.encodeIfPresent(alwaysSendEvent, forKey: .alwaysSendEvent)
try container.encodeIfPresent(isAnimated, forKey: .isAnimated)
try container.encodeIfPresent(hidesForSinglePage, forKey: .hidesForSinglePage)
try container.encodeIfPresent(accessibilityHasSlidesInsteadOfPage, forKey: .accessibilityHasSlidesInsteadOfPage)
try container.encodeIfPresent(isEnabled, forKey: .isEnabled)
try container.encodeIfPresent(disabledIndicatorColor, forKey: .disabledIndicatorColor)
try container.encodeIfPresent(indicatorTintColor, forKey: .indicatorTintColor)
try container.encodeIfPresent(currentIndicatorColor, forKey: .currentIndicatorColor)
}
}

View File

@ -11,7 +11,7 @@ import UIKit
open class BarsIndicatorView: View, IndicatorViewProtocol {
//--------------------------------------------------
// MARK: - Properties
// MARK: - Stored Properties
//--------------------------------------------------
let stackView: StackView = {
@ -19,40 +19,49 @@ open class BarsIndicatorView: View, IndicatorViewProtocol {
stackView.axis = .horizontal
stackView.distribution = .equalSpacing
stackView.spacing = 6
stackView.heightAnchor.constraint(greaterThanOrEqualToConstant: BarsIndicatorView.indicatorBarHeight.selected).isActive = true
return stackView
}()
public var barsReference: [(view: View, constraint: NSLayoutConstraint)] = []
public var barReferences: [(view: View, constraint: NSLayoutConstraint)] = []
// Dimensions are based on InVision Design Guidelines.
public static let indicatorBarWidth: CGFloat = 24
public static let indicatorBarHeight: (selected: CGFloat, unselected: CGFloat) = (selected: 4, unselected: 1)
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
public var parentCarouselIndicator: CarouselIndicator? {
return superview as? CarouselIndicator
}
public var enabledColor: UIColor {
return (superview as? CarouselIndicator)?.indicatorTintColor ?? .black
return parentCarouselIndicator?.indicatorTintColor ?? .mvmBlack
}
public var currentIndexColor: UIColor {
return (superview as? CarouselIndicator)?.currentIndicatorColor ?? .black
return parentCarouselIndicator?.currentIndicatorColor ?? .mvmBlack
}
public var disabledColor: UIColor {
return (superview as? CarouselIndicator)?.disabledIndicatorColor ?? .mvmCoolGray3
return parentCarouselIndicator?.disabledIndicatorColor ?? .mvmCoolGray3
}
/// Returns the numberOfPages count from its parent CarouselIndicator.
public var numberOfPages: Int? {
return (superview as? CarouselIndicator)?.numberOfPages
return parentCarouselIndicator?.numberOfPages
}
/// Returns the numberOfPages count from its parent CarouselIndicator.
public var currentIndex: Int? {
return (superview as? CarouselIndicator)?.currentIndex
return parentCarouselIndicator?.currentIndex
}
open var isEnabled: Bool = true {
didSet {
barsReference.forEach { view, heightConstraint in
barReferences.forEach { view, heightConstraint in
view.backgroundColor = isEnabled ? enabledColor : disabledColor
}
}
@ -82,7 +91,7 @@ open class BarsIndicatorView: View, IndicatorViewProtocol {
stackView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
stackView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor).isActive = true
trailingAnchor.constraint(lessThanOrEqualTo: stackView.trailingAnchor).isActive = true
trailingAnchor.constraint(greaterThanOrEqualTo: stackView.trailingAnchor).isActive = true
generateBars()
}
@ -102,7 +111,7 @@ open class BarsIndicatorView: View, IndicatorViewProtocol {
for i in 0..<numberOfPages {
let bar = View()
bar.widthAnchor.constraint(equalToConstant: BarsIndicatorView.indicatorBarWidth).isActive = true
bar.backgroundColor = enabledColor
bar.backgroundColor = isEnabled ? enabledColor : disabledColor
let barHeight = i == currentIndex ? BarsIndicatorView.indicatorBarHeight.selected : BarsIndicatorView.indicatorBarHeight.unselected
let heightConstraint = bar.heightAnchor.constraint(equalToConstant: barHeight)
heightConstraint.isActive = true
@ -111,7 +120,7 @@ open class BarsIndicatorView: View, IndicatorViewProtocol {
bars.append((bar, heightConstraint))
}
barsReference = bars
barReferences = bars
}
//--------------------------------------------------
@ -120,17 +129,17 @@ open class BarsIndicatorView: View, IndicatorViewProtocol {
public override func reset() {
super.reset()
barsReference.forEach { $0.view.removeFromSuperview() }
barsReference = []
barReferences.forEach { $0.view.removeFromSuperview() }
barReferences = []
}
public func updateUI(previousIndex: Int, newIndex: Int, totalCount: Int, isAnimated: Bool) {
let expression = {
self.barsReference[previousIndex].view.backgroundColor = self.enabledColor
self.barsReference[newIndex].view.backgroundColor = self.currentIndexColor
self.barsReference[previousIndex].constraint.constant = BarsIndicatorView.indicatorBarHeight.unselected
self.barsReference[newIndex].constraint.constant = BarsIndicatorView.indicatorBarHeight.selected
self.barReferences[previousIndex].view.backgroundColor = self.enabledColor
self.barReferences[newIndex].view.backgroundColor = self.currentIndexColor
self.barReferences[previousIndex].constraint.constant = BarsIndicatorView.indicatorBarHeight.unselected
self.barReferences[newIndex].constraint.constant = BarsIndicatorView.indicatorBarHeight.selected
self.layoutIfNeeded()
}

View File

@ -15,7 +15,7 @@ open class NumericIndicatorView: View, IndicatorViewProtocol {
//--------------------------------------------------
/// Text to display the current count of total pages for viewing.
open var titleLabel: Label = {
open var pageCountLabel: Label = {
let label = Label.commonLabelB2(true)
label.setContentCompressionResistancePriority(.required, for: .vertical)
label.textAlignment = .center
@ -25,41 +25,43 @@ open class NumericIndicatorView: View, IndicatorViewProtocol {
let leftArrow: ImageView = {
let arrow = UIImage(named: "peakingRightArrow")?.withRenderingMode(.alwaysTemplate).withHorizontallyFlippedOrientation()
let imageView = ImageView(image: arrow)
imageView.isUserInteractionEnabled = true
imageView.tintColor = .mvmBlack
imageView.heightAnchor.constraint(equalToConstant: PaddingTwo).isActive = true
imageView.widthAnchor.constraint(equalToConstant: PaddingTwo).isActive = true
return imageView
}()
let rightArrow: ImageView = {
let arrow = UIImage(named: "peakingRightArrow")?.withRenderingMode(.alwaysTemplate)
let imageView = ImageView(image: arrow)
imageView.isUserInteractionEnabled = true
imageView.tintColor = .mvmBlack
imageView.heightAnchor.constraint(equalToConstant: PaddingTwo).isActive = true
imageView.widthAnchor.constraint(equalToConstant: PaddingTwo).isActive = true
return imageView
}()
//--------------------------------------------------
// MARK: - Properties
// MARK: - Computed Properties
//--------------------------------------------------
open var isEnabled: Bool = true {
didSet {
titleLabel.isEnabled = isEnabled
pageCountLabel.textColor = isEnabled ? enabledColor : disabledColor
leftArrow.tintColor = isEnabled ? enabledColor : disabledColor
rightArrow.tintColor = isEnabled ? enabledColor : disabledColor
}
}
public var parentCarouselIndicator: CarouselIndicator? {
return superview as? CarouselIndicator
}
public var enabledColor: UIColor {
return (superview as? CarouselIndicator)?.indicatorTintColor ?? .black
return parentCarouselIndicator?.indicatorTintColor ?? .mvmBlack
}
public var disabledColor: UIColor {
return (superview as? CarouselIndicator)?.disabledIndicatorColor ?? .mvmCoolGray3
}
public var parentCarouselIndicator: CarouselIndicator? {
return superview as? CarouselIndicator
return parentCarouselIndicator?.disabledIndicatorColor ?? .mvmCoolGray3
}
/// Returns the currentIndex from its parent CarouselIndicator.
@ -98,7 +100,7 @@ open class NumericIndicatorView: View, IndicatorViewProtocol {
open override func updateView(_ size: CGFloat) {
super.updateView(size)
titleLabel.updateView(size)
pageCountLabel.updateView(size)
}
//--------------------------------------------------
@ -109,29 +111,24 @@ open class NumericIndicatorView: View, IndicatorViewProtocol {
super.setupView()
isUserInteractionEnabled = false
leftArrow.tintColor = isEnabled ? enabledColor : disabledColor
rightArrow.tintColor = isEnabled ? enabledColor : disabledColor
addSubview(titleLabel)
titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
NSLayoutConstraint.constraintPinSubview(titleLabel, pinTop: true, pinBottom: true, pinLeft: false, pinRight: false)
addSubview(pageCountLabel)
pageCountLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
pageCountLabel.topAnchor.constraint(equalTo: topAnchor).isActive = true
bottomAnchor.constraint(equalTo: pageCountLabel.bottomAnchor).isActive = true
addSubview(leftArrow)
NSLayoutConstraint.constraintPinView(leftArrow, heightConstraint: true, heightConstant: PaddingTwo, widthConstraint: true, widthConstant: PaddingTwo)
leftArrow.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
addSubview(rightArrow)
NSLayoutConstraint.constraintPinView(rightArrow, heightConstraint: true, heightConstant: PaddingTwo, widthConstraint: true, widthConstant: PaddingTwo)
rightArrow.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
rightArrow.isUserInteractionEnabled = true
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[leftArrowView]-(padding)-[titleLabel]-(padding)-[rightArrowView]-0-|",
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|[leftArrow]-(padding)-[pageCountLabel]-(padding)-[rightArrow]|",
options: .directionLeadingToTrailing,
metrics: ["padding": PaddingOne],
views: ["leftArrowView": leftArrow,
"titleLabel": titleLabel,
"rightArrowView": rightArrow]))
views: ["leftArrow": leftArrow,
"pageCountLabel": pageCountLabel,
"rightArrow": rightArrow]))
}
//--------------------------------------------------
@ -143,7 +140,7 @@ open class NumericIndicatorView: View, IndicatorViewProtocol {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.titleLabel.text = "\(newIndex)/\(totalCount)"
self.pageCountLabel.text = "\(newIndex)/\(totalCount)"
self.layoutIfNeeded()
}
}

View File

@ -8,16 +8,16 @@
import UIKit
class ImageView: UIImageView, ModelMoleculeViewProtocol {
open class ImageView: UIImageView, ModelMoleculeViewProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open var json: [AnyHashable: Any]?
open var model: MoleculeModelProtocol?
private var initialSetupPerformed = false
//--------------------------------------------------
// MARK: - Initialization
//--------------------------------------------------
@ -31,7 +31,7 @@ class ImageView: UIImageView, ModelMoleculeViewProtocol {
super.init(image: image)
initialSetup()
}
public convenience init() {
self.init(frame: .zero)
}
@ -41,6 +41,10 @@ class ImageView: UIImageView, ModelMoleculeViewProtocol {
initialSetup()
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
public func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
@ -48,12 +52,15 @@ class ImageView: UIImageView, ModelMoleculeViewProtocol {
}
}
// MARK:- ModelMoleculeViewProtocol
//--------------------------------------------------
// MARK: - ModelMoleculeViewProtocol
//--------------------------------------------------
open func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
self.model = model
if let backgroundColor = model?.backgroundColor {
self.model = model
if let backgroundColor = model?.backgroundColor {
self.backgroundColor = backgroundColor.uiColor
}
}
}
open class func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? {
@ -72,8 +79,8 @@ class ImageView: UIImageView, ModelMoleculeViewProtocol {
// MARK:- MVMCoreViewProtocol
extension ImageView: MVMCoreViewProtocol {
open func updateView(_ size: CGFloat) {}
open func updateView(_ size: CGFloat) { }
/// Will be called only once.
open func setupView() {
translatesAutoresizingMaskIntoConstraints = false
@ -99,4 +106,3 @@ extension ImageView: MVMCoreUIMoleculeViewProtocol {
open func setAsMolecule() { }
}

View File

@ -9,17 +9,29 @@
import Foundation
public class MoleculeContainerModel: ContainerModel {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var molecule: MoleculeModelProtocol
private enum CodingKeys: String, CodingKey {
case molecule
}
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(with moleculeModel: MoleculeModelProtocol) {
molecule = moleculeModel
super.init()
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
molecule = try typeContainer.decodeMolecule(codingKey: .molecule)

View File

@ -8,6 +8,15 @@
import UIKit
/// Contracts behavior between carousel and its page control.
public protocol CarouselPageControlProtocol {
var currentIndex: Int { get set }
var numberOfPages: Int { get set }
var indicatorTouchAction: ((CarouselPageControlProtocol) -> ())? { get set }
func scrollViewDidScroll(_ collectionView: UICollectionView)
}
open class Carousel: View {
//--------------------------------------------------
// MARK: - Properties
@ -33,7 +42,7 @@ open class Carousel: View {
var molecules: [MoleculeModelProtocol]?
/// The horizontal alignment of the cell in the collection view. Only noticeable if the itemWidthPercent is less than 100%.
var itemAlignment = UICollectionView.ScrollPosition.left
var itemAlignment: UICollectionView.ScrollPosition = .left
/// From 0-1. The item width as a percent of the carousel width.
var itemWidthPercent: Float = 1
@ -42,10 +51,11 @@ open class Carousel: View {
var collectionViewHeight: NSLayoutConstraint?
/// The view that we use for paging
var pagingView: (UIView & MVMCoreUIPagingProtocol)?
var pagingView: (UIView & CarouselPageControlProtocol)?
/// If the carousel should loop after scrolling past the first and final cells.
var loop = false
private var dragging = false
/// For adding pager
@ -141,9 +151,17 @@ open class Carousel: View {
if carouselModel?.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)
if let last = newMolecules.last {
molecules?.insert(last, at: 0)
}
molecules?.insert(newMolecules[(newMolecules.count - 2)], at: 0)
molecules?.append(newMolecules.first!)
if let first = newMolecules.first {
molecules?.append(first)
}
molecules?.append(newMolecules[1])
}
@ -153,9 +171,10 @@ open class Carousel: View {
/// Sets up the paging molecule
open func setupPagingMolecule(_ molecule: CarouselPagingModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) {
var pagingView: (UIView & MVMCoreUIPagingProtocol)? = nil
var pagingView: (UIView & CarouselPageControlProtocol)? = nil
if let molecule = molecule {
pagingView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(molecule, delegateObject, false) as? (UIView & MVMCoreUIPagingProtocol)
pagingView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(molecule, delegateObject, false) as? (UIView & CarouselPageControlProtocol)
}
addPaging(view: pagingView, position: (CGFloat(molecule?.position ?? 20)))
@ -163,6 +182,7 @@ open class Carousel: View {
/// Registers the cells with the collection view
func registerCells(with carouselModel: CarouselModel, delegateObject: MVMCoreUIDelegateObject?) {
for molecule in carouselModel.molecules {
if let info = getMoleculeInfo(with: molecule, delegateObject: delegateObject) {
collectionView.register(info.class, forCellWithReuseIdentifier: info.identifier)
@ -176,6 +196,7 @@ open class Carousel: View {
/// Returns the (identifier, class) of the molecule for the given map.
func getMoleculeInfo(with molecule: MoleculeModelProtocol, delegateObject: MVMCoreUIDelegateObject?) -> (identifier: String, class: AnyClass, molecule: MoleculeModelProtocol)? {
guard let className = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(molecule),
let moleculeName = (className as? ModelMoleculeViewProtocol.Type)?.nameForReuse(molecule, delegateObject) ?? molecule.moleculeName
else { return nil }
@ -189,26 +210,29 @@ open class Carousel: View {
switch string {
case "leading":
itemAlignment = .left
case "trailing":
itemAlignment = .right
case "center":
itemAlignment = .centeredHorizontally
default: break
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) {
open func addPaging(view: (UIView & CarouselPageControlProtocol)?, position: CGFloat) {
pagingView?.removeFromSuperview()
guard let pagingView = view else {
guard var 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
@ -218,16 +242,17 @@ open class Carousel: View {
bottomPin?.priority = .defaultLow
bottomPin?.isActive = true
pagingView.setNumberOfPages(numberOfPages)
pagingView.numberOfPages = numberOfPages
(pagingView as? MVMCoreUIViewConstrainingProtocol)?.alignHorizontal?(.fill)
pagingView.setPagingTouch { [weak self] pager in
pagingView.indicatorTouchAction = { [weak self] pager in
DispatchQueue.main.async {
guard let self = self else { return }
let currentPage = pager.currentPage()
let currentPage = pager.currentIndex
self.pageIndex = currentPage
self.goTo(self.currentIndex, animated: !UIAccessibility.isVoiceOverRunning)
}
}
self.pagingView = pagingView
}
@ -236,6 +261,7 @@ open class Carousel: View {
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 {
(collectionView.cellForItem(at: firstItem) as? MoleculeCollectionViewCell)?.setPeaking(true, animated: true)
}
@ -257,10 +283,12 @@ open class Carousel: View {
cell.accessibilityElementsHidden = false
var array = cell.accessibilityElements
if let acc = pagingView?.accessibilityElements {
array?.append(contentsOf: acc)
} else {
array?.append(pagingView!)
if let pagingView = pagingView {
if let acc = pagingView.accessibilityElements {
array?.append(contentsOf: acc)
} else {
array?.append(pagingView)
}
}
accessibilityElements = array
@ -328,7 +356,7 @@ extension Carousel: UIScrollViewDelegate {
let goToIndex = { (index: Int) in
self.goTo(index, animated: false)
self.collectionView.layoutIfNeeded()
self.pagingView?.setPage(self.pageIndex)
self.pagingView?.currentIndex = self.pageIndex
}
if currentIndex < 2 {
@ -367,7 +395,7 @@ extension Carousel: UIScrollViewDelegate {
checkForDraggingOutOfBounds(scrollView)
// Let the pager know our progress if needed.
pagingView?.scrollViewDidScroll?(collectionView)
pagingView?.scrollViewDidScroll(collectionView)
}
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
@ -386,12 +414,13 @@ extension Carousel: UIScrollViewDelegate {
// We switch cards if we pass the velocity threshold or position threshold (currently 50%).
let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent)
var cellToSwipeTo = Int(scrollView.contentOffset.x/(itemWidth + separatorWidth) + 0.5)
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
}
@ -404,7 +433,7 @@ extension Carousel: UIScrollViewDelegate {
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
// Cycle to other end if on buffer cell.
handleUserOnBufferCell()
pagingView?.setPage(pageIndex)
pagingView?.currentIndex = pageIndex
showPeaking(true)
}
}

View File

@ -13,7 +13,10 @@ import UIKit
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "carousel"
public static var identifier: String {
return "carousel"
}
public var backgroundColor: Color?
public var molecules: [CarouselItemModel]
public var moleculeName: String?
@ -29,7 +32,7 @@ import UIKit
// MARK: - Initializer
//--------------------------------------------------
public init(molecules: [CarouselItemModel]){
public init(molecules: [CarouselItemModel]) {
self.molecules = molecules
}
@ -56,6 +59,7 @@ import UIKit
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
moleculeName = try typeContainer.decodeIfPresent(String.self, forKey: .moleculeName)
molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
spacing = try typeContainer.decodeIfPresent(Float.self, forKey: .spacing)

View File

@ -19,6 +19,7 @@ public extension MVMCoreUIMoleculeMappingObject {
if let moleculeName = model.moleculeName {
return moleculeMapping.object(forKey: moleculeName) as? AnyClass
}
return nil
}
@ -32,7 +33,7 @@ public extension MVMCoreUIMoleculeMappingObject {
return nil
}
let setData = {() in
let setData = {
if let molecule = molecule as? ModelMoleculeViewProtocol {
molecule.setWithModel(model, delegateObject, nil)
} else {
@ -45,6 +46,7 @@ public extension MVMCoreUIMoleculeMappingObject {
let view = ViewConstrainingView(molecule: molecule, alignment: castMolecule.horizontalAlignment?() ?? .fill)
setData()
return view
} else {
setData()
return molecule

View File

@ -8,7 +8,9 @@
import Foundation
@objcMembers public class MoleculeObjectMapping: NSObject {
public static func registerObjects() {
// Stacks
MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: MoleculeStackView.self, viewModelClass: MoleculeStackModel.self)
@ -95,7 +97,6 @@ import Foundation
// Other Organisms
MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: Carousel.self, viewModelClass: CarouselModel.self)
MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: CarouselIndicator.self, viewModelClass: CarouselIndicatorModel.self)
// TODO: Need model