work in progress
This commit is contained in:
parent
4577274e08
commit
fd72f5ff95
@ -63,7 +63,6 @@
|
||||
0A14F69323E349EF00EDF7F7 /* CarouselIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F69223E349EF00EDF7F7 /* CarouselIndicator.swift */; };
|
||||
0A14F6A523E4803A00EDF7F7 /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F6A423E4803A00EDF7F7 /* StackView.swift */; };
|
||||
0A14F6A923E8750300EDF7F7 /* CarouselIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F6A823E8750300EDF7F7 /* CarouselIndicatorModel.swift */; };
|
||||
0A14F6B223E8C28D00EDF7F7 /* BarsIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F6B123E8C28D00EDF7F7 /* BarsIndicatorView.swift */; };
|
||||
0A14F6B423E8C29700EDF7F7 /* NumericIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F6B323E8C29700EDF7F7 /* NumericIndicatorView.swift */; };
|
||||
0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */; };
|
||||
0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* EntryField.swift */; };
|
||||
@ -94,7 +93,10 @@
|
||||
0A7EF86323D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86223D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift */; };
|
||||
0A7EF86523D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */; };
|
||||
0A7EF86723D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift */; };
|
||||
0A9F3DE823EDE9F200318918 /* Arrow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9F3DE723EDE9F200318918 /* Arrow.swift */; };
|
||||
0A9F3DEA23EDEA1A00318918 /* ArrowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9F3DE923EDEA1A00318918 /* ArrowModel.swift */; };
|
||||
0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA33B392398524F0067DD0F /* Toggle.swift */; };
|
||||
0AB2AA2323F19CFA00C6D3CF /* BarsIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB2AA2223F19CFA00C6D3CF /* BarsIndicatorView.swift */; };
|
||||
0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */; };
|
||||
0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */; };
|
||||
0AE14F64238315D2005417F8 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE14F63238315D2005417F8 /* TextField.swift */; };
|
||||
@ -394,7 +396,6 @@
|
||||
0A14F69223E349EF00EDF7F7 /* CarouselIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselIndicator.swift; sourceTree = "<group>"; };
|
||||
0A14F6A423E4803A00EDF7F7 /* StackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackView.swift; sourceTree = "<group>"; };
|
||||
0A14F6A823E8750300EDF7F7 /* CarouselIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselIndicatorModel.swift; sourceTree = "<group>"; };
|
||||
0A14F6B123E8C28D00EDF7F7 /* BarsIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsIndicatorView.swift; sourceTree = "<group>"; };
|
||||
0A14F6B323E8C29700EDF7F7 /* NumericIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumericIndicatorView.swift; sourceTree = "<group>"; };
|
||||
0A209CD223A7E2810068F8B0 /* UIStackViewAlignment+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackViewAlignment+Extension.swift"; sourceTree = "<group>"; };
|
||||
0A21DB7E235DECC500C160A2 /* EntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryField.swift; sourceTree = "<group>"; };
|
||||
@ -415,8 +416,11 @@
|
||||
0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryFieldModel.swift; sourceTree = "<group>"; };
|
||||
0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryFieldModel.swift; sourceTree = "<group>"; };
|
||||
0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = "<group>"; };
|
||||
0A9F3DE723EDE9F200318918 /* Arrow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Arrow.swift; sourceTree = "<group>"; };
|
||||
0A9F3DE923EDEA1A00318918 /* ArrowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrowModel.swift; sourceTree = "<group>"; };
|
||||
0AA33B33239813C50067DD0F /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = "<group>"; };
|
||||
0AA33B392398524F0067DD0F /* Toggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toggle.swift; sourceTree = "<group>"; };
|
||||
0AB2AA2223F19CFA00C6D3CF /* BarsIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarsIndicatorView.swift; sourceTree = "<group>"; };
|
||||
0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = "<group>"; };
|
||||
0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = "<group>"; };
|
||||
0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
|
||||
@ -742,7 +746,7 @@
|
||||
0A14F6B723ECA7F900EDF7F7 /* IndicatorViews */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0A14F6B123E8C28D00EDF7F7 /* BarsIndicatorView.swift */,
|
||||
0AB2AA2223F19CFA00C6D3CF /* BarsIndicatorView.swift */,
|
||||
0A14F6B323E8C29700EDF7F7 /* NumericIndicatorView.swift */,
|
||||
);
|
||||
path = IndicatorViews;
|
||||
@ -1241,6 +1245,8 @@
|
||||
0AA33B392398524F0067DD0F /* Toggle.swift */,
|
||||
D260105423CEA7DC00764D80 /* MVMCoreUISwitch+Model.swift */,
|
||||
012CA99D2385A2D3003F810F /* MFView+ModelExtension.swift */,
|
||||
0A9F3DE723EDE9F200318918 /* Arrow.swift */,
|
||||
0A9F3DE923EDEA1A00318918 /* ArrowModel.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@ -1688,6 +1694,7 @@
|
||||
D2B18B94236214AD00A9AEDC /* NavigationController.swift in Sources */,
|
||||
D29E28DA23D21AFA00ACEA85 /* StringAndMoleculeModel.swift in Sources */,
|
||||
D282AACB2243C61700C46919 /* ButtonView.swift in Sources */,
|
||||
0A9F3DE823EDE9F200318918 /* Arrow.swift in Sources */,
|
||||
D260105D23D0BCD400764D80 /* Stack.swift in Sources */,
|
||||
0A7EF85D23D8A95600B2AAD1 /* TextEntryFieldModel.swift in Sources */,
|
||||
D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */,
|
||||
@ -1773,9 +1780,11 @@
|
||||
943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */,
|
||||
D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */,
|
||||
948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */,
|
||||
0A9F3DEA23EDEA1A00318918 /* ArrowModel.swift in Sources */,
|
||||
D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */,
|
||||
D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */,
|
||||
D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */,
|
||||
0AB2AA2323F19CFA00C6D3CF /* BarsIndicatorView.swift in Sources */,
|
||||
94C2D9A323872C110006CF46 /* LabelAttributeStrikeThroughModel.swift in Sources */,
|
||||
D28A838523CCCA8900DFE4FC /* ScrollerModel.swift in Sources */,
|
||||
D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */,
|
||||
@ -1798,7 +1807,6 @@
|
||||
D2B18B922361E65A00A9AEDC /* CoreUIObject.swift in Sources */,
|
||||
D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */,
|
||||
014AA72E23C5059B006F3E93 /* StackCenteredPageTemplateModel.swift in Sources */,
|
||||
0A14F6B223E8C28D00EDF7F7 /* BarsIndicatorView.swift in Sources */,
|
||||
D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */,
|
||||
D260105923D0A92900764D80 /* ContainerProtocol.swift in Sources */,
|
||||
C695A69423C9909000BFB94E /* DoughnutChartModel.swift in Sources */,
|
||||
|
||||
24
MVMCoreUI/Atoms/Views/Arrow.swift
Normal file
24
MVMCoreUI/Atoms/Views/Arrow.swift
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// Arrow.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Kevin Christiano on 2/7/20.
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
open class Arrow: View {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
public var color: Color?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
|
||||
|
||||
}
|
||||
61
MVMCoreUI/Atoms/Views/ArrowModel.swift
Normal file
61
MVMCoreUI/Atoms/Views/ArrowModel.swift
Normal file
@ -0,0 +1,61 @@
|
||||
|
||||
//
|
||||
// ArrowModel.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Kevin Christiano on 2/7/20.
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class ArrowModel: MoleculeModelProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
public var moleculeName: String?
|
||||
public var backgroundColor: Color?
|
||||
|
||||
public static var identifier: String {
|
||||
return "arrow"
|
||||
}
|
||||
|
||||
public var enabledColor: Color?
|
||||
public var disabledColor: Color?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Keys
|
||||
//--------------------------------------------------
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case moleculeName
|
||||
case backgroundColor
|
||||
case enabledColor
|
||||
case disabledColor
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Codec
|
||||
//--------------------------------------------------
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
// if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) {
|
||||
// self.state = state
|
||||
// }
|
||||
// action = try typeContainer.decodeModelIfPresent(codingKey: .action, typeCodingKey: ActionCodingKey.actionType)
|
||||
// alternateAction = try typeContainer.decodeModelIfPresent(codingKey: .alternateAction, typeCodingKey: ActionCodingKey.actionType)
|
||||
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
||||
disabledColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledColor)
|
||||
enabledColor = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledColor)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||
try container.encodeIfPresent(enabledColor, forKey: .enabledColor)
|
||||
try container.encode(moleculeName, forKey: .moleculeName)
|
||||
try container.encodeIfPresent(disabledColor, forKey: .disabledColor)
|
||||
}
|
||||
}
|
||||
@ -9,13 +9,19 @@
|
||||
import Foundation
|
||||
|
||||
public protocol IndicatorViewProtocol {
|
||||
func updateUI(oldIndex: Int, newIndex: Int)
|
||||
func updateUI(previousIndex: Int, newIndex: Int, totalCount: Int, isAnimated: Bool)
|
||||
func reset()
|
||||
var isEnabled: Bool { get set }
|
||||
var currentIndex: Int? { get set }
|
||||
var numberOfPages: Int? { get }
|
||||
}
|
||||
|
||||
|
||||
open class CarouselIndicator: Control {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Outlets
|
||||
//--------------------------------------------------
|
||||
|
||||
public typealias IndicatorView = UIView & IndicatorViewProtocol
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Constraints
|
||||
//--------------------------------------------------
|
||||
@ -27,8 +33,6 @@ open class CarouselIndicator: Control {
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
public typealias IndicatorView = UIView & IndicatorViewProtocol
|
||||
|
||||
/// The types of indicators that can appear.
|
||||
public enum IndicatorType: String {
|
||||
case bar
|
||||
@ -38,47 +42,42 @@ open class CarouselIndicator: Control {
|
||||
|
||||
/// Determines interactivity and appearance of the indicator.
|
||||
public var indicatorType: IndicatorType = .hybrid {
|
||||
didSet {
|
||||
assignIndicatorView()
|
||||
}
|
||||
didSet { assignIndicatorView() }
|
||||
}
|
||||
|
||||
/// The currently active indicator view.
|
||||
public var currentIndicator: IndicatorView?
|
||||
|
||||
public var carouselIndicatorModel: CarouselIndicatorModel? {
|
||||
return model as? CarouselIndicatorModel
|
||||
}
|
||||
|
||||
/// The view control relative to the state of the indicator type.
|
||||
private(set) var indicatorView: IndicatorView? {
|
||||
didSet {
|
||||
topConstraint = indicatorView!.topAnchor.constraint(equalTo: topAnchor, constant: PaddingTwo)
|
||||
topConstraint = indicatorView?.topAnchor.constraint(equalTo: topAnchor, constant: PaddingTwo)
|
||||
topConstraint?.isActive = true
|
||||
|
||||
bottomConstraint = bottomAnchor.constraint(equalTo: indicatorView!.bottomAnchor, constant: PaddingTwo)
|
||||
bottomConstraint?.isActive = true
|
||||
if let indicatorView = indicatorView {
|
||||
bottomConstraint = bottomAnchor.constraint(equalTo: indicatorView.bottomAnchor, constant: PaddingTwo)
|
||||
bottomConstraint?.isActive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The maxmum count of pages before the indicatorView forces a numeric Indicator insead of Bar.
|
||||
public var hybridThreshold: Int = 5
|
||||
|
||||
/// Spacing used between bars of the Bars Indicator and between the title and arrows of the Numeric Indicator
|
||||
public var indicatorBarSpacing: CGFloat = 6 {
|
||||
didSet {
|
||||
if let stackView = indicatorView as? StackView {
|
||||
stackView.spacing = indicatorBarSpacing
|
||||
stackView.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private(set) var indicatorBars = [BarIndicator]()
|
||||
|
||||
/// Set this closure to perform an action when a different indicator was selected.
|
||||
public var indicatorTappedBlock: ((Int)->())?
|
||||
public var indicatorTouchAction: ((Int)->())?
|
||||
|
||||
/// Allows sendActions() to trigger even if index is min/max index.
|
||||
public var alwaysSendControlEvent = false
|
||||
public var alwaysSendEvent = false
|
||||
|
||||
/// Set true to make the accessibility value as "Slide #currentPage of #totalPage", otherwise will be "Page #currentPage of #totalPage", default is false
|
||||
public var isSlidesAccessibile = false
|
||||
public var accessibilityHasSlidesInsteadOfPage = false
|
||||
|
||||
public var isAnimated = false
|
||||
public var isAnimated = true
|
||||
|
||||
/// Will hide this control if page count is 1.
|
||||
public var hidesForSinglePage = false {
|
||||
@ -97,11 +96,7 @@ open class CarouselIndicator: Control {
|
||||
|
||||
} else {
|
||||
if let stackView = indicatorView as? BarsIndicatorView {
|
||||
stackView.stackView.arrangedSubviews.forEach { view in
|
||||
// if let indicator = {
|
||||
(view as? BarsIndicatorView)?.isEnabled = isEnabled
|
||||
// }
|
||||
}
|
||||
stackView.stackView.arrangedSubviews.forEach { ($0 as? BarsIndicatorView)?.isEnabled = isEnabled }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,25 +106,22 @@ open class CarouselIndicator: Control {
|
||||
// MARK: - Computed Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
/// The currently active indicator view.
|
||||
public weak var currentIndicator: IndicatorView? {
|
||||
didSet {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private(set) var oldIndex = 0
|
||||
private(set) var previousIndex = 0
|
||||
private var _currentIndex = 0
|
||||
|
||||
public var currentIndex: Int {
|
||||
get { return _currentIndex }
|
||||
set (newIndex) {
|
||||
guard _currentIndex != newIndex else { return }
|
||||
oldIndex = _currentIndex
|
||||
|
||||
previousIndex = _currentIndex
|
||||
_currentIndex = newIndex
|
||||
sendActions(for: .valueChanged)
|
||||
indicatorTappedBlock?(newIndex)
|
||||
indicatorView?.updateUI(oldIndex: oldIndex, newIndex: newIndex)
|
||||
indicatorTouchAction?(newIndex)
|
||||
indicatorView?.updateUI(previousIndex: previousIndex,
|
||||
newIndex: newIndex,
|
||||
totalCount: numberOfPages,
|
||||
isAnimated: isAnimated)
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,44 +129,46 @@ open class CarouselIndicator: Control {
|
||||
|
||||
public var numberOfPages: Int {
|
||||
get { return _numberOfPages }
|
||||
set {
|
||||
guard _numberOfPages != newValue else { return }
|
||||
_numberOfPages = newValue
|
||||
set (newTotal) {
|
||||
guard _numberOfPages != newTotal else { return }
|
||||
_numberOfPages = newTotal
|
||||
|
||||
if hidesForSinglePage && newValue <= 1 {
|
||||
if hidesForSinglePage && newTotal <= 1 {
|
||||
isHidden = true
|
||||
} else {
|
||||
isHidden = false
|
||||
indicatorView = BarsIndicatorView(numberOfBars: numberOfPages)
|
||||
indicatorView = BarsIndicatorView()
|
||||
}
|
||||
|
||||
indicatorView?.updateUI(oldIndex: oldIndex, newIndex: currentIndex)
|
||||
if alwaysSendEvent {
|
||||
sendActions(for: .valueChanged)
|
||||
}
|
||||
|
||||
indicatorView?.updateUI(previousIndex: previousIndex,
|
||||
newIndex: currentIndex,
|
||||
totalCount: newTotal,
|
||||
isAnimated: isAnimated)
|
||||
}
|
||||
}
|
||||
|
||||
private var _indicatorTintColor: UIColor = .mvmCoolGray6
|
||||
public var disabledIndicatorColor: UIColor = .mvmCoolGray3
|
||||
|
||||
private var _indicatorTintColor: UIColor = .black
|
||||
|
||||
public var indicatorTintColor: UIColor {
|
||||
get { return _indicatorTintColor }
|
||||
set {
|
||||
_indicatorTintColor = newValue
|
||||
if isBarIndicator(), let barsView = (indicatorView as? BarsIndicatorView)?.stackView.arrangedSubviews.isEmpty {
|
||||
indicatorView = BarsIndicatorView(numberOfBars: numberOfPages)
|
||||
}
|
||||
(indicatorView as? BarsIndicatorView)?.stackView.arrangedSubviews.forEach { $0.backgroundColor = newValue}
|
||||
set (newColor) {
|
||||
_indicatorTintColor = newColor
|
||||
}
|
||||
}
|
||||
|
||||
private var _currentPageIndicatorTintColor: UIColor = .black
|
||||
private var _currentIndicatorColor: UIColor = .black
|
||||
|
||||
public var currentPageIndicatorTintColor: UIColor {
|
||||
get { return _currentPageIndicatorTintColor }
|
||||
set {
|
||||
_currentPageIndicatorTintColor = newValue
|
||||
if ((indicatorView as? BarsIndicatorView)?.stackView.arrangedSubviews.isEmpty)! {
|
||||
indicatorView = BarsIndicatorView(numberOfBars: numberOfPages)
|
||||
}
|
||||
currentIndicator?.backgroundColor = newValue
|
||||
/// Colors the currently selected index, unique from other indicators
|
||||
public var currentIndicatorColor: UIColor {
|
||||
get { return _currentIndicatorColor }
|
||||
set (newColor) {
|
||||
_currentIndicatorColor = newColor
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,45 +207,57 @@ open class CarouselIndicator: Control {
|
||||
open override func setupView() {
|
||||
super.setupView()
|
||||
|
||||
if indicatorView == nil {
|
||||
|
||||
assignIndicatorView()
|
||||
|
||||
if let accessibleValue = MVMCoreUIUtility.hardcodedString(withKey: isSlidesAccessibile ? "MVMCoreUIPageControlslides_currentpage_index" : "MVMCoreUIPageControl_currentpage_index") {
|
||||
accessibilityValue = String(format: accessibleValue, currentIndex + 1, numberOfPages)
|
||||
}
|
||||
guard indicatorView == nil else { return }
|
||||
|
||||
assignIndicatorView()
|
||||
|
||||
if let accessibleValue = MVMCoreUIUtility.hardcodedString(withKey: accessibilityHasSlidesInsteadOfPage ? "MVMCoreUIPageControlslides_currentpage_index" : "MVMCoreUIPageControl_currentpage_index") {
|
||||
accessibilityValue = String(format: accessibleValue, currentIndex + 1, numberOfPages)
|
||||
}
|
||||
}
|
||||
|
||||
open override func updateView(_ size: CGFloat) {
|
||||
super.updateView(size)
|
||||
//--------------------------------------------------
|
||||
// MARK: - UITouch
|
||||
//--------------------------------------------------
|
||||
|
||||
@objc func pageValueIncrement() {
|
||||
|
||||
currentIndex = min(currentIndex + 1, numberOfPages - 1)
|
||||
}
|
||||
|
||||
@objc func pageValueDecrement() {
|
||||
|
||||
currentIndex = max(0, currentIndex - 1)
|
||||
}
|
||||
|
||||
func indicatorTapped(_ tapGesture: UITapGestureRecognizer?) {
|
||||
|
||||
if isEnabled, let bars = (indicatorView as? BarsIndicatorView)?.barsReference {
|
||||
let touchPoint_X = tapGesture?.location(in: self).x ?? 0.0
|
||||
|
||||
currentIndex = bars.firstIndex { $0.0.frame.maxX >= touchPoint_X && $0.0.frame.minX <= touchPoint_X } ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
/// Sets the indicatorView based on the current indicatorType.
|
||||
func assignIndicatorView() {
|
||||
|
||||
switch indicatorType {
|
||||
case .bar:
|
||||
indicatorView = BarsIndicatorView(numberOfBars: numberOfPages)
|
||||
indicatorView = BarsIndicatorView()
|
||||
|
||||
case .numeric:
|
||||
indicatorView = NumericIndicatorView()
|
||||
|
||||
case .hybrid:
|
||||
indicatorView = numberOfPages >= hybridThreshold ? NumericIndicatorView() : BarsIndicatorView(numberOfBars: numberOfPages)
|
||||
indicatorView = numberOfPages >= hybridThreshold ? NumericIndicatorView() : BarsIndicatorView()
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes all indicators from their subview and then clears the holding array.
|
||||
func removeIndicatorView() {
|
||||
|
||||
// indicatorBars.forEach { $0.removeFromSuperview() }
|
||||
// indicatorBars = []
|
||||
}
|
||||
|
||||
/// Convenience to determine if current view is displaying bars.
|
||||
func isBarIndicator() -> Bool {
|
||||
return indicatorType != .bar && numberOfPages > hybridThreshold
|
||||
@ -266,11 +272,8 @@ open class CarouselIndicator: Control {
|
||||
|
||||
guard let model = model as? CarouselIndicatorModel else { return }
|
||||
|
||||
if let type = model.type, let indicator = IndicatorType(rawValue: type) {
|
||||
indicatorType = indicator
|
||||
}
|
||||
|
||||
// backgroundColor = model.backgroundColor?.uiColor
|
||||
indicatorType = IndicatorType(rawValue: model.type ?? "") ?? .hybrid
|
||||
backgroundColor = model.backgroundColor?.uiColor
|
||||
// barsColor = model.barsColor
|
||||
// pageIndicatorTintColor
|
||||
// currentPageIndicatorTintColor
|
||||
@ -290,11 +293,11 @@ open class CarouselIndicator: Control {
|
||||
|
||||
func accessibilityAdjust(toPage index: Int) {
|
||||
|
||||
if (index < numberOfPages && index >= 0) || alwaysSendControlEvent {
|
||||
if (index < numberOfPages && index >= 0) || alwaysSendEvent {
|
||||
isAnimated = false
|
||||
currentIndex = index
|
||||
sendActions(for: .valueChanged)
|
||||
indicatorTappedBlock?(index)
|
||||
indicatorTouchAction?(index)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -22,7 +22,19 @@ public class CarouselIndicatorModel: MoleculeModelProtocol {
|
||||
|
||||
public var moleculeName: String?
|
||||
public var type: String? = "hybrid"
|
||||
public var hybridThreshold: Int = 5
|
||||
public var barsColor: Color?
|
||||
public var currentBarColor: Color?
|
||||
public var currentIndex: Int? = 0
|
||||
public var numberOfPages: Int? = 0
|
||||
public var alwaysSendEvent: Bool? = false
|
||||
public var isAnimated: Bool? = true
|
||||
public var hidesForSinglePage: Bool? = false
|
||||
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)
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Keys
|
||||
@ -33,6 +45,7 @@ public class CarouselIndicatorModel: MoleculeModelProtocol {
|
||||
case backgroundColor
|
||||
case type
|
||||
case barsColor
|
||||
case currentBarColor
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -41,11 +54,8 @@ public class CarouselIndicatorModel: MoleculeModelProtocol {
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
// if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) {
|
||||
// self.state = state
|
||||
// }
|
||||
// action = try typeContainer.decodeModelIfPresent(codingKey: .action, typeCodingKey: ActionCodingKey.actionType)
|
||||
// alternateAction = try typeContainer.decodeModelIfPresent(codingKey: .alternateAction, typeCodingKey: ActionCodingKey.actionType)
|
||||
|
||||
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"
|
||||
@ -55,6 +65,7 @@ public class CarouselIndicatorModel: MoleculeModelProtocol {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
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)
|
||||
}
|
||||
|
||||
@ -1,156 +0,0 @@
|
||||
//
|
||||
// BarIndicatorView.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Kevin Christiano on 2/3/20.
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
open class BarsIndicatorView: View, IndicatorViewProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
let stackView: StackView = {
|
||||
let stackView = StackView()
|
||||
stackView.axis = .horizontal
|
||||
stackView.distribution = .equalSpacing
|
||||
stackView.spacing = PaddingOne
|
||||
return stackView
|
||||
}()
|
||||
|
||||
var barsReference: [(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)
|
||||
|
||||
public var enabledColor: UIColor = .black
|
||||
public var disabledColor: UIColor = .mvmCoolGray3
|
||||
|
||||
private var oldIndex: Int = 0
|
||||
|
||||
/// Returns the currentIndex from its parent CarouselIndicator.
|
||||
public var currentIndex: Int? {
|
||||
get { return (superview as? CarouselIndicator)?.currentIndex }
|
||||
set {
|
||||
guard let newValue = newValue else { return }
|
||||
(superview as? CarouselIndicator)?.currentIndex = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the numberOfPages count from its parent CarouselIndicator.
|
||||
public var numberOfPages: Int? {
|
||||
return (superview as? CarouselIndicator)?.numberOfPages
|
||||
}
|
||||
|
||||
public var numberOfBars: Int = 0 {
|
||||
didSet {
|
||||
// TODO: Generate bars...
|
||||
}
|
||||
}
|
||||
|
||||
open var isEnabled: Bool = true {
|
||||
didSet {
|
||||
barsReference.forEach { view, heightConstraint in
|
||||
view.backgroundColor = isEnabled ? enabledColor : disabledColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open var isAnimated: Bool {
|
||||
return (superview as? CarouselIndicator)?.isAnimated ?? true
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
|
||||
public init(numberOfBars: Int) {
|
||||
super.init(frame: .zero)
|
||||
self.numberOfBars = numberOfBars
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Setup
|
||||
//--------------------------------------------------
|
||||
|
||||
open override func setupView() {
|
||||
super.setupView()
|
||||
|
||||
if subviews.isEmpty {
|
||||
addSubview(stackView)
|
||||
|
||||
stackView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
|
||||
stackView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor).isActive = true
|
||||
trailingAnchor.constraint(lessThanOrEqualTo: stackView.trailingAnchor).isActive = true
|
||||
|
||||
clearBars()
|
||||
|
||||
let tapGesture = UITapGestureRecognizer()
|
||||
tapGesture.addTarget(self, action: #selector(indicatorTapped(_:)))
|
||||
addGestureRecognizer(tapGesture)
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
func generateBars() {
|
||||
|
||||
var bars = [(View, NSLayoutConstraint)]()
|
||||
|
||||
for i in 0..<numberOfBars {
|
||||
let bar = View()
|
||||
bar.widthAnchor.constraint(equalToConstant: BarsIndicatorView.indicatorBarWidth).isActive = true
|
||||
bar.backgroundColor = enabledColor
|
||||
// let barHeight = i == currentIndex ? indicatorBarWidth.indicatorBarHeight.selected : indicatorBarWidth.indicatorBarHeight.unselected
|
||||
// let heightConstraint = bar.heightAnchor.constraint(equalToConstant: barHeight)
|
||||
// heightConstraint.isActive = true
|
||||
|
||||
// stackView.app
|
||||
// bars.append((bar, heightConstraint))
|
||||
}
|
||||
|
||||
barsReference = bars
|
||||
}
|
||||
|
||||
func clearBars() {
|
||||
barsReference.forEach { $0.view.removeFromSuperview() }
|
||||
barsReference = []
|
||||
}
|
||||
|
||||
func indicatorTapped(_ tapGesture: UITapGestureRecognizer?) {
|
||||
|
||||
if isEnabled {
|
||||
let touchPoint_X = tapGesture?.location(in: self).x ?? 0.0
|
||||
|
||||
currentIndex = barsReference.firstIndex { view, _ in
|
||||
return view.frame.maxX >= touchPoint_X && view.frame.minX <= touchPoint_X
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - IndicatorViewProtocol
|
||||
//--------------------------------------------------
|
||||
|
||||
public func updateUI(oldIndex: Int, newIndex: Int) {
|
||||
|
||||
let expression = {
|
||||
self.barsReference[oldIndex].constraint.constant = BarsIndicatorView.indicatorBarHeight.unselected
|
||||
self.barsReference[newIndex].constraint.constant = BarsIndicatorView.indicatorBarHeight.selected
|
||||
self.layoutIfNeeded()
|
||||
}
|
||||
|
||||
isAnimated ? UIView.animate(withDuration: 0.3) { expression() } : expression()
|
||||
}
|
||||
}
|
||||
@ -22,24 +22,24 @@ open class NumericIndicatorView: View, IndicatorViewProtocol {
|
||||
return label
|
||||
}()
|
||||
|
||||
// Left and right arrows for Numeric indicator
|
||||
open var leftArrow = MFLoadImageView()
|
||||
open var rightArrow = MFLoadImageView()
|
||||
|
||||
open var isEnabled: Bool = true
|
||||
|
||||
public var parentCarouselIndicator: CarouselIndicator? {
|
||||
return superview as? CarouselIndicator
|
||||
}
|
||||
|
||||
/// Returns the currentIndex from its parent CarouselIndicator.
|
||||
public var currentIndex: Int? {
|
||||
get { return (superview as? CarouselIndicator)?.currentIndex }
|
||||
get { return parentCarouselIndicator?.currentIndex }
|
||||
set {
|
||||
guard let newValue = newValue else { return }
|
||||
(superview as? CarouselIndicator)?.currentIndex = newValue
|
||||
parentCarouselIndicator?.currentIndex = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the numberOfPages count from its parent CarouselIndicator.
|
||||
public var numberOfPages: Int? {
|
||||
return (superview as? CarouselIndicator)?.numberOfPages
|
||||
return parentCarouselIndicator?.numberOfPages
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -76,37 +76,27 @@ open class NumericIndicatorView: View, IndicatorViewProtocol {
|
||||
|
||||
guard subviews.isEmpty else { return }
|
||||
|
||||
isUserInteractionEnabled = false
|
||||
|
||||
addSubview(titleLabel)
|
||||
titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
|
||||
NSLayoutConstraint.constraintPinSubview(titleLabel, pinTop: true, pinBottom: true, pinLeft: false, pinRight: false)
|
||||
|
||||
let arrow = UIImage(named: "peakingRightArrow")?.withHorizontallyFlippedOrientation()
|
||||
let leftArrow = UIImageView(image: arrow)
|
||||
leftArrow.isUserInteractionEnabled = true
|
||||
|
||||
addSubview(leftArrow)
|
||||
NSLayoutConstraint.constraintPinView(leftArrow, heightConstraint: true, heightConstant: PaddingTwo, widthConstraint: true, widthConstant: PaddingTwo)
|
||||
leftArrow.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
|
||||
leftArrow.isUserInteractionEnabled = true
|
||||
|
||||
leftArrow.loadImage(withName: "peakingRightArrow", width: nil, height: nil) { [weak self] image, _, _ in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let image = image else { return }
|
||||
self?.leftArrow.imageView.image = image.withHorizontallyFlippedOrientation()
|
||||
self?.leftArrow.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
let leftTap = UITapGestureRecognizer()
|
||||
leftTap.addTarget(self, action: #selector(pageValueDecrement))
|
||||
leftArrow.addGestureRecognizer(leftTap)
|
||||
|
||||
rightArrow.loadImage(withName: "peakingRightArrow")
|
||||
let rightArrow = UIImageView(image: UIImage(named: "peakingRightArrow"))
|
||||
addSubview(rightArrow)
|
||||
|
||||
NSLayoutConstraint.constraintPinView(rightArrow, heightConstraint: true, heightConstant: PaddingTwo, widthConstraint: true, widthConstant: PaddingTwo)
|
||||
rightArrow.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
|
||||
rightArrow.isUserInteractionEnabled = true
|
||||
|
||||
let rightTap = UITapGestureRecognizer()
|
||||
rightTap.addTarget(self, action: #selector(pageValueIncrement))
|
||||
rightArrow.addGestureRecognizer(rightTap)
|
||||
|
||||
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[leftArrowView]-(padding)-[titleLabel]-(padding)-[rightArrowView]-0-|",
|
||||
options: .directionLeadingToTrailing,
|
||||
metrics: ["padding": PaddingOne],
|
||||
@ -115,36 +105,17 @@ open class NumericIndicatorView: View, IndicatorViewProtocol {
|
||||
"rightArrowView": rightArrow]))
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Actions
|
||||
//--------------------------------------------------
|
||||
|
||||
@objc func pageValueIncrement() {
|
||||
guard let currentIndex = currentIndex,
|
||||
let numberOfPages = numberOfPages
|
||||
else { return }
|
||||
|
||||
self.currentIndex = min(currentIndex + 1, numberOfPages - 1)
|
||||
}
|
||||
|
||||
@objc func pageValueDecrement() {
|
||||
guard let currentIndex = currentIndex else { return }
|
||||
|
||||
self.currentIndex = max(0, currentIndex - 1)
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - IndicatorViewProtocol
|
||||
//--------------------------------------------------
|
||||
|
||||
open func updateUI(oldIndex: Int, newIndex: Int) {
|
||||
open func updateUI(previousIndex oldIndex: Int, newIndex: Int, totalCount: Int, isAnimated: Bool) {
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.titleLabel.text = "\(newIndex)/\(self.numberOfPages ?? 0)"
|
||||
self.titleLabel.text = "\(newIndex)/\(totalCount)"
|
||||
self.layoutIfNeeded()
|
||||
(self.superview as? CarouselIndicator)?.sendActions(for: .valueChanged)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
public protocol CarouselPagingModelProtocol: MoleculeModelProtocol {
|
||||
var position: Float? {get}
|
||||
var position: Float? { get }
|
||||
}
|
||||
|
||||
@ -10,12 +10,20 @@ import Foundation
|
||||
|
||||
|
||||
@objcMembers public class CarouselItemModel: MoleculeContainerModel, CarouselItemModelProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
public static var identifier: String = "carouselItem"
|
||||
public var backgroundColor: Color?
|
||||
public var peakingUI: Bool?
|
||||
public var peakingArrowColor: Color?
|
||||
public var moleculeName: String?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Keys
|
||||
//--------------------------------------------------
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case moleculeName
|
||||
case backgroundColor
|
||||
@ -23,6 +31,10 @@ import Foundation
|
||||
case peakingArrowColor
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Codec
|
||||
//--------------------------------------------------
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
moleculeName = try typeContainer.decodeIfPresent(String.self, forKey: .moleculeName)
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
import UIKit
|
||||
|
||||
open class Carousel: View {
|
||||
|
||||
|
||||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
|
||||
|
||||
/// The current index of the collection view. Includes dummy cells when looping.
|
||||
@ -17,10 +17,8 @@ open class Carousel: View {
|
||||
|
||||
/// The index of the page, does not include dummy cells.
|
||||
var pageIndex: Int {
|
||||
get {
|
||||
return loop ? currentIndex - 2 : currentIndex
|
||||
}
|
||||
set(newIndex) {
|
||||
get { return loop ? currentIndex - 2 : currentIndex }
|
||||
set (newIndex) {
|
||||
currentIndex = loop ? newIndex + 2 : newIndex
|
||||
}
|
||||
}
|
||||
@ -30,7 +28,7 @@ open class Carousel: View {
|
||||
|
||||
/// The json for the molecules.
|
||||
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
|
||||
|
||||
@ -53,9 +51,8 @@ open class Carousel: View {
|
||||
// MARK: - MVMCoreViewProtocol
|
||||
open override func setupView() {
|
||||
super.setupView()
|
||||
guard collectionView.superview == nil else {
|
||||
return
|
||||
}
|
||||
guard collectionView.superview == nil else { return }
|
||||
|
||||
collectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
collectionView.dataSource = self
|
||||
collectionView.delegate = self
|
||||
@ -73,7 +70,7 @@ open class Carousel: View {
|
||||
super.updateView(size)
|
||||
collectionView.collectionViewLayout.invalidateLayout()
|
||||
showPeaking(false)
|
||||
|
||||
|
||||
// Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled.
|
||||
DispatchQueue.main.async {
|
||||
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false)
|
||||
@ -83,32 +80,35 @@ open class Carousel: View {
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol
|
||||
|
||||
public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||
super.setWithModel(model, delegateObject, additionalData)
|
||||
|
||||
guard let carouselModel = model as? CarouselModel else { return }
|
||||
|
||||
collectionView.backgroundColor = backgroundColor
|
||||
collectionView.layer.borderColor = backgroundColor?.cgColor
|
||||
collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0
|
||||
backgroundColor = .white
|
||||
|
||||
|
||||
registerCells(with: carouselModel, delegateObject: delegateObject)
|
||||
setupLayout(with: carouselModel)
|
||||
prepareMolecules(with: carouselModel)
|
||||
itemWidthPercent = (carouselModel.itemWidthPercent ?? 100) / 100
|
||||
setAlignment(with: carouselModel.itemAlignment)
|
||||
|
||||
|
||||
if let height = carouselModel.height {
|
||||
collectionViewHeight?.constant = CGFloat(height)
|
||||
collectionViewHeight?.isActive = true
|
||||
}
|
||||
|
||||
|
||||
setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject)
|
||||
collectionView.reloadData()
|
||||
}
|
||||
|
||||
// MARK: - JSON Setters
|
||||
/// Updates the layout being used
|
||||
|
||||
|
||||
func setupLayout(with carouselModel: CarouselModel?) {
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.scrollDirection = .horizontal
|
||||
@ -116,14 +116,14 @@ open class Carousel: View {
|
||||
layout.minimumInteritemSpacing = 0
|
||||
collectionView.collectionViewLayout = layout
|
||||
}
|
||||
|
||||
|
||||
func prepareMolecules(with carouselModel: CarouselModel?) {
|
||||
guard let newMolecules = carouselModel?.molecules else {
|
||||
numberOfPages = 0
|
||||
molecules = nil
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
numberOfPages = newMolecules.count
|
||||
molecules = newMolecules
|
||||
if carouselModel?.loop ?? false && newMolecules.count > 2 {
|
||||
@ -136,16 +136,17 @@ open class Carousel: View {
|
||||
}
|
||||
pageIndex = 0
|
||||
}
|
||||
|
||||
|
||||
/// Sets up the paging molecule
|
||||
open func setupPagingMolecule(_ molecule: CarouselPagingModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) {
|
||||
|
||||
var pagingView: (UIView & MVMCoreUIPagingProtocol)? = nil
|
||||
if let molecule = molecule {
|
||||
pagingView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(molecule, delegateObject, false) as? (UIView & MVMCoreUIPagingProtocol)
|
||||
}
|
||||
addPaging(view: pagingView, position: (CGFloat(molecule?.position ?? 20)))
|
||||
}
|
||||
|
||||
|
||||
/// Registers the cells with the collection view
|
||||
func registerCells(with carouselModel: CarouselModel, delegateObject: MVMCoreUIDelegateObject?) {
|
||||
for molecule in carouselModel.molecules {
|
||||
@ -154,19 +155,20 @@ open class Carousel: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Convenience
|
||||
/// 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
|
||||
}
|
||||
guard let className = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(molecule),
|
||||
let moleculeName = (className as? ModelMoleculeViewProtocol.Type)?.nameForReuse(molecule, delegateObject) ?? molecule.moleculeName
|
||||
else { return nil }
|
||||
|
||||
return (moleculeName, className, molecule)
|
||||
}
|
||||
|
||||
/// Sets the alignment from the string.
|
||||
open func setAlignment(with string: String?) {
|
||||
|
||||
switch string {
|
||||
case "leading":
|
||||
itemAlignment = .left
|
||||
@ -213,6 +215,7 @@ open class Carousel: View {
|
||||
}
|
||||
|
||||
open func showPeaking(_ peaking: Bool) {
|
||||
|
||||
if peaking && !UIAccessibility.isVoiceOverRunning {
|
||||
// Show overlay and arrow in peaking Cell
|
||||
let visibleItemsPaths = collectionView.indexPathsForVisibleItems.sorted { $0.row < $1.row }
|
||||
@ -231,9 +234,8 @@ open class Carousel: View {
|
||||
}
|
||||
|
||||
func setAccessiblity(_ cell: UICollectionViewCell?, index: Int) {
|
||||
guard let cell = cell else {
|
||||
return
|
||||
}
|
||||
guard let cell = cell else { return }
|
||||
|
||||
if index == currentIndex {
|
||||
cell.accessibilityElementsHidden = false
|
||||
var array = cell.accessibilityElements
|
||||
@ -269,9 +271,9 @@ extension Carousel: UICollectionViewDataSource {
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
guard let molecule = molecules?[indexPath.row],
|
||||
let moleculeInfo = getMoleculeInfo(with: molecule, delegateObject: nil) else {
|
||||
return UICollectionViewCell()
|
||||
}
|
||||
let moleculeInfo = getMoleculeInfo(with: molecule, delegateObject: nil)
|
||||
else { return UICollectionViewCell() }
|
||||
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: moleculeInfo.identifier, for: indexPath)
|
||||
if let protocolCell = cell as? MVMCoreUIMoleculeViewProtocol & ModelMoleculeViewProtocol {
|
||||
protocolCell.reset?()
|
||||
@ -286,6 +288,7 @@ extension Carousel: UICollectionViewDataSource {
|
||||
extension Carousel: UIScrollViewDelegate {
|
||||
|
||||
func goTo(_ index: Int, animated: Bool) {
|
||||
|
||||
showPeaking(false)
|
||||
setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index)
|
||||
self.currentIndex = index
|
||||
@ -297,17 +300,15 @@ extension Carousel: UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
func handleUserOnBufferCell() {
|
||||
guard loop else {
|
||||
return
|
||||
}
|
||||
|
||||
guard loop else { return }
|
||||
|
||||
let lastPageIndex = numberOfPages + 1
|
||||
let goToIndex = {(index: Int) in
|
||||
self.goTo(index, animated: false)
|
||||
self.collectionView.layoutIfNeeded()
|
||||
self.pagingView?.setPage(self.pageIndex)
|
||||
}
|
||||
|
||||
|
||||
if currentIndex < 2 {
|
||||
// If on a "buffer" last row (which is the first index), go to the real last row secretly. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking.
|
||||
goToIndex(lastPageIndex)
|
||||
@ -318,9 +319,8 @@ extension Carousel: UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
func checkForDraggingOutOfBounds(_ scrollView: UIScrollView) {
|
||||
guard loop, dragging else {
|
||||
return
|
||||
}
|
||||
guard loop, dragging else { return }
|
||||
|
||||
// Checks if the user is not paging but attempting to drag endlessly and goes out of bounds. Caps the index.
|
||||
if let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing {
|
||||
let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent)
|
||||
@ -346,24 +346,25 @@ extension Carousel: UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
|
||||
dragging = true
|
||||
showPeaking(false)
|
||||
}
|
||||
|
||||
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
|
||||
dragging = false
|
||||
targetContentOffset.pointee = scrollView.contentOffset
|
||||
|
||||
// This is for setting up smooth custom paging. (Since UICollectionView only handles paging based on collection view size and not cell size).
|
||||
guard let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else {
|
||||
return
|
||||
}
|
||||
guard let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else { return }
|
||||
|
||||
// We switch cards if we pass the velocity threshold or position threshold (currently 50%).
|
||||
let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent)
|
||||
var cellToSwipeTo = Int(scrollView.contentOffset.x/(itemWidth + separatorWidth) + 0.5)
|
||||
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
||||
let velocityThreshold: CGFloat = 1.1
|
||||
|
||||
if velocity.x > velocityThreshold {
|
||||
cellToSwipeTo = currentIndex + 1
|
||||
} else if velocity.x < -velocityThreshold {
|
||||
|
||||
@ -9,10 +9,14 @@
|
||||
import UIKit
|
||||
|
||||
@objcMembers public class CarouselModel: MoleculeModelProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
public static var identifier: String = "carousel"
|
||||
public var backgroundColor: Color?
|
||||
public var molecules: [CarouselItemModel]
|
||||
|
||||
public var moleculeName: String?
|
||||
public var spacing: Float?
|
||||
public var border: Bool?
|
||||
public var loop: Bool?
|
||||
@ -20,12 +24,20 @@ import UIKit
|
||||
public var itemWidthPercent: Float?
|
||||
public var itemAlignment: String?
|
||||
public var pagingMolecule: CarouselPagingModelProtocol?
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializer
|
||||
//--------------------------------------------------
|
||||
|
||||
public init(molecules: [CarouselItemModel]){
|
||||
self.molecules = molecules
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Keys
|
||||
//--------------------------------------------------
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case moleculeName
|
||||
case backgroundColor
|
||||
case molecules
|
||||
@ -36,32 +48,36 @@ import UIKit
|
||||
case itemWidthPercent
|
||||
case itemAlignment
|
||||
case pagingMolecule
|
||||
}
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Codec
|
||||
//--------------------------------------------------
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules)
|
||||
self.backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
||||
self.spacing = try typeContainer.decode(Float.self, forKey: .spacing)
|
||||
self.border = try typeContainer.decode(Bool.self, forKey: .border)
|
||||
self.loop = try typeContainer.decode(Bool.self, forKey: .loop)
|
||||
self.height = try typeContainer.decode(Float.self, forKey: .height)
|
||||
self.itemWidthPercent = try typeContainer.decode(Float.self, forKey: .itemWidthPercent)
|
||||
self.itemAlignment = try typeContainer.decode(String.self, forKey: .itemAlignment)
|
||||
self.pagingMolecule = try typeContainer.decodeMoleculeIfPresent(codingKey: .pagingMolecule) as? CarouselPagingModelProtocol
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules)
|
||||
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
||||
spacing = try typeContainer.decodeIfPresent(Float.self, forKey: .spacing)
|
||||
border = try typeContainer.decodeIfPresent(Bool.self, forKey: .border)
|
||||
loop = try typeContainer.decodeIfPresent(Bool.self, forKey: .loop)
|
||||
height = try typeContainer.decodeIfPresent(Float.self, forKey: .height)
|
||||
itemWidthPercent = try typeContainer.decodeIfPresent(Float.self, forKey: .itemWidthPercent)
|
||||
itemAlignment = try typeContainer.decodeIfPresent(String.self, forKey: .itemAlignment)
|
||||
pagingMolecule = try typeContainer.decodeMoleculeIfPresent(codingKey: .pagingMolecule) as? CarouselPagingModelProtocol
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encodeIfPresent(moleculeName, forKey: .moleculeName)
|
||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||
try container.encode(molecules, forKey: .molecules)
|
||||
try container.encode(spacing, forKey: .spacing)
|
||||
try container.encode(border, forKey: .border)
|
||||
try container.encode(loop, forKey: .loop)
|
||||
try container.encode(height, forKey: .height)
|
||||
try container.encode(itemWidthPercent, forKey: .itemWidthPercent)
|
||||
try container.encode(itemAlignment, forKey: .itemAlignment)
|
||||
try container.encodeIfPresent(molecules, forKey: .molecules)
|
||||
try container.encodeIfPresent(spacing, forKey: .spacing)
|
||||
try container.encodeIfPresent(border, forKey: .border)
|
||||
try container.encodeIfPresent(loop, forKey: .loop)
|
||||
try container.encodeIfPresent(height, forKey: .height)
|
||||
try container.encodeIfPresent(itemWidthPercent, forKey: .itemWidthPercent)
|
||||
try container.encodeIfPresent(itemAlignment, forKey: .itemAlignment)
|
||||
try container.encodeModelIfPresent(pagingMolecule, forKey: .pagingMolecule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,6 +94,8 @@ 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
|
||||
MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping.setObject(DigitEntryField.self, forKey: "digitTextField" as NSString)
|
||||
MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping.setObject(DateDropdownEntryField.self, forKey: "dateDropdownEntryField" as NSString)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user