From fd72f5ff9553bfef5bdf8bed7685f4487ec7a33d Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 13 Feb 2020 12:42:38 -0500 Subject: [PATCH] work in progress --- MVMCoreUI.xcodeproj/project.pbxproj | 16 +- MVMCoreUI/Atoms/Views/Arrow.swift | 24 +++ MVMCoreUI/Atoms/Views/ArrowModel.swift | 61 ++++++ .../CarouselIndicator/CarouselIndicator.swift | 183 +++++++++--------- .../CarouselIndicatorModel.swift | 21 +- .../IndicatorViews/BarsIndicatorView.swift | 156 --------------- .../IndicatorViews/NumericIndicatorView.swift | 63 ++---- .../CarouselPagingModelProtocol.swift | 3 +- .../Molecules/Items/CarouselItemModel.swift | 12 ++ MVMCoreUI/Organisms/Carousel.swift | 81 ++++---- MVMCoreUI/Organisms/CarouselModel.swift | 70 ++++--- .../OtherHandlers/MoleculeObjectMapping.swift | 2 + 12 files changed, 323 insertions(+), 369 deletions(-) create mode 100644 MVMCoreUI/Atoms/Views/Arrow.swift create mode 100644 MVMCoreUI/Atoms/Views/ArrowModel.swift delete mode 100644 MVMCoreUI/Atoms/Views/CarouselIndicator/IndicatorViews/BarsIndicatorView.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index a9d17c99..081f3f68 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -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 = ""; }; 0A14F6A423E4803A00EDF7F7 /* StackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackView.swift; sourceTree = ""; }; 0A14F6A823E8750300EDF7F7 /* CarouselIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselIndicatorModel.swift; sourceTree = ""; }; - 0A14F6B123E8C28D00EDF7F7 /* BarsIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsIndicatorView.swift; sourceTree = ""; }; 0A14F6B323E8C29700EDF7F7 /* NumericIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumericIndicatorView.swift; sourceTree = ""; }; 0A209CD223A7E2810068F8B0 /* UIStackViewAlignment+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackViewAlignment+Extension.swift"; sourceTree = ""; }; 0A21DB7E235DECC500C160A2 /* EntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryField.swift; sourceTree = ""; }; @@ -415,8 +416,11 @@ 0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryFieldModel.swift; sourceTree = ""; }; 0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryFieldModel.swift; sourceTree = ""; }; 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = ""; }; + 0A9F3DE723EDE9F200318918 /* Arrow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Arrow.swift; sourceTree = ""; }; + 0A9F3DE923EDEA1A00318918 /* ArrowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrowModel.swift; sourceTree = ""; }; 0AA33B33239813C50067DD0F /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; 0AA33B392398524F0067DD0F /* Toggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toggle.swift; sourceTree = ""; }; + 0AB2AA2223F19CFA00C6D3CF /* BarsIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarsIndicatorView.swift; sourceTree = ""; }; 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = ""; }; 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = ""; }; 0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; @@ -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 = ""; @@ -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 */, diff --git a/MVMCoreUI/Atoms/Views/Arrow.swift b/MVMCoreUI/Atoms/Views/Arrow.swift new file mode 100644 index 00000000..12179dba --- /dev/null +++ b/MVMCoreUI/Atoms/Views/Arrow.swift @@ -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 + //-------------------------------------------------- + + +} diff --git a/MVMCoreUI/Atoms/Views/ArrowModel.swift b/MVMCoreUI/Atoms/Views/ArrowModel.swift new file mode 100644 index 00000000..eff64b97 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/ArrowModel.swift @@ -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) + } +} diff --git a/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicator.swift b/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicator.swift index fbd42691..f263d7b9 100644 --- a/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicator.swift +++ b/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicator.swift @@ -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) } } diff --git a/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift b/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift index 04583fcc..074b0570 100644 --- a/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift +++ b/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift @@ -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) } diff --git a/MVMCoreUI/Atoms/Views/CarouselIndicator/IndicatorViews/BarsIndicatorView.swift b/MVMCoreUI/Atoms/Views/CarouselIndicator/IndicatorViews/BarsIndicatorView.swift deleted file mode 100644 index 5122adc4..00000000 --- a/MVMCoreUI/Atoms/Views/CarouselIndicator/IndicatorViews/BarsIndicatorView.swift +++ /dev/null @@ -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..= 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() - } -} diff --git a/MVMCoreUI/Atoms/Views/CarouselIndicator/IndicatorViews/NumericIndicatorView.swift b/MVMCoreUI/Atoms/Views/CarouselIndicator/IndicatorViews/NumericIndicatorView.swift index c4b7ae39..95ea16d1 100644 --- a/MVMCoreUI/Atoms/Views/CarouselIndicator/IndicatorViews/NumericIndicatorView.swift +++ b/MVMCoreUI/Atoms/Views/CarouselIndicator/IndicatorViews/NumericIndicatorView.swift @@ -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) } } } diff --git a/MVMCoreUI/Models/ModelProtocols/CarouselPagingModelProtocol.swift b/MVMCoreUI/Models/ModelProtocols/CarouselPagingModelProtocol.swift index 5f593d3b..94b1277a 100644 --- a/MVMCoreUI/Models/ModelProtocols/CarouselPagingModelProtocol.swift +++ b/MVMCoreUI/Models/ModelProtocols/CarouselPagingModelProtocol.swift @@ -8,6 +8,7 @@ import Foundation + public protocol CarouselPagingModelProtocol: MoleculeModelProtocol { - var position: Float? {get} + var position: Float? { get } } diff --git a/MVMCoreUI/Molecules/Items/CarouselItemModel.swift b/MVMCoreUI/Molecules/Items/CarouselItemModel.swift index f2c88e9b..996ad4e9 100644 --- a/MVMCoreUI/Molecules/Items/CarouselItemModel.swift +++ b/MVMCoreUI/Molecules/Items/CarouselItemModel.swift @@ -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) diff --git a/MVMCoreUI/Organisms/Carousel.swift b/MVMCoreUI/Organisms/Carousel.swift index 7262e122..46c6f269 100644 --- a/MVMCoreUI/Organisms/Carousel.swift +++ b/MVMCoreUI/Organisms/Carousel.swift @@ -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) { + 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 { diff --git a/MVMCoreUI/Organisms/CarouselModel.swift b/MVMCoreUI/Organisms/CarouselModel.swift index e43e6caa..c68f083a 100644 --- a/MVMCoreUI/Organisms/CarouselModel.swift +++ b/MVMCoreUI/Organisms/CarouselModel.swift @@ -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) - } + } } diff --git a/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift b/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift index 8c1a36e3..ff285e2a 100644 --- a/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift +++ b/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift @@ -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)