Checking bars indicator
This commit is contained in:
parent
345fb971cc
commit
d90e68e263
@ -14,6 +14,37 @@ open class BarsIndicatorView: CarouselIndicator {
|
||||
// MARK: - Stored Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
/// Represents the data of each individual bar.
|
||||
private class IndicatorBar: View {
|
||||
// Dimensions are based on InVision Design Guidelines.
|
||||
static let width: CGFloat = 24
|
||||
static let selectedHeight: CGFloat = 4
|
||||
static let unselectedHeight: CGFloat = 1
|
||||
|
||||
var constraint: NSLayoutConstraint?
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public required init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
|
||||
fatalError("init(model:_:_:) has not been implemented")
|
||||
}
|
||||
|
||||
override func setupView() {
|
||||
super.setupView()
|
||||
isAccessibilityElement = true
|
||||
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccTabHint")
|
||||
widthAnchor.constraint(equalToConstant: BarsIndicatorView.IndicatorBar.width).isActive = true
|
||||
accessibilityTraits = .button
|
||||
}
|
||||
}
|
||||
|
||||
/// Structured container to hold and layout the indicator bars.
|
||||
public let stackView: UIStackView = {
|
||||
let stackView = UIStackView()
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
@ -25,11 +56,8 @@ open class BarsIndicatorView: CarouselIndicator {
|
||||
return stackView
|
||||
}()
|
||||
|
||||
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)
|
||||
/// Reference to each bar displayed.
|
||||
private var barReferences: [IndicatorBar] = []
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Computed Properties
|
||||
@ -37,16 +65,16 @@ open class BarsIndicatorView: CarouselIndicator {
|
||||
|
||||
/// Convenience to access the model.
|
||||
public var barsCarouselIndicatorModel: BarsCarouselIndicatorModel? {
|
||||
return model as? BarsCarouselIndicatorModel
|
||||
model as? BarsCarouselIndicatorModel
|
||||
}
|
||||
|
||||
open override var isEnabled: Bool {
|
||||
didSet {
|
||||
for (i, bar) in barReferences.enumerated() {
|
||||
for (i, indicatorBar) in barReferences.enumerated() {
|
||||
if i == currentIndex {
|
||||
bar.view.backgroundColor = isEnabled ? currentIndicatorColor : disabledIndicatorColor
|
||||
indicatorBar.backgroundColor = isEnabled ? currentIndicatorColor : disabledIndicatorColor
|
||||
} else {
|
||||
bar.view.backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor
|
||||
indicatorBar.backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,21 +82,22 @@ open class BarsIndicatorView: CarouselIndicator {
|
||||
|
||||
/// Colors the currently selected index, unique from other indicators
|
||||
public var currentIndicatorColor: UIColor {
|
||||
get { return barsCarouselIndicatorModel?.currentIndicatorColor.uiColor ?? indicatorColor }
|
||||
set (newColor) {
|
||||
barsCarouselIndicatorModel?.currentIndicatorColor = Color(uiColor: newColor)
|
||||
get { barsCarouselIndicatorModel?.currentIndicatorColor.uiColor ?? indicatorColor }
|
||||
set {
|
||||
barsCarouselIndicatorModel?.currentIndicatorColor = Color(uiColor: newValue)
|
||||
|
||||
if isEnabled && !barReferences.isEmpty {
|
||||
barReferences[currentIndex].view.backgroundColor = newColor
|
||||
barReferences[currentIndex].backgroundColor = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The color of the indicator bars.
|
||||
public override var indicatorColor: UIColor {
|
||||
get { return super.indicatorColor }
|
||||
set (newColor) {
|
||||
super.indicatorColor = newColor
|
||||
refreshBarColors(with: newColor)
|
||||
get { super.indicatorColor }
|
||||
set {
|
||||
super.indicatorColor = newValue
|
||||
refreshBarColors(with: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +113,7 @@ open class BarsIndicatorView: CarouselIndicator {
|
||||
isAccessibilityElement = false
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
stackView.heightAnchor.constraint(equalToConstant: 4),
|
||||
stackView.heightAnchor.constraint(equalToConstant: Padding.One),
|
||||
heightAnchor.constraint(equalTo: stackView.heightAnchor),
|
||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
stackView.topAnchor.constraint(equalTo: topAnchor),
|
||||
@ -97,44 +126,95 @@ open class BarsIndicatorView: CarouselIndicator {
|
||||
// MARK: - Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
/// Updates the color of all indicator bars.
|
||||
private func refreshBarColors(with color: UIColor) {
|
||||
|
||||
if isEnabled {
|
||||
for (i, barTuple) in barReferences.enumerated() {
|
||||
barTuple.view.backgroundColor = i == currentIndex ? currentIndicatorColor : color
|
||||
}
|
||||
guard isEnabled else { return }
|
||||
|
||||
for (i, indicatorBar) in barReferences.enumerated() {
|
||||
indicatorBar.backgroundColor = i == currentIndex ? currentIndicatorColor : color
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates all the indicato bars for display.
|
||||
func generateBars() {
|
||||
|
||||
var bars = [(View, NSLayoutConstraint)]()
|
||||
var bars = [IndicatorBar]()
|
||||
|
||||
for i in 0..<numberOfPages {
|
||||
let bar = View()
|
||||
bar.isAccessibilityElement = true
|
||||
if let accessibleValueFormat = accessibilityValueFormat, let accessibleIndex = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: i + 1)) {
|
||||
bar.accessibilityLabel = String(format: accessibleValueFormat, accessibleIndex, numberOfPages)
|
||||
}
|
||||
bar.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccTabHint")
|
||||
bar.accessibilityTraits = .button
|
||||
bar.widthAnchor.constraint(equalToConstant: BarsIndicatorView.indicatorBarWidth).isActive = true
|
||||
bar.backgroundColor = isEnabled ? (i == currentIndex ? currentIndicatorColor : indicatorColor) : disabledIndicatorColor
|
||||
let barHeight = i == currentIndex ? BarsIndicatorView.indicatorBarHeight.selected : BarsIndicatorView.indicatorBarHeight.unselected
|
||||
let heightConstraint = bar.heightAnchor.constraint(equalToConstant: barHeight)
|
||||
heightConstraint.isActive = true
|
||||
|
||||
stackView.addArrangedSubview(bar)
|
||||
bars.append((bar, heightConstraint))
|
||||
let indicatorBar = createIndicatorBar(index: i)
|
||||
stackView.addArrangedSubview(indicatorBar)
|
||||
bars.append(indicatorBar)
|
||||
}
|
||||
|
||||
accessibilityElements = stackView.arrangedSubviews
|
||||
barReferences = bars
|
||||
}
|
||||
|
||||
/// Removes an indicator bar from the view.
|
||||
private func removeBar() {
|
||||
let lastView = barReferences.removeLast()
|
||||
stackView.removeArrangedSubview(lastView)
|
||||
lastView.removeFromSuperview()
|
||||
accessibilityElements = stackView.arrangedSubviews
|
||||
}
|
||||
|
||||
/// Based of the sign of the difference indicator bars will be removed or added.
|
||||
private func balanceBarCount(_ difference: Int) {
|
||||
|
||||
if difference > 0 {
|
||||
// If positive, add n bars.
|
||||
var copyBars = barReferences
|
||||
|
||||
for _ in 0..<difference {
|
||||
let indicatorBar = createIndicatorBar(index: barReferences.count)
|
||||
indicatorBar.constraint?.constant = IndicatorBar.unselectedHeight
|
||||
stackView.addArrangedSubview(indicatorBar)
|
||||
copyBars.append(indicatorBar)
|
||||
}
|
||||
|
||||
accessibilityElements = stackView.arrangedSubviews
|
||||
barReferences = copyBars
|
||||
|
||||
} else if difference < 0 {
|
||||
// If negative, remove n bars.
|
||||
for _ in 0..<(difference * -1) {
|
||||
removeBar()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func createIndicatorBar(index: Int) -> IndicatorBar {
|
||||
|
||||
let bar = IndicatorBar()
|
||||
setAccessibilityLabel(view: bar, index: index)
|
||||
bar.backgroundColor = isEnabled ? (index == currentIndex ? currentIndicatorColor : indicatorColor) : disabledIndicatorColor
|
||||
let barHeight = index == currentIndex ? BarsIndicatorView.IndicatorBar.selectedHeight : BarsIndicatorView.IndicatorBar.unselectedHeight
|
||||
let heightConstraint = bar.heightAnchor.constraint(equalToConstant: barHeight)
|
||||
heightConstraint.isActive = true
|
||||
bar.constraint = heightConstraint
|
||||
|
||||
return bar
|
||||
}
|
||||
|
||||
/// Refreshes the accessibility labels to read "x of n".
|
||||
private func refreshAccessibilityLabels() {
|
||||
for i in 0..<barReferences.count {
|
||||
setAccessibilityLabel(view: barReferences[i], index: i)
|
||||
}
|
||||
}
|
||||
|
||||
private func setAccessibilityLabel(view: UIView, index: Int) {
|
||||
guard let accessibleValueFormat = accessibilityValueFormat,
|
||||
let accessibleIndex = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: index + 1))
|
||||
else { return }
|
||||
|
||||
view.accessibilityLabel = String(format: accessibleValueFormat, accessibleIndex, numberOfPages)
|
||||
}
|
||||
|
||||
public override func assessTouchOf(_ touchPoint_X: CGFloat) {
|
||||
|
||||
currentIndex = barReferences.firstIndex { $0.0.frame.maxX >= touchPoint_X && $0.0.frame.minX <= touchPoint_X } ?? 0
|
||||
currentIndex = barReferences.firstIndex { $0.frame.maxX >= touchPoint_X && $0.frame.minX <= touchPoint_X } ?? 0
|
||||
performAction()
|
||||
}
|
||||
|
||||
@ -152,27 +232,40 @@ open class BarsIndicatorView: CarouselIndicator {
|
||||
|
||||
public override func reset() {
|
||||
super.reset()
|
||||
barReferences.forEach { $0.view.removeFromSuperview() }
|
||||
barReferences.forEach { $0.removeFromSuperview() }
|
||||
barReferences = []
|
||||
}
|
||||
|
||||
public override func updateUI(previousIndex: Int, newIndex: Int, totalCount: Int, isAnimated: Bool) {
|
||||
|
||||
guard newIndex < totalCount else { return }
|
||||
|
||||
// Ensure that at least one bar exists.
|
||||
guard !barReferences.isEmpty else {
|
||||
generateBars()
|
||||
return
|
||||
}
|
||||
|
||||
let expression = {
|
||||
self.barReferences[previousIndex].view.backgroundColor = self.isEnabled ? self.indicatorColor : self.disabledIndicatorColor
|
||||
self.barReferences[newIndex].view.backgroundColor = self.isEnabled ? self.currentIndicatorColor : self.disabledIndicatorColor
|
||||
self.barReferences[previousIndex].constraint.constant = BarsIndicatorView.indicatorBarHeight.unselected
|
||||
self.barReferences[newIndex].constraint.constant = BarsIndicatorView.indicatorBarHeight.selected
|
||||
self.layoutIfNeeded()
|
||||
guard newIndex < totalCount else { return }
|
||||
|
||||
// Ensure the number of pages matches the number of bar references.
|
||||
if (totalCount - barReferences.count) != 0 {
|
||||
barReferences.forEach {
|
||||
$0.backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor
|
||||
$0.constraint?.constant = IndicatorBar.unselectedHeight
|
||||
}
|
||||
|
||||
balanceBarCount(numberOfPages - barReferences.count)
|
||||
refreshAccessibilityLabels()
|
||||
}
|
||||
|
||||
isAnimated ? UIView.animate(withDuration: 0.3) { expression() } : expression()
|
||||
let expression = { [self] in
|
||||
barReferences[previousIndex].backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor
|
||||
barReferences[previousIndex].constraint?.constant = IndicatorBar.unselectedHeight
|
||||
barReferences[newIndex].backgroundColor = isEnabled ? currentIndicatorColor : disabledIndicatorColor
|
||||
barReferences[newIndex].constraint?.constant = IndicatorBar.selectedHeight
|
||||
layoutIfNeeded()
|
||||
}
|
||||
|
||||
// Perform the animation.
|
||||
isAnimated ? UIView.animate(withDuration: 0.25) { expression() } : expression()
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,6 @@
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
||||
//--------------------------------------------------
|
||||
@ -25,7 +23,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
||||
|
||||
/// Convenience to access the model.
|
||||
public var carouselIndicatorModel: CarouselIndicatorModel? {
|
||||
return model as? CarouselIndicatorModel
|
||||
model as? CarouselIndicatorModel
|
||||
}
|
||||
|
||||
/// Set this closure to perform an action when a different indicator was selected.
|
||||
@ -43,7 +41,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
||||
private(set) var previousIndex = 0
|
||||
|
||||
public var currentIndex: Int {
|
||||
get { return carouselIndicatorModel?.currentIndex ?? 0 }
|
||||
get { carouselIndicatorModel?.currentIndex ?? 0 }
|
||||
set (newIndex) {
|
||||
previousIndex = currentIndex
|
||||
carouselIndicatorModel?.currentIndex = newIndex
|
||||
@ -58,22 +56,22 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
||||
/// Holds the total number of pages displayed by the carousel.
|
||||
/// Updating this property will potentially update the UI.
|
||||
public var numberOfPages: Int {
|
||||
get { return carouselIndicatorModel?.numberOfPages ?? 0 }
|
||||
set (newTotal) {
|
||||
guard numberOfPages != newTotal else { return }
|
||||
get { carouselIndicatorModel?.numberOfPages ?? 0 }
|
||||
set {
|
||||
guard numberOfPages != newValue else { return }
|
||||
|
||||
carouselIndicatorModel?.numberOfPages = newTotal
|
||||
carouselIndicatorModel?.numberOfPages = newValue
|
||||
reset()
|
||||
isHidden = (carouselIndicatorModel?.hidesForSinglePage ?? false) && newTotal <= 1
|
||||
isHidden = (carouselIndicatorModel?.hidesForSinglePage ?? false) && newValue <= 1
|
||||
updateUI(previousIndex: previousIndex,
|
||||
newIndex: currentIndex,
|
||||
totalCount: newTotal,
|
||||
totalCount: newValue,
|
||||
isAnimated: carouselIndicatorModel?.animated ?? true)
|
||||
}
|
||||
}
|
||||
|
||||
public var disabledIndicatorColor: UIColor {
|
||||
get { return carouselIndicatorModel?.disabledIndicatorColor.uiColor ?? .mvmCoolGray3 }
|
||||
get { carouselIndicatorModel?.disabledIndicatorColor.uiColor ?? .mvmCoolGray3 }
|
||||
set { carouselIndicatorModel?.disabledIndicatorColor = Color(uiColor: newValue) }
|
||||
}
|
||||
|
||||
@ -92,7 +90,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
||||
}
|
||||
|
||||
var accessibilityValueFormat: String? {
|
||||
return MVMCoreUIUtility.hardcodedString(withKey: (carouselIndicatorModel?.accessibilityHasSlidesInsteadOfPage ?? false) ? "MVMCoreUIPageControlslides_currentpage_index" : "MVMCoreUIPageControl_currentpage_index")
|
||||
MVMCoreUIUtility.hardcodedString(withKey: (carouselIndicatorModel?.accessibilityHasSlidesInsteadOfPage ?? false) ? "MVMCoreUIPageControlslides_currentpage_index" : "MVMCoreUIPageControl_currentpage_index")
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -175,12 +173,14 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
||||
assessTouchOf(touchPoint_X)
|
||||
}
|
||||
|
||||
/// Determines where a touch in the indicator translate to an index selection.
|
||||
func assessTouchOf(_ touchPoint_X: CGFloat) { }
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
/// Called to update the Indicator UI with the latest values.
|
||||
open func updateUI(previousIndex: Int, newIndex: Int, totalCount: Int, isAnimated: Bool) { }
|
||||
|
||||
public func performAction() {
|
||||
@ -200,6 +200,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
||||
|
||||
guard let model = model as? CarouselIndicatorModel else { return }
|
||||
|
||||
previousIndex = 0
|
||||
indicatorColor = model.inverted ? model.indicatorColor_inverted.uiColor : model.indicatorColor.uiColor
|
||||
disabledIndicatorColor = model.disabledIndicatorColor.uiColor
|
||||
currentIndex = model.currentIndex
|
||||
@ -214,20 +215,18 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
||||
//--------------------------------------------------
|
||||
|
||||
open override func accessibilityIncrement() {
|
||||
|
||||
adjustAccessibility(toPage: currentIndex + 1)
|
||||
}
|
||||
|
||||
open override func accessibilityDecrement() {
|
||||
|
||||
adjustAccessibility(toPage: currentIndex - 1)
|
||||
}
|
||||
|
||||
func formatAccessibilityValue(index: Int, total: Int) {
|
||||
|
||||
guard let accessibleFormat = accessibilityValueFormat,
|
||||
let accessibleIndex = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: index))
|
||||
else { return }
|
||||
let accessibleIndex = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: index))
|
||||
else { return }
|
||||
|
||||
accessibilityValue = String(format: accessibleFormat, accessibleIndex, total)
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ open class NumericIndicatorView: CarouselIndicator {
|
||||
|
||||
/// Sets the color for pageCount text, left arrow and right arrow.
|
||||
public override var indicatorColor: UIColor {
|
||||
get { return super.indicatorColor }
|
||||
get { super.indicatorColor }
|
||||
set (newColor) {
|
||||
super.indicatorColor = newColor
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ public protocol CarouselPageControlProtocol {
|
||||
}
|
||||
|
||||
open class Carousel: View {
|
||||
|
||||
|
||||
public let collectionView: CollectionView = {
|
||||
let layout = CarouselCollectionLayout()
|
||||
layout.scrollDirection = .horizontal
|
||||
@ -25,7 +25,7 @@ open class Carousel: View {
|
||||
layout.minimumLineSpacing = 0
|
||||
return CollectionView(frame: .zero, collectionViewLayout: layout)
|
||||
}()
|
||||
|
||||
|
||||
/// The current index of the collection view. Includes dummy cells when looping.
|
||||
public var currentIndex = 0
|
||||
|
||||
@ -53,8 +53,8 @@ open class Carousel: View {
|
||||
public var collectionViewHeight: NSLayoutConstraint?
|
||||
|
||||
/// The view that we use for paging
|
||||
public var pagingView: (UIView & CarouselPageControlProtocol)?
|
||||
|
||||
public var pagingView: (MoleculeViewProtocol & CarouselPageControlProtocol)?
|
||||
|
||||
/// If the carousel should loop after scrolling past the first and final cells.
|
||||
public var loop = false
|
||||
|
||||
@ -85,10 +85,10 @@ open class Carousel: View {
|
||||
open func layoutCollection() {
|
||||
collectionView.collectionViewLayout.invalidateLayout()
|
||||
showPeaking(false)
|
||||
|
||||
|
||||
// Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled.
|
||||
guard let model = model as? CarouselModel,
|
||||
(model.paging == true || loop == true) else { return }
|
||||
(model.paging == true || loop == true) else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false)
|
||||
self.collectionView.layoutIfNeeded()
|
||||
@ -152,12 +152,12 @@ open class Carousel: View {
|
||||
collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0
|
||||
(collectionView.collectionViewLayout as? CarouselCollectionLayout)?.useLines = carouselModel.border ?? false
|
||||
(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing = carouselModel.spacing ?? 0
|
||||
|
||||
|
||||
itemWidthPercent = carouselModel.itemWidthPercent / 100.0
|
||||
if let alignment = carouselModel.itemAlignment {
|
||||
itemAlignment = alignment
|
||||
}
|
||||
|
||||
|
||||
if let height = carouselModel.height {
|
||||
collectionViewHeight?.constant = height
|
||||
collectionViewHeight?.isActive = true
|
||||
@ -168,7 +168,7 @@ open class Carousel: View {
|
||||
registerCells(with: carouselModel, delegateObject: delegateObject)
|
||||
prepareMolecules(with: carouselModel)
|
||||
FormValidator.setupValidation(for: carouselModel, delegate: delegateObject?.formHolderDelegate)
|
||||
|
||||
|
||||
setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject)
|
||||
|
||||
pageIndex = carouselModel.index
|
||||
@ -197,7 +197,7 @@ open class Carousel: View {
|
||||
if carouselModel?.loop ?? false && newMolecules.count > 1 && !UIAccessibility.isVoiceOverRunning {
|
||||
// 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(contentsOf: newMolecules.suffix(2), at: 0)
|
||||
molecules?.append(contentsOf: newMolecules.prefix(2))
|
||||
} else {
|
||||
@ -207,12 +207,25 @@ open class Carousel: View {
|
||||
pageIndex = 0
|
||||
}
|
||||
|
||||
var pagingMoleculeName: String?
|
||||
|
||||
/// Sets up the paging molecule
|
||||
open func setupPagingMolecule(_ molecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)?, delegateObject: MVMCoreUIDelegateObject?) {
|
||||
var pagingView: (UIView & CarouselPageControlProtocol)? = nil
|
||||
|
||||
if let molecule = molecule,
|
||||
(!molecule.hidesForSinglePage || numberOfPages > 1) {
|
||||
pagingView = ModelRegistry.createMolecule(molecule, delegateObject: delegateObject) as? (UIView & CarouselPageControlProtocol)
|
||||
molecule.moleculeName == pagingMoleculeName {
|
||||
pagingView?.set(with: molecule, delegateObject, nil)
|
||||
pagingView?.numberOfPages = numberOfPages
|
||||
return
|
||||
}
|
||||
|
||||
var pagingView: (MoleculeViewProtocol & CarouselPageControlProtocol)? = nil
|
||||
if let molecule = molecule,
|
||||
(!molecule.hidesForSinglePage || numberOfPages > 1) {
|
||||
pagingView = ModelRegistry.createMolecule(molecule, delegateObject: delegateObject) as? (MoleculeViewProtocol & CarouselPageControlProtocol)
|
||||
pagingMoleculeName = molecule.moleculeName
|
||||
} else {
|
||||
pagingMoleculeName = nil
|
||||
}
|
||||
|
||||
addPaging(view: pagingView, position: molecule?.position ?? 20)
|
||||
@ -239,7 +252,7 @@ open class Carousel: View {
|
||||
}
|
||||
|
||||
/// 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 & CarouselPageControlProtocol)?, position: CGFloat) {
|
||||
open func addPaging(view: (MoleculeViewProtocol & CarouselPageControlProtocol)?, position: CGFloat) {
|
||||
|
||||
pagingView?.removeFromSuperview()
|
||||
bottomPin?.isActive = false
|
||||
@ -296,7 +309,7 @@ open class Carousel: View {
|
||||
|
||||
func trackSwipeActionAnalyticsforIndex(_ index : Int){
|
||||
guard let itemModel = molecules?[index],
|
||||
let viewControllerObject = delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol else { return }
|
||||
let viewControllerObject = delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol else { return }
|
||||
MVMCoreUILoggingHandler.shared()?.defaultLogAction(forController: viewControllerObject, actionInformation: itemModel.toJSON(), additionalData: nil)
|
||||
}
|
||||
|
||||
@ -352,7 +365,7 @@ open class Carousel: View {
|
||||
carouselAccessibilityElement.accessibilityFrameInContainerSpace = collectionView.frame
|
||||
self.carouselAccessibilityElement = carouselAccessibilityElement
|
||||
}
|
||||
|
||||
|
||||
if let currentCell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) {
|
||||
_accessibilityElements = [currentCell, carouselAccessibilityElement]
|
||||
} else {
|
||||
@ -378,11 +391,11 @@ extension Carousel: UICollectionViewDataSource {
|
||||
open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return molecules?.count ?? 0
|
||||
}
|
||||
|
||||
|
||||
open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
guard let molecule = molecules?[indexPath.row],
|
||||
let moleculeInfo = getMoleculeInfo(with: molecule, delegateObject: nil)
|
||||
else { return UICollectionViewCell() }
|
||||
let moleculeInfo = getMoleculeInfo(with: molecule, delegateObject: nil)
|
||||
else { return UICollectionViewCell() }
|
||||
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: moleculeInfo.identifier, for: indexPath)
|
||||
if let protocolCell = cell as? MoleculeViewProtocol {
|
||||
@ -445,22 +458,22 @@ extension Carousel: UIScrollViewDelegate {
|
||||
if translatedPoint > 0 {
|
||||
// Moving left, see if we are moving passed the first left buffer card and adjust
|
||||
if let threshold = collectionView.layoutAttributesForItem(at: IndexPath(item: 1, section: 0))?.frame.minX,
|
||||
scrollView.contentOffset.x < threshold,
|
||||
let newOffset = collectionView.layoutAttributesForItem(at: IndexPath(item: numberOfPages + 1, section: 0))?.frame.minX {
|
||||
scrollView.contentOffset.x < threshold,
|
||||
let newOffset = collectionView.layoutAttributesForItem(at: IndexPath(item: numberOfPages + 1, section: 0))?.frame.minX {
|
||||
scrollView.contentOffset.x = newOffset
|
||||
}
|
||||
} else if translatedPoint < 0 {
|
||||
// Moving right, see if we are moving passed the first right buffer card and adjust
|
||||
if let threshold = collectionView.layoutAttributesForItem(at: IndexPath(item: numberOfPages + 2, section: 0))?.frame.maxX,
|
||||
scrollView.contentOffset.x + scrollView.bounds.width > threshold,
|
||||
let newEndOffset = collectionView.layoutAttributesForItem(at: IndexPath(item: 2, section: 0))?.frame.maxX {
|
||||
scrollView.contentOffset.x + scrollView.bounds.width > threshold,
|
||||
let newEndOffset = collectionView.layoutAttributesForItem(at: IndexPath(item: 2, section: 0))?.frame.maxX {
|
||||
scrollView.contentOffset.x = newEndOffset - scrollView.bounds.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
|
||||
|
||||
// Adjust for looping
|
||||
if loop == true {
|
||||
adjustOffsetForLooping(scrollView)
|
||||
@ -474,7 +487,7 @@ extension Carousel: UIScrollViewDelegate {
|
||||
|
||||
// Disable peaking when dragging.
|
||||
dragging = true
|
||||
|
||||
|
||||
showPeaking(false)
|
||||
}
|
||||
|
||||
@ -484,7 +497,7 @@ extension Carousel: UIScrollViewDelegate {
|
||||
|
||||
// This is for setting up smooth custom paging. (Since UICollectionView only handles paging based on collection view size and not cell size). Math requires that we are using UICollectionViewFlowLayout.
|
||||
guard (model as? CarouselModel)?.paging == true,
|
||||
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
|
||||
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
|
||||
|
||||
let separatorWidth = layout.minimumLineSpacing
|
||||
let itemWidth = collectionView.bounds.width * itemWidthPercent
|
||||
@ -529,7 +542,7 @@ extension Carousel: UIScrollViewDelegate {
|
||||
} else {
|
||||
targetContentOffset.pointee = scrollView.contentOffset
|
||||
}
|
||||
|
||||
|
||||
// Cap the index.
|
||||
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
||||
goTo(min(max(cellToSwipeTo, 0), lastCellIndex), animated: true)
|
||||
@ -548,35 +561,35 @@ extension Carousel: UIScrollViewDelegate {
|
||||
// Adapted from: https://developer.apple.com/documentation/uikit/accessibility_for_ios_and_tvos/delivering_an_exceptional_accessibility_experience
|
||||
/// Ensures a good accessibility experience. Adds adjustable swiping for cards.
|
||||
class CarouselAccessibilityElement: UIAccessibilityElement {
|
||||
|
||||
|
||||
/// This indicates to the user what exactly this element is supposed to be.
|
||||
override var accessibilityLabel: String? {
|
||||
get {
|
||||
guard let containerView = accessibilityContainer as? Carousel,
|
||||
let accessibilityLabel = containerView.accessibilityLabel else { return super.accessibilityLabel }
|
||||
let accessibilityLabel = containerView.accessibilityLabel else { return super.accessibilityLabel }
|
||||
return accessibilityLabel
|
||||
}
|
||||
set {
|
||||
super.accessibilityLabel = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override var accessibilityValue: String? {
|
||||
get {
|
||||
// Read which card we are on.
|
||||
guard let containerView = accessibilityContainer as? Carousel,
|
||||
let format = MVMCoreUIUtility.hardcodedString(withKey: "index_string_of_total"),
|
||||
let indexString = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: containerView.currentIndex + 1)) else {
|
||||
let format = MVMCoreUIUtility.hardcodedString(withKey: "index_string_of_total"),
|
||||
let indexString = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: containerView.currentIndex + 1)) else {
|
||||
return super.accessibilityValue
|
||||
}
|
||||
return String(format: format, indexString, containerView.numberOfPages)
|
||||
}
|
||||
|
||||
|
||||
set {
|
||||
super.accessibilityValue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This tells VoiceOver that our element will support the increment and decrement callbacks.
|
||||
/// - Tag: accessibility_traits
|
||||
override var accessibilityTraits: UIAccessibilityTraits {
|
||||
@ -587,41 +600,41 @@ class CarouselAccessibilityElement: UIAccessibilityElement {
|
||||
super.accessibilityTraits = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
A convenience for forward scrolling in both `accessibilityIncrement` and `accessibilityScroll`.
|
||||
It returns a `Bool` because `accessibilityScroll` needs to know if the scroll was successful.
|
||||
*/
|
||||
A convenience for forward scrolling in both `accessibilityIncrement` and `accessibilityScroll`.
|
||||
It returns a `Bool` because `accessibilityScroll` needs to know if the scroll was successful.
|
||||
*/
|
||||
func accessibilityScrollForward() -> Bool {
|
||||
guard let containerView = accessibilityContainer as? Carousel else { return false }
|
||||
|
||||
|
||||
let newIndex = containerView.currentIndex + 1
|
||||
guard newIndex < containerView.numberOfPages else { return false }
|
||||
|
||||
|
||||
containerView.goTo(newIndex, animated: false)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
A convenience for backward scrolling in both `accessibilityIncrement` and `accessibilityScroll`.
|
||||
It returns a `Bool` because `accessibilityScroll` needs to know if the scroll was successful.
|
||||
*/
|
||||
A convenience for backward scrolling in both `accessibilityIncrement` and `accessibilityScroll`.
|
||||
It returns a `Bool` because `accessibilityScroll` needs to know if the scroll was successful.
|
||||
*/
|
||||
func accessibilityScrollBackward() -> Bool {
|
||||
guard let containerView = accessibilityContainer as? Carousel else { return false }
|
||||
|
||||
|
||||
let newIndex = containerView.currentIndex - 1
|
||||
guard newIndex >= 0 else { return false }
|
||||
|
||||
|
||||
containerView.goTo(newIndex, animated: false)
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Overriding the following two methods allows the user to perform increment and decrement actions
|
||||
(done by swiping up or down).
|
||||
*/
|
||||
Overriding the following two methods allows the user to perform increment and decrement actions
|
||||
(done by swiping up or down).
|
||||
*/
|
||||
/// - Tag: accessibility_increment_decrement
|
||||
override func accessibilityIncrement() {
|
||||
// This causes the picker to move forward one if the user swipes up.
|
||||
@ -632,12 +645,12 @@ class CarouselAccessibilityElement: UIAccessibilityElement {
|
||||
// This causes the picker to move back one if the user swipes down.
|
||||
_ = accessibilityScrollBackward()
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
This will cause the picker to move forward or backwards on when the user does a 3-finger swipe,
|
||||
depending on the direction of the swipe. The return value indicates whether or not the scroll was successful,
|
||||
so that VoiceOver can alert the user if it was not.
|
||||
*/
|
||||
This will cause the picker to move forward or backwards on when the user does a 3-finger swipe,
|
||||
depending on the direction of the swipe. The return value indicates whether or not the scroll was successful,
|
||||
so that VoiceOver can alert the user if it was not.
|
||||
*/
|
||||
override func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool {
|
||||
if direction == .left {
|
||||
return accessibilityScrollForward()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user