latest carousel state
This commit is contained in:
parent
5558cc8324
commit
faa9c94f90
@ -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
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() { }
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user