work in progress

This commit is contained in:
Kevin G Christiano 2020-02-13 12:42:38 -05:00
parent 4577274e08
commit fd72f5ff95
12 changed files with 323 additions and 369 deletions

View File

@ -63,7 +63,6 @@
0A14F69323E349EF00EDF7F7 /* CarouselIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F69223E349EF00EDF7F7 /* CarouselIndicator.swift */; }; 0A14F69323E349EF00EDF7F7 /* CarouselIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F69223E349EF00EDF7F7 /* CarouselIndicator.swift */; };
0A14F6A523E4803A00EDF7F7 /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F6A423E4803A00EDF7F7 /* StackView.swift */; }; 0A14F6A523E4803A00EDF7F7 /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F6A423E4803A00EDF7F7 /* StackView.swift */; };
0A14F6A923E8750300EDF7F7 /* CarouselIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F6A823E8750300EDF7F7 /* CarouselIndicatorModel.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 */; }; 0A14F6B423E8C29700EDF7F7 /* NumericIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F6B323E8C29700EDF7F7 /* NumericIndicatorView.swift */; };
0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */; }; 0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */; };
0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* EntryField.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 */; }; 0A7EF86323D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86223D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift */; };
0A7EF86523D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */; }; 0A7EF86523D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */; };
0A7EF86723D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.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 */; }; 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 */; }; 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */; };
0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */; }; 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */; };
0AE14F64238315D2005417F8 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE14F63238315D2005417F8 /* TextField.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
@ -742,7 +746,7 @@
0A14F6B723ECA7F900EDF7F7 /* IndicatorViews */ = { 0A14F6B723ECA7F900EDF7F7 /* IndicatorViews */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0A14F6B123E8C28D00EDF7F7 /* BarsIndicatorView.swift */, 0AB2AA2223F19CFA00C6D3CF /* BarsIndicatorView.swift */,
0A14F6B323E8C29700EDF7F7 /* NumericIndicatorView.swift */, 0A14F6B323E8C29700EDF7F7 /* NumericIndicatorView.swift */,
); );
path = IndicatorViews; path = IndicatorViews;
@ -1241,6 +1245,8 @@
0AA33B392398524F0067DD0F /* Toggle.swift */, 0AA33B392398524F0067DD0F /* Toggle.swift */,
D260105423CEA7DC00764D80 /* MVMCoreUISwitch+Model.swift */, D260105423CEA7DC00764D80 /* MVMCoreUISwitch+Model.swift */,
012CA99D2385A2D3003F810F /* MFView+ModelExtension.swift */, 012CA99D2385A2D3003F810F /* MFView+ModelExtension.swift */,
0A9F3DE723EDE9F200318918 /* Arrow.swift */,
0A9F3DE923EDEA1A00318918 /* ArrowModel.swift */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1688,6 +1694,7 @@
D2B18B94236214AD00A9AEDC /* NavigationController.swift in Sources */, D2B18B94236214AD00A9AEDC /* NavigationController.swift in Sources */,
D29E28DA23D21AFA00ACEA85 /* StringAndMoleculeModel.swift in Sources */, D29E28DA23D21AFA00ACEA85 /* StringAndMoleculeModel.swift in Sources */,
D282AACB2243C61700C46919 /* ButtonView.swift in Sources */, D282AACB2243C61700C46919 /* ButtonView.swift in Sources */,
0A9F3DE823EDE9F200318918 /* Arrow.swift in Sources */,
D260105D23D0BCD400764D80 /* Stack.swift in Sources */, D260105D23D0BCD400764D80 /* Stack.swift in Sources */,
0A7EF85D23D8A95600B2AAD1 /* TextEntryFieldModel.swift in Sources */, 0A7EF85D23D8A95600B2AAD1 /* TextEntryFieldModel.swift in Sources */,
D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */, D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */,
@ -1773,9 +1780,11 @@
943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */, 943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */,
D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */,
948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */, 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */,
0A9F3DEA23EDEA1A00318918 /* ArrowModel.swift in Sources */,
D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */,
D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */, D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */,
D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */,
0AB2AA2323F19CFA00C6D3CF /* BarsIndicatorView.swift in Sources */,
94C2D9A323872C110006CF46 /* LabelAttributeStrikeThroughModel.swift in Sources */, 94C2D9A323872C110006CF46 /* LabelAttributeStrikeThroughModel.swift in Sources */,
D28A838523CCCA8900DFE4FC /* ScrollerModel.swift in Sources */, D28A838523CCCA8900DFE4FC /* ScrollerModel.swift in Sources */,
D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */, D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */,
@ -1798,7 +1807,6 @@
D2B18B922361E65A00A9AEDC /* CoreUIObject.swift in Sources */, D2B18B922361E65A00A9AEDC /* CoreUIObject.swift in Sources */,
D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */,
014AA72E23C5059B006F3E93 /* StackCenteredPageTemplateModel.swift in Sources */, 014AA72E23C5059B006F3E93 /* StackCenteredPageTemplateModel.swift in Sources */,
0A14F6B223E8C28D00EDF7F7 /* BarsIndicatorView.swift in Sources */,
D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */,
D260105923D0A92900764D80 /* ContainerProtocol.swift in Sources */, D260105923D0A92900764D80 /* ContainerProtocol.swift in Sources */,
C695A69423C9909000BFB94E /* DoughnutChartModel.swift in Sources */, C695A69423C9909000BFB94E /* DoughnutChartModel.swift in Sources */,

View 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
//--------------------------------------------------
}

View 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)
}
}

View File

@ -9,13 +9,19 @@
import Foundation import Foundation
public protocol IndicatorViewProtocol { 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 isEnabled: Bool { get set }
var currentIndex: Int? { get set }
var numberOfPages: Int? { get }
} }
open class CarouselIndicator: Control { open class CarouselIndicator: Control {
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
public typealias IndicatorView = UIView & IndicatorViewProtocol
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Constraints // MARK: - Constraints
//-------------------------------------------------- //--------------------------------------------------
@ -27,8 +33,6 @@ open class CarouselIndicator: Control {
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
public typealias IndicatorView = UIView & IndicatorViewProtocol
/// The types of indicators that can appear. /// The types of indicators that can appear.
public enum IndicatorType: String { public enum IndicatorType: String {
case bar case bar
@ -38,47 +42,42 @@ open class CarouselIndicator: Control {
/// Determines interactivity and appearance of the indicator. /// Determines interactivity and appearance of the indicator.
public var indicatorType: IndicatorType = .hybrid { public var indicatorType: IndicatorType = .hybrid {
didSet { didSet { assignIndicatorView() }
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. /// The view control relative to the state of the indicator type.
private(set) var indicatorView: IndicatorView? { private(set) var indicatorView: IndicatorView? {
didSet { didSet {
topConstraint = indicatorView!.topAnchor.constraint(equalTo: topAnchor, constant: PaddingTwo) topConstraint = indicatorView?.topAnchor.constraint(equalTo: topAnchor, constant: PaddingTwo)
topConstraint?.isActive = true topConstraint?.isActive = true
bottomConstraint = bottomAnchor.constraint(equalTo: indicatorView!.bottomAnchor, constant: PaddingTwo) if let indicatorView = indicatorView {
bottomConstraint?.isActive = true 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. /// The maxmum count of pages before the indicatorView forces a numeric Indicator insead of Bar.
public var hybridThreshold: Int = 5 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. /// 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. /// 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 /// 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. /// Will hide this control if page count is 1.
public var hidesForSinglePage = false { public var hidesForSinglePage = false {
@ -97,11 +96,7 @@ open class CarouselIndicator: Control {
} else { } else {
if let stackView = indicatorView as? BarsIndicatorView { if let stackView = indicatorView as? BarsIndicatorView {
stackView.stackView.arrangedSubviews.forEach { view in stackView.stackView.arrangedSubviews.forEach { ($0 as? BarsIndicatorView)?.isEnabled = isEnabled }
// if let indicator = {
(view as? BarsIndicatorView)?.isEnabled = isEnabled
// }
}
} }
} }
} }
@ -111,25 +106,22 @@ open class CarouselIndicator: Control {
// MARK: - Computed Properties // MARK: - Computed Properties
//-------------------------------------------------- //--------------------------------------------------
/// The currently active indicator view. private(set) var previousIndex = 0
public weak var currentIndicator: IndicatorView? {
didSet {
}
}
private(set) var oldIndex = 0
private var _currentIndex = 0 private var _currentIndex = 0
public var currentIndex: Int { public var currentIndex: Int {
get { return _currentIndex } get { return _currentIndex }
set (newIndex) { set (newIndex) {
guard _currentIndex != newIndex else { return } guard _currentIndex != newIndex else { return }
oldIndex = _currentIndex
previousIndex = _currentIndex
_currentIndex = newIndex _currentIndex = newIndex
sendActions(for: .valueChanged) sendActions(for: .valueChanged)
indicatorTappedBlock?(newIndex) indicatorTouchAction?(newIndex)
indicatorView?.updateUI(oldIndex: oldIndex, newIndex: newIndex) indicatorView?.updateUI(previousIndex: previousIndex,
newIndex: newIndex,
totalCount: numberOfPages,
isAnimated: isAnimated)
} }
} }
@ -137,44 +129,46 @@ open class CarouselIndicator: Control {
public var numberOfPages: Int { public var numberOfPages: Int {
get { return _numberOfPages } get { return _numberOfPages }
set { set (newTotal) {
guard _numberOfPages != newValue else { return } guard _numberOfPages != newTotal else { return }
_numberOfPages = newValue _numberOfPages = newTotal
if hidesForSinglePage && newValue <= 1 { if hidesForSinglePage && newTotal <= 1 {
isHidden = true isHidden = true
} else { } else {
isHidden = false 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 { public var indicatorTintColor: UIColor {
get { return _indicatorTintColor } get { return _indicatorTintColor }
set { set (newColor) {
_indicatorTintColor = newValue _indicatorTintColor = newColor
if isBarIndicator(), let barsView = (indicatorView as? BarsIndicatorView)?.stackView.arrangedSubviews.isEmpty {
indicatorView = BarsIndicatorView(numberOfBars: numberOfPages)
}
(indicatorView as? BarsIndicatorView)?.stackView.arrangedSubviews.forEach { $0.backgroundColor = newValue}
} }
} }
private var _currentPageIndicatorTintColor: UIColor = .black private var _currentIndicatorColor: UIColor = .black
public var currentPageIndicatorTintColor: UIColor { /// Colors the currently selected index, unique from other indicators
get { return _currentPageIndicatorTintColor } public var currentIndicatorColor: UIColor {
set { get { return _currentIndicatorColor }
_currentPageIndicatorTintColor = newValue set (newColor) {
if ((indicatorView as? BarsIndicatorView)?.stackView.arrangedSubviews.isEmpty)! { _currentIndicatorColor = newColor
indicatorView = BarsIndicatorView(numberOfBars: numberOfPages)
}
currentIndicator?.backgroundColor = newValue
} }
} }
@ -213,45 +207,57 @@ open class CarouselIndicator: Control {
open override func setupView() { open override func setupView() {
super.setupView() super.setupView()
if indicatorView == nil { guard indicatorView == nil else { return }
assignIndicatorView() assignIndicatorView()
if let accessibleValue = MVMCoreUIUtility.hardcodedString(withKey: isSlidesAccessibile ? "MVMCoreUIPageControlslides_currentpage_index" : "MVMCoreUIPageControl_currentpage_index") { if let accessibleValue = MVMCoreUIUtility.hardcodedString(withKey: accessibilityHasSlidesInsteadOfPage ? "MVMCoreUIPageControlslides_currentpage_index" : "MVMCoreUIPageControl_currentpage_index") {
accessibilityValue = String(format: accessibleValue, currentIndex + 1, numberOfPages) 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 // MARK: - Methods
//-------------------------------------------------- //--------------------------------------------------
/// Sets the indicatorView based on the current indicatorType.
func assignIndicatorView() { func assignIndicatorView() {
switch indicatorType { switch indicatorType {
case .bar: case .bar:
indicatorView = BarsIndicatorView(numberOfBars: numberOfPages) indicatorView = BarsIndicatorView()
case .numeric: case .numeric:
indicatorView = NumericIndicatorView() indicatorView = NumericIndicatorView()
case .hybrid: 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. /// Convenience to determine if current view is displaying bars.
func isBarIndicator() -> Bool { func isBarIndicator() -> Bool {
return indicatorType != .bar && numberOfPages > hybridThreshold return indicatorType != .bar && numberOfPages > hybridThreshold
@ -266,11 +272,8 @@ open class CarouselIndicator: Control {
guard let model = model as? CarouselIndicatorModel else { return } guard let model = model as? CarouselIndicatorModel else { return }
if let type = model.type, let indicator = IndicatorType(rawValue: type) { indicatorType = IndicatorType(rawValue: model.type ?? "") ?? .hybrid
indicatorType = indicator backgroundColor = model.backgroundColor?.uiColor
}
// backgroundColor = model.backgroundColor?.uiColor
// barsColor = model.barsColor // barsColor = model.barsColor
// pageIndicatorTintColor // pageIndicatorTintColor
// currentPageIndicatorTintColor // currentPageIndicatorTintColor
@ -290,11 +293,11 @@ open class CarouselIndicator: Control {
func accessibilityAdjust(toPage index: Int) { func accessibilityAdjust(toPage index: Int) {
if (index < numberOfPages && index >= 0) || alwaysSendControlEvent { if (index < numberOfPages && index >= 0) || alwaysSendEvent {
isAnimated = false isAnimated = false
currentIndex = index currentIndex = index
sendActions(for: .valueChanged) sendActions(for: .valueChanged)
indicatorTappedBlock?(index) indicatorTouchAction?(index)
} }
} }

View File

@ -22,7 +22,19 @@ public class CarouselIndicatorModel: MoleculeModelProtocol {
public var moleculeName: String? public var moleculeName: String?
public var type: String? = "hybrid" public var type: String? = "hybrid"
public var hybridThreshold: Int = 5
public var barsColor: Color? 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 // MARK: - Keys
@ -33,6 +45,7 @@ public class CarouselIndicatorModel: MoleculeModelProtocol {
case backgroundColor case backgroundColor
case type case type
case barsColor case barsColor
case currentBarColor
} }
//-------------------------------------------------- //--------------------------------------------------
@ -41,11 +54,8 @@ public class CarouselIndicatorModel: MoleculeModelProtocol {
required public init(from decoder: Decoder) throws { required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self) let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
// if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) {
// self.state = state currentBarColor = try typeContainer.decodeIfPresent(Color.self, forKey: .currentBarColor)
// }
// 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) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
barsColor = try typeContainer.decodeIfPresent(Color.self, forKey: .barsColor) barsColor = try typeContainer.decodeIfPresent(Color.self, forKey: .barsColor)
type = try typeContainer.decodeIfPresent(String.self, forKey: .type) ?? "hybrid" type = try typeContainer.decodeIfPresent(String.self, forKey: .type) ?? "hybrid"
@ -55,6 +65,7 @@ public class CarouselIndicatorModel: MoleculeModelProtocol {
var container = encoder.container(keyedBy: CodingKeys.self) var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(barsColor, forKey: .barsColor) try container.encodeIfPresent(barsColor, forKey: .barsColor)
try container.encodeIfPresent(currentBarColor, forKey: .currentBarColor)
try container.encode(moleculeName, forKey: .moleculeName) try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(type, forKey: .type) try container.encodeIfPresent(type, forKey: .type)
} }

View File

@ -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()
}
}

View File

@ -22,24 +22,24 @@ open class NumericIndicatorView: View, IndicatorViewProtocol {
return label return label
}() }()
// Left and right arrows for Numeric indicator
open var leftArrow = MFLoadImageView()
open var rightArrow = MFLoadImageView()
open var isEnabled: Bool = true open var isEnabled: Bool = true
public var parentCarouselIndicator: CarouselIndicator? {
return superview as? CarouselIndicator
}
/// Returns the currentIndex from its parent CarouselIndicator. /// Returns the currentIndex from its parent CarouselIndicator.
public var currentIndex: Int? { public var currentIndex: Int? {
get { return (superview as? CarouselIndicator)?.currentIndex } get { return parentCarouselIndicator?.currentIndex }
set { set {
guard let newValue = newValue else { return } guard let newValue = newValue else { return }
(superview as? CarouselIndicator)?.currentIndex = newValue parentCarouselIndicator?.currentIndex = newValue
} }
} }
/// Returns the numberOfPages count from its parent CarouselIndicator. /// Returns the numberOfPages count from its parent CarouselIndicator.
public var numberOfPages: Int? { 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 } guard subviews.isEmpty else { return }
isUserInteractionEnabled = false
addSubview(titleLabel) addSubview(titleLabel)
titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
NSLayoutConstraint.constraintPinSubview(titleLabel, pinTop: true, pinBottom: true, pinLeft: false, pinRight: false) 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) addSubview(leftArrow)
NSLayoutConstraint.constraintPinView(leftArrow, heightConstraint: true, heightConstant: PaddingTwo, widthConstraint: true, widthConstant: PaddingTwo) NSLayoutConstraint.constraintPinView(leftArrow, heightConstraint: true, heightConstant: PaddingTwo, widthConstraint: true, widthConstant: PaddingTwo)
leftArrow.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true leftArrow.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
leftArrow.isUserInteractionEnabled = true
leftArrow.loadImage(withName: "peakingRightArrow", width: nil, height: nil) { [weak self] image, _, _ in let rightArrow = UIImageView(image: UIImage(named: "peakingRightArrow"))
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")
addSubview(rightArrow) addSubview(rightArrow)
NSLayoutConstraint.constraintPinView(rightArrow, heightConstraint: true, heightConstant: PaddingTwo, widthConstraint: true, widthConstant: PaddingTwo) NSLayoutConstraint.constraintPinView(rightArrow, heightConstraint: true, heightConstant: PaddingTwo, widthConstraint: true, widthConstant: PaddingTwo)
rightArrow.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true rightArrow.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
rightArrow.isUserInteractionEnabled = 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-|", NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[leftArrowView]-(padding)-[titleLabel]-(padding)-[rightArrowView]-0-|",
options: .directionLeadingToTrailing, options: .directionLeadingToTrailing,
metrics: ["padding": PaddingOne], metrics: ["padding": PaddingOne],
@ -115,36 +105,17 @@ open class NumericIndicatorView: View, IndicatorViewProtocol {
"rightArrowView": rightArrow])) "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 // 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 DispatchQueue.main.async { [weak self] in
guard let self = self else { return } guard let self = self else { return }
self.titleLabel.text = "\(newIndex)/\(self.numberOfPages ?? 0)" self.titleLabel.text = "\(newIndex)/\(totalCount)"
self.layoutIfNeeded() self.layoutIfNeeded()
(self.superview as? CarouselIndicator)?.sendActions(for: .valueChanged)
} }
} }
} }

View File

@ -8,6 +8,7 @@
import Foundation import Foundation
public protocol CarouselPagingModelProtocol: MoleculeModelProtocol { public protocol CarouselPagingModelProtocol: MoleculeModelProtocol {
var position: Float? {get} var position: Float? { get }
} }

View File

@ -10,12 +10,20 @@ import Foundation
@objcMembers public class CarouselItemModel: MoleculeContainerModel, CarouselItemModelProtocol { @objcMembers public class CarouselItemModel: MoleculeContainerModel, CarouselItemModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "carouselItem" public static var identifier: String = "carouselItem"
public var backgroundColor: Color? public var backgroundColor: Color?
public var peakingUI: Bool? public var peakingUI: Bool?
public var peakingArrowColor: Color? public var peakingArrowColor: Color?
public var moleculeName: String? public var moleculeName: String?
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case moleculeName case moleculeName
case backgroundColor case backgroundColor
@ -23,6 +31,10 @@ import Foundation
case peakingArrowColor case peakingArrowColor
} }
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws { required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self) let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
moleculeName = try typeContainer.decodeIfPresent(String.self, forKey: .moleculeName) moleculeName = try typeContainer.decodeIfPresent(String.self, forKey: .moleculeName)

View File

@ -9,7 +9,7 @@
import UIKit import UIKit
open class Carousel: View { open class Carousel: View {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
/// The current index of the collection view. Includes dummy cells when looping. /// 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. /// The index of the page, does not include dummy cells.
var pageIndex: Int { var pageIndex: Int {
get { get { return loop ? currentIndex - 2 : currentIndex }
return loop ? currentIndex - 2 : currentIndex set (newIndex) {
}
set(newIndex) {
currentIndex = loop ? newIndex + 2 : newIndex currentIndex = loop ? newIndex + 2 : newIndex
} }
} }
@ -30,7 +28,7 @@ open class Carousel: View {
/// The json for the molecules. /// The json for the molecules.
var molecules: [MoleculeModelProtocol]? var molecules: [MoleculeModelProtocol]?
/// The horizontal alignment of the cell in the collection view. Only noticeable if the itemWidthPercent is less than 100%. /// The horizontal alignment of the cell in the collection view. Only noticeable if the itemWidthPercent is less than 100%.
var itemAlignment = UICollectionView.ScrollPosition.left var itemAlignment = UICollectionView.ScrollPosition.left
@ -53,9 +51,8 @@ open class Carousel: View {
// MARK: - MVMCoreViewProtocol // MARK: - MVMCoreViewProtocol
open override func setupView() { open override func setupView() {
super.setupView() super.setupView()
guard collectionView.superview == nil else { guard collectionView.superview == nil else { return }
return
}
collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.dataSource = self collectionView.dataSource = self
collectionView.delegate = self collectionView.delegate = self
@ -73,7 +70,7 @@ open class Carousel: View {
super.updateView(size) super.updateView(size)
collectionView.collectionViewLayout.invalidateLayout() collectionView.collectionViewLayout.invalidateLayout()
showPeaking(false) 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. // 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 { DispatchQueue.main.async {
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) 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 // MARK: - MVMCoreUIMoleculeViewProtocol
public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.setWithModel(model, delegateObject, additionalData) super.setWithModel(model, delegateObject, additionalData)
guard let carouselModel = model as? CarouselModel else { return } guard let carouselModel = model as? CarouselModel else { return }
collectionView.backgroundColor = backgroundColor collectionView.backgroundColor = backgroundColor
collectionView.layer.borderColor = backgroundColor?.cgColor collectionView.layer.borderColor = backgroundColor?.cgColor
collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0 collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0
backgroundColor = .white backgroundColor = .white
registerCells(with: carouselModel, delegateObject: delegateObject) registerCells(with: carouselModel, delegateObject: delegateObject)
setupLayout(with: carouselModel) setupLayout(with: carouselModel)
prepareMolecules(with: carouselModel) prepareMolecules(with: carouselModel)
itemWidthPercent = (carouselModel.itemWidthPercent ?? 100) / 100 itemWidthPercent = (carouselModel.itemWidthPercent ?? 100) / 100
setAlignment(with: carouselModel.itemAlignment) setAlignment(with: carouselModel.itemAlignment)
if let height = carouselModel.height { if let height = carouselModel.height {
collectionViewHeight?.constant = CGFloat(height) collectionViewHeight?.constant = CGFloat(height)
collectionViewHeight?.isActive = true collectionViewHeight?.isActive = true
} }
setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject) setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject)
collectionView.reloadData() collectionView.reloadData()
} }
// MARK: - JSON Setters // MARK: - JSON Setters
/// Updates the layout being used /// Updates the layout being used
func setupLayout(with carouselModel: CarouselModel?) { func setupLayout(with carouselModel: CarouselModel?) {
let layout = UICollectionViewFlowLayout() let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal layout.scrollDirection = .horizontal
@ -116,14 +116,14 @@ open class Carousel: View {
layout.minimumInteritemSpacing = 0 layout.minimumInteritemSpacing = 0
collectionView.collectionViewLayout = layout collectionView.collectionViewLayout = layout
} }
func prepareMolecules(with carouselModel: CarouselModel?) { func prepareMolecules(with carouselModel: CarouselModel?) {
guard let newMolecules = carouselModel?.molecules else { guard let newMolecules = carouselModel?.molecules else {
numberOfPages = 0 numberOfPages = 0
molecules = nil molecules = nil
return return
} }
numberOfPages = newMolecules.count numberOfPages = newMolecules.count
molecules = newMolecules molecules = newMolecules
if carouselModel?.loop ?? false && newMolecules.count > 2 { if carouselModel?.loop ?? false && newMolecules.count > 2 {
@ -136,16 +136,17 @@ open class Carousel: View {
} }
pageIndex = 0 pageIndex = 0
} }
/// Sets up the paging molecule /// Sets up the paging molecule
open func setupPagingMolecule(_ molecule: CarouselPagingModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) { open func setupPagingMolecule(_ molecule: CarouselPagingModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) {
var pagingView: (UIView & MVMCoreUIPagingProtocol)? = nil var pagingView: (UIView & MVMCoreUIPagingProtocol)? = nil
if let molecule = molecule { if let molecule = molecule {
pagingView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(molecule, delegateObject, false) as? (UIView & MVMCoreUIPagingProtocol) pagingView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(molecule, delegateObject, false) as? (UIView & MVMCoreUIPagingProtocol)
} }
addPaging(view: pagingView, position: (CGFloat(molecule?.position ?? 20))) addPaging(view: pagingView, position: (CGFloat(molecule?.position ?? 20)))
} }
/// Registers the cells with the collection view /// Registers the cells with the collection view
func registerCells(with carouselModel: CarouselModel, delegateObject: MVMCoreUIDelegateObject?) { func registerCells(with carouselModel: CarouselModel, delegateObject: MVMCoreUIDelegateObject?) {
for molecule in carouselModel.molecules { for molecule in carouselModel.molecules {
@ -154,19 +155,20 @@ open class Carousel: View {
} }
} }
} }
// MARK: - Convenience // MARK: - Convenience
/// Returns the (identifier, class) of the molecule for the given map. /// Returns the (identifier, class) of the molecule for the given map.
func getMoleculeInfo(with molecule: MoleculeModelProtocol, delegateObject: MVMCoreUIDelegateObject?) -> (identifier: String, class: AnyClass, molecule: MoleculeModelProtocol)? { func getMoleculeInfo(with molecule: MoleculeModelProtocol, delegateObject: MVMCoreUIDelegateObject?) -> (identifier: String, class: AnyClass, molecule: MoleculeModelProtocol)? {
guard let className = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(molecule) , guard let className = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(molecule),
let moleculeName = (className as? ModelMoleculeViewProtocol.Type)?.nameForReuse(molecule, delegateObject) ?? molecule.moleculeName else { let moleculeName = (className as? ModelMoleculeViewProtocol.Type)?.nameForReuse(molecule, delegateObject) ?? molecule.moleculeName
return nil else { return nil }
}
return (moleculeName, className, molecule) return (moleculeName, className, molecule)
} }
/// Sets the alignment from the string. /// Sets the alignment from the string.
open func setAlignment(with string: String?) { open func setAlignment(with string: String?) {
switch string { switch string {
case "leading": case "leading":
itemAlignment = .left itemAlignment = .left
@ -213,6 +215,7 @@ open class Carousel: View {
} }
open func showPeaking(_ peaking: Bool) { open func showPeaking(_ peaking: Bool) {
if peaking && !UIAccessibility.isVoiceOverRunning { if peaking && !UIAccessibility.isVoiceOverRunning {
// Show overlay and arrow in peaking Cell // Show overlay and arrow in peaking Cell
let visibleItemsPaths = collectionView.indexPathsForVisibleItems.sorted { $0.row < $1.row } let visibleItemsPaths = collectionView.indexPathsForVisibleItems.sorted { $0.row < $1.row }
@ -231,9 +234,8 @@ open class Carousel: View {
} }
func setAccessiblity(_ cell: UICollectionViewCell?, index: Int) { func setAccessiblity(_ cell: UICollectionViewCell?, index: Int) {
guard let cell = cell else { guard let cell = cell else { return }
return
}
if index == currentIndex { if index == currentIndex {
cell.accessibilityElementsHidden = false cell.accessibilityElementsHidden = false
var array = cell.accessibilityElements var array = cell.accessibilityElements
@ -269,9 +271,9 @@ extension Carousel: UICollectionViewDataSource {
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let molecule = molecules?[indexPath.row], guard let molecule = molecules?[indexPath.row],
let moleculeInfo = getMoleculeInfo(with: molecule, delegateObject: nil) else { let moleculeInfo = getMoleculeInfo(with: molecule, delegateObject: nil)
return UICollectionViewCell() else { return UICollectionViewCell() }
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: moleculeInfo.identifier, for: indexPath) let cell = collectionView.dequeueReusableCell(withReuseIdentifier: moleculeInfo.identifier, for: indexPath)
if let protocolCell = cell as? MVMCoreUIMoleculeViewProtocol & ModelMoleculeViewProtocol { if let protocolCell = cell as? MVMCoreUIMoleculeViewProtocol & ModelMoleculeViewProtocol {
protocolCell.reset?() protocolCell.reset?()
@ -286,6 +288,7 @@ extension Carousel: UICollectionViewDataSource {
extension Carousel: UIScrollViewDelegate { extension Carousel: UIScrollViewDelegate {
func goTo(_ index: Int, animated: Bool) { func goTo(_ index: Int, animated: Bool) {
showPeaking(false) showPeaking(false)
setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index) setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index)
self.currentIndex = index self.currentIndex = index
@ -297,17 +300,15 @@ extension Carousel: UIScrollViewDelegate {
} }
func handleUserOnBufferCell() { func handleUserOnBufferCell() {
guard loop else { guard loop else { return }
return
}
let lastPageIndex = numberOfPages + 1 let lastPageIndex = numberOfPages + 1
let goToIndex = {(index: Int) in let goToIndex = {(index: Int) in
self.goTo(index, animated: false) self.goTo(index, animated: false)
self.collectionView.layoutIfNeeded() self.collectionView.layoutIfNeeded()
self.pagingView?.setPage(self.pageIndex) self.pagingView?.setPage(self.pageIndex)
} }
if currentIndex < 2 { 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. // 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) goToIndex(lastPageIndex)
@ -318,9 +319,8 @@ extension Carousel: UIScrollViewDelegate {
} }
func checkForDraggingOutOfBounds(_ scrollView: UIScrollView) { func checkForDraggingOutOfBounds(_ scrollView: UIScrollView) {
guard loop, dragging else { guard loop, dragging else { return }
return
}
// Checks if the user is not paging but attempting to drag endlessly and goes out of bounds. Caps the index. // 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 { if let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing {
let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent) let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent)
@ -346,24 +346,25 @@ extension Carousel: UIScrollViewDelegate {
} }
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
dragging = true dragging = true
showPeaking(false) showPeaking(false)
} }
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
dragging = false dragging = false
targetContentOffset.pointee = scrollView.contentOffset 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). // 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 { guard let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else { return }
return
}
// We switch cards if we pass the velocity threshold or position threshold (currently 50%). // We switch cards if we pass the velocity threshold or position threshold (currently 50%).
let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent) let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent)
var cellToSwipeTo = Int(scrollView.contentOffset.x/(itemWidth + separatorWidth) + 0.5) var cellToSwipeTo = Int(scrollView.contentOffset.x/(itemWidth + separatorWidth) + 0.5)
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1 let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
let velocityThreshold: CGFloat = 1.1 let velocityThreshold: CGFloat = 1.1
if velocity.x > velocityThreshold { if velocity.x > velocityThreshold {
cellToSwipeTo = currentIndex + 1 cellToSwipeTo = currentIndex + 1
} else if velocity.x < -velocityThreshold { } else if velocity.x < -velocityThreshold {

View File

@ -9,10 +9,14 @@
import UIKit import UIKit
@objcMembers public class CarouselModel: MoleculeModelProtocol { @objcMembers public class CarouselModel: MoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "carousel" public static var identifier: String = "carousel"
public var backgroundColor: Color? public var backgroundColor: Color?
public var molecules: [CarouselItemModel] public var molecules: [CarouselItemModel]
public var moleculeName: String?
public var spacing: Float? public var spacing: Float?
public var border: Bool? public var border: Bool?
public var loop: Bool? public var loop: Bool?
@ -20,12 +24,20 @@ import UIKit
public var itemWidthPercent: Float? public var itemWidthPercent: Float?
public var itemAlignment: String? public var itemAlignment: String?
public var pagingMolecule: CarouselPagingModelProtocol? public var pagingMolecule: CarouselPagingModelProtocol?
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(molecules: [CarouselItemModel]){ public init(molecules: [CarouselItemModel]){
self.molecules = molecules self.molecules = molecules
} }
private enum CodingKeys: String, CodingKey { //--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case moleculeName case moleculeName
case backgroundColor case backgroundColor
case molecules case molecules
@ -36,32 +48,36 @@ import UIKit
case itemWidthPercent case itemWidthPercent
case itemAlignment case itemAlignment
case pagingMolecule 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) let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
self.molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules) molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules)
self.backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
self.spacing = try typeContainer.decode(Float.self, forKey: .spacing) spacing = try typeContainer.decodeIfPresent(Float.self, forKey: .spacing)
self.border = try typeContainer.decode(Bool.self, forKey: .border) border = try typeContainer.decodeIfPresent(Bool.self, forKey: .border)
self.loop = try typeContainer.decode(Bool.self, forKey: .loop) loop = try typeContainer.decodeIfPresent(Bool.self, forKey: .loop)
self.height = try typeContainer.decode(Float.self, forKey: .height) height = try typeContainer.decodeIfPresent(Float.self, forKey: .height)
self.itemWidthPercent = try typeContainer.decode(Float.self, forKey: .itemWidthPercent) itemWidthPercent = try typeContainer.decodeIfPresent(Float.self, forKey: .itemWidthPercent)
self.itemAlignment = try typeContainer.decode(String.self, forKey: .itemAlignment) itemAlignment = try typeContainer.decodeIfPresent(String.self, forKey: .itemAlignment)
self.pagingMolecule = try typeContainer.decodeMoleculeIfPresent(codingKey: .pagingMolecule) as? CarouselPagingModelProtocol pagingMolecule = try typeContainer.decodeMoleculeIfPresent(codingKey: .pagingMolecule) as? CarouselPagingModelProtocol
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self) var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(moleculeName, forKey: .moleculeName) try container.encodeIfPresent(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encode(molecules, forKey: .molecules) try container.encodeIfPresent(molecules, forKey: .molecules)
try container.encode(spacing, forKey: .spacing) try container.encodeIfPresent(spacing, forKey: .spacing)
try container.encode(border, forKey: .border) try container.encodeIfPresent(border, forKey: .border)
try container.encode(loop, forKey: .loop) try container.encodeIfPresent(loop, forKey: .loop)
try container.encode(height, forKey: .height) try container.encodeIfPresent(height, forKey: .height)
try container.encode(itemWidthPercent, forKey: .itemWidthPercent) try container.encodeIfPresent(itemWidthPercent, forKey: .itemWidthPercent)
try container.encode(itemAlignment, forKey: .itemAlignment) try container.encodeIfPresent(itemAlignment, forKey: .itemAlignment)
try container.encodeModelIfPresent(pagingMolecule, forKey: .pagingMolecule) try container.encodeModelIfPresent(pagingMolecule, forKey: .pagingMolecule)
} }
} }

View File

@ -94,6 +94,8 @@ import Foundation
// Other Organisms // Other Organisms
MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: Carousel.self, viewModelClass: CarouselModel.self) MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: Carousel.self, viewModelClass: CarouselModel.self)
MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: CarouselIndicator.self, viewModelClass: CarouselIndicatorModel.self)
// TODO: Need model // TODO: Need model
MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping.setObject(DigitEntryField.self, forKey: "digitTextField" as NSString) MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping.setObject(DigitEntryField.self, forKey: "digitTextField" as NSString)
MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping.setObject(DateDropdownEntryField.self, forKey: "dateDropdownEntryField" as NSString) MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping.setObject(DateDropdownEntryField.self, forKey: "dateDropdownEntryField" as NSString)