This commit is contained in:
Subhankar Acharya 2020-05-11 11:37:21 +05:30
commit efe3e4f190
37 changed files with 1594 additions and 233 deletions

View File

@ -91,6 +91,12 @@
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 */; };
0A9D091D2433796500D2E6C0 /* BarsCarouselIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9D09172433796500D2E6C0 /* BarsCarouselIndicatorModel.swift */; };
0A9D091E2433796500D2E6C0 /* NumericCarouselIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9D09182433796500D2E6C0 /* NumericCarouselIndicatorModel.swift */; };
0A9D091F2433796500D2E6C0 /* NumericIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9D09192433796500D2E6C0 /* NumericIndicatorView.swift */; };
0A9D09202433796500D2E6C0 /* BarsIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9D091A2433796500D2E6C0 /* BarsIndicatorView.swift */; };
0A9D09212433796500D2E6C0 /* CarouselIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9D091B2433796500D2E6C0 /* CarouselIndicatorModel.swift */; };
0A9D09222433796500D2E6C0 /* CarouselIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9D091C2433796500D2E6C0 /* CarouselIndicator.swift */; };
0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA33B392398524F0067DD0F /* Toggle.swift */; }; 0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA33B392398524F0067DD0F /* Toggle.swift */; };
0AB764D124460F6300E7FE72 /* UIDatePicker+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB764D024460F6300E7FE72 /* UIDatePicker+Extension.swift */; }; 0AB764D124460F6300E7FE72 /* UIDatePicker+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB764D024460F6300E7FE72 /* UIDatePicker+Extension.swift */; };
0AB764D324460FA400E7FE72 /* UIPickerView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB764D224460FA400E7FE72 /* UIPickerView+Extension.swift */; }; 0AB764D324460FA400E7FE72 /* UIPickerView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB764D224460FA400E7FE72 /* UIPickerView+Extension.swift */; };
@ -480,6 +486,7 @@
0A6682B4243769C700AD3CA1 /* TextViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextViewModel.swift; sourceTree = "<group>"; }; 0A6682B4243769C700AD3CA1 /* TextViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextViewModel.swift; sourceTree = "<group>"; };
0A69F610241BDEA700F7231B /* RuleAnyRequiredModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleAnyRequiredModel.swift; sourceTree = "<group>"; }; 0A69F610241BDEA700F7231B /* RuleAnyRequiredModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleAnyRequiredModel.swift; sourceTree = "<group>"; };
0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDropdownEntryField.swift; sourceTree = "<group>"; }; 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDropdownEntryField.swift; sourceTree = "<group>"; };
0A7918F423F5E7EA00772FF4 /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.swift; sourceTree = "<group>"; }; 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.swift; sourceTree = "<group>"; };
0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = "<group>"; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = "<group>"; };
0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxLabel.swift; sourceTree = "<group>"; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxLabel.swift; sourceTree = "<group>"; };
@ -494,6 +501,12 @@
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>"; };
0A9D09172433796500D2E6C0 /* BarsCarouselIndicatorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarsCarouselIndicatorModel.swift; sourceTree = "<group>"; };
0A9D09182433796500D2E6C0 /* NumericCarouselIndicatorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumericCarouselIndicatorModel.swift; sourceTree = "<group>"; };
0A9D09192433796500D2E6C0 /* NumericIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumericIndicatorView.swift; sourceTree = "<group>"; };
0A9D091A2433796500D2E6C0 /* BarsIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarsIndicatorView.swift; sourceTree = "<group>"; };
0A9D091B2433796500D2E6C0 /* CarouselIndicatorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CarouselIndicatorModel.swift; sourceTree = "<group>"; };
0A9D091C2433796500D2E6C0 /* CarouselIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CarouselIndicator.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>"; };
0AB764D024460F6300E7FE72 /* UIDatePicker+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDatePicker+Extension.swift"; sourceTree = "<group>"; }; 0AB764D024460F6300E7FE72 /* UIDatePicker+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDatePicker+Extension.swift"; sourceTree = "<group>"; };
@ -891,6 +904,19 @@
path = Protocols; path = Protocols;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
0A9D09162433796500D2E6C0 /* CarouselIndicator */ = {
isa = PBXGroup;
children = (
0A9D091B2433796500D2E6C0 /* CarouselIndicatorModel.swift */,
0A9D091C2433796500D2E6C0 /* CarouselIndicator.swift */,
0A9D09172433796500D2E6C0 /* BarsCarouselIndicatorModel.swift */,
0A9D091A2433796500D2E6C0 /* BarsIndicatorView.swift */,
0A9D09182433796500D2E6C0 /* NumericCarouselIndicatorModel.swift */,
0A9D09192433796500D2E6C0 /* NumericIndicatorView.swift */,
);
path = CarouselIndicator;
sourceTree = "<group>";
};
0ABD1369237B18EE0081388D /* Views */ = { 0ABD1369237B18EE0081388D /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1012,13 +1038,6 @@
path = Label; path = Label;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
94FB5B83238D892800EB2193 /* Recovered References */ = {
isa = PBXGroup;
children = (
);
name = "Recovered References";
sourceTree = "<group>";
};
AA4FC2A323F4F69600E251DB /* RightVariable */ = { AA4FC2A323F4F69600E251DB /* RightVariable */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1350,7 +1369,6 @@
D29DF0CE21E404D4003B2FB9 /* MVMCoreUI */, D29DF0CE21E404D4003B2FB9 /* MVMCoreUI */,
D29DF0CD21E404D4003B2FB9 /* Products */, D29DF0CD21E404D4003B2FB9 /* Products */,
D29DF0E421E4F3C7003B2FB9 /* Frameworks */, D29DF0E421E4F3C7003B2FB9 /* Frameworks */,
94FB5B83238D892800EB2193 /* Recovered References */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -1570,6 +1588,7 @@
D29DF17D21E69E26003B2FB9 /* Views */ = { D29DF17D21E69E26003B2FB9 /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0A9D09162433796500D2E6C0 /* CarouselIndicator */,
9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */, 9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */,
01509D922327ECFB00EF99AA /* ProgressBar.swift */, 01509D922327ECFB00EF99AA /* ProgressBar.swift */,
9445890D2385C3F800DE9FD4 /* MultiProgressModel.swift */, 9445890D2385C3F800DE9FD4 /* MultiProgressModel.swift */,
@ -1740,6 +1759,7 @@
D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */, D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */,
D264FAA92440F97600D98315 /* CollectionView.swift */, D264FAA92440F97600D98315 /* CollectionView.swift */,
0A5D59C323AD488600EFD9E9 /* Protocols */, 0A5D59C323AD488600EFD9E9 /* Protocols */,
0A7918F423F5E7EA00772FF4 /* ImageView.swift */,
); );
path = BaseClasses; path = BaseClasses;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1926,6 +1946,7 @@
D21B7F602437C5BC00051ABF /* MoleculeStackView.swift in Sources */, D21B7F602437C5BC00051ABF /* MoleculeStackView.swift in Sources */,
0A6682A42434DB8D00AD3CA1 /* ListLeftVariableRadioButtonBodyTextModel.swift in Sources */, 0A6682A42434DB8D00AD3CA1 /* ListLeftVariableRadioButtonBodyTextModel.swift in Sources */,
AA2AD116244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift in Sources */, AA2AD116244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift in Sources */,
0A9D09202433796500D2E6C0 /* BarsIndicatorView.swift in Sources */,
D2E2A99423D8CCBC000B42E6 /* HeadlineBodyLinkModel.swift in Sources */, D2E2A99423D8CCBC000B42E6 /* HeadlineBodyLinkModel.swift in Sources */,
01004F3022721C3800991ECC /* RadioButton.swift in Sources */, 01004F3022721C3800991ECC /* RadioButton.swift in Sources */,
D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */, D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */,
@ -1985,6 +2006,7 @@
D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */,
C695A69623C990BC00BFB94E /* DoughnutChart.swift in Sources */, C695A69623C990BC00BFB94E /* DoughnutChart.swift in Sources */,
014AA72D23C5059B006F3E93 /* StackPageTemplateModel.swift in Sources */, 014AA72D23C5059B006F3E93 /* StackPageTemplateModel.swift in Sources */,
0A9D091F2433796500D2E6C0 /* NumericIndicatorView.swift in Sources */,
D260106123D0C02A00764D80 /* StackItemModelProtocol.swift in Sources */, D260106123D0C02A00764D80 /* StackItemModelProtocol.swift in Sources */,
0AE98BAF23FEF956004C5109 /* ExternalLink.swift in Sources */, 0AE98BAF23FEF956004C5109 /* ExternalLink.swift in Sources */,
012A88C4238D86E600FE3DA1 /* CarouselItemModelProtocol.swift in Sources */, 012A88C4238D86E600FE3DA1 /* CarouselItemModelProtocol.swift in Sources */,
@ -2022,9 +2044,11 @@
D2A514672213885800345BFB /* MoleculeHeaderView.swift in Sources */, D2A514672213885800345BFB /* MoleculeHeaderView.swift in Sources */,
D29E28D823D21AB800ACEA85 /* StringAndMoleculeView.swift in Sources */, D29E28D823D21AB800ACEA85 /* StringAndMoleculeView.swift in Sources */,
01EB369023609801006832FA /* MoleculeListItemModel.swift in Sources */, 01EB369023609801006832FA /* MoleculeListItemModel.swift in Sources */,
D28A838323CCBD3F00DFE4FC /* WheelModel.swift in Sources */, 0A9D09212433796500D2E6C0 /* CarouselIndicatorModel.swift in Sources */,
EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */, EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */,
D28A838323CCBD3F00DFE4FC /* WheelModel.swift in Sources */,
D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */, D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */,
0A9D091D2433796500D2E6C0 /* BarsCarouselIndicatorModel.swift in Sources */,
DBEFFA04225A829700230692 /* Label.swift in Sources */, DBEFFA04225A829700230692 /* Label.swift in Sources */,
D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */, D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */,
0A7ECC5D243CE85300C828E8 /* DoughnutChartItemModel.swift in Sources */, 0A7ECC5D243CE85300C828E8 /* DoughnutChartItemModel.swift in Sources */,
@ -2061,6 +2085,7 @@
D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */, D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */,
D22479942316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift in Sources */, D22479942316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift in Sources */,
D2B18B94236214AD00A9AEDC /* NavigationController.swift in Sources */, D2B18B94236214AD00A9AEDC /* NavigationController.swift in Sources */,
0A9D09222433796500D2E6C0 /* CarouselIndicator.swift in Sources */,
D29E28DA23D21AFA00ACEA85 /* StringAndMoleculeModel.swift in Sources */, D29E28DA23D21AFA00ACEA85 /* StringAndMoleculeModel.swift in Sources */,
D260105D23D0BCD400764D80 /* Stack.swift in Sources */, D260105D23D0BCD400764D80 /* Stack.swift in Sources */,
0A7EF85D23D8A95600B2AAD1 /* TextEntryFieldModel.swift in Sources */, 0A7EF85D23D8A95600B2AAD1 /* TextEntryFieldModel.swift in Sources */,
@ -2078,7 +2103,7 @@
0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */, 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */,
011B58F023A2AA980085F53C /* ListItemModelProtocol.swift in Sources */, 011B58F023A2AA980085F53C /* ListItemModelProtocol.swift in Sources */,
D22479962316AF6E003FCCF9 /* HeadlineBodyLink.swift in Sources */, D22479962316AF6E003FCCF9 /* HeadlineBodyLink.swift in Sources */,
8DE5BECD2456F7A200772E76 /* ListTwoColumnDropdownSelectorsModel.swift in Sources */, 8DE5BECD2456F7A200772E76 /* ListTwoColumnDropdownSelectorsModel.swift in Sources */,
0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */, 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */,
BB55B51D244482C1002001AD /* ListRightVariablePriceChangeBodyText.swift in Sources */, BB55B51D244482C1002001AD /* ListRightVariablePriceChangeBodyText.swift in Sources */,
017BEB382360C6AC0024EF95 /* RadioButtonLabel.swift in Sources */, 017BEB382360C6AC0024EF95 /* RadioButtonLabel.swift in Sources */,
@ -2111,6 +2136,7 @@
BB2C969224330F73006FF80C /* ListRightVariableTextLinkAllTextAndLinks.swift in Sources */, BB2C969224330F73006FF80C /* ListRightVariableTextLinkAllTextAndLinks.swift in Sources */,
D2D90B42240463E100DD6EC9 /* MoleculeHeaderModel.swift in Sources */, D2D90B42240463E100DD6EC9 /* MoleculeHeaderModel.swift in Sources */,
012A88B1238C880100FE3DA1 /* CarouselPagingModelProtocol.swift in Sources */, 012A88B1238C880100FE3DA1 /* CarouselPagingModelProtocol.swift in Sources */,
0A9D091E2433796500D2E6C0 /* NumericCarouselIndicatorModel.swift in Sources */,
D29DF2C921E7BFC6003B2FB9 /* MFSizeObject.m in Sources */, D29DF2C921E7BFC6003B2FB9 /* MFSizeObject.m in Sources */,
0A6682B6243769C700AD3CA1 /* TextViewModel.swift in Sources */, 0A6682B6243769C700AD3CA1 /* TextViewModel.swift in Sources */,
9445890E2385C3F800DE9FD4 /* MultiProgressModel.swift in Sources */, 9445890E2385C3F800DE9FD4 /* MultiProgressModel.swift in Sources */,

View File

@ -8,40 +8,69 @@
import UIKit import UIKit
public enum ButtonStyle: String, Codable { public typealias FacadeElements = (fill: UIColor?, text: UIColor?, border: UIColor?)
case primary
case secondary
}
public enum ButtonSize: String, Codable {
case standard
case tiny
}
public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol { public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "button" public static var identifier: String = "button"
public var backgroundColor: Color? public var backgroundColor: Color?
public var title: String public var title: String
public var action: ActionModelProtocol public var action: ActionModelProtocol
public var enabled: Bool = true public var enabled: Bool = true
public var style: ButtonStyle? public var style: Styler.Button.Style? {
public var size: ButtonSize? = .standard didSet {
public var fillColor: Color? guard let style = style else { return }
public var textColor: Color? setFacade(by: style)
public var borderColor: Color? }
}
public var size: Styler.Button.Size? = .standard
public var groupName: String = ""
public var inverted: Bool = false
public lazy var enabledColors: FacadeElements = (fill: enabled_fillColor(),
text: enabled_textColor(),
border: enabled_borderColor())
public lazy var disabledColors: FacadeElements = (fill: disabled_fillColor(),
text: disabled_textColor(),
border: disabled_borderColor())
public var enabledFillColor: Color?
public var enabledTextColor: Color?
public var enabledBorderColor: Color?
public var enabledFillColor_inverted: Color?
public var enabledTextColor_inverted: Color?
public var enabledBorderColor_inverted: Color?
public var disabledFillColor: Color? public var disabledFillColor: Color?
public var disabledTextColor: Color? public var disabledTextColor: Color?
public var disabledBorderColor: Color? public var disabledBorderColor: Color?
public var groupName: String = ""
public var disabledFillColor_inverted: Color?
public var disabledTextColor_inverted: Color?
public var disabledBorderColor_inverted: Color?
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
public func setValidity(_ valid: Bool, group: FormGroupRule) { public func setValidity(_ valid: Bool, group: FormGroupRule) {
enabled = valid enabled = valid
updateUI?() updateUI?()
} }
/// Temporary binding mechanism for the view to update on enable changes. /// Temporary binding mechanism for the view to update on enable changes.
public var updateUI: (() -> Void)? public var updateUI: ActionBlock?
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public init(with title: String, action: ActionModelProtocol) { public init(with title: String, action: ActionModelProtocol) {
self.title = title self.title = title
self.action = action self.action = action
@ -52,71 +81,185 @@ public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupW
self.action = action self.action = action
style = .secondary style = .secondary
} }
public init(primaryButtonWith title: String, action: ActionModelProtocol) { public init(primaryButtonWith title: String, action: ActionModelProtocol) {
self.title = title self.title = title
self.action = action self.action = action
style = .primary style = .primary
} }
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
public func enabled_fillColor() -> UIColor? {
return (inverted ? enabledFillColor_inverted : enabledFillColor)?.uiColor
}
public func enabled_textColor() -> UIColor? {
return (inverted ? enabledTextColor_inverted : enabledTextColor)?.uiColor
}
public func enabled_borderColor() -> UIColor? {
return (inverted ? enabledBorderColor_inverted : enabledBorderColor)?.uiColor
}
public func disabled_fillColor() -> UIColor? {
return (inverted ? disabledFillColor_inverted : disabledFillColor)?.uiColor
}
public func disabled_textColor() -> UIColor? {
return (inverted ? disabledTextColor_inverted : disabledTextColor)?.uiColor
}
public func disabled_borderColor() -> UIColor? {
return (inverted ? disabledBorderColor_inverted : disabledBorderColor)?.uiColor
}
/// Defines the default appearance for the primary style.
func setPrimaryFacade() {
if enabledFillColor == nil && enabledTextColor == nil {
enabledFillColor = Color(uiColor: .mvmBlack)
enabledTextColor = Color(uiColor: .mvmWhite)
}
if disabledFillColor == nil && disabledTextColor == nil {
disabledFillColor = Color(uiColor: .mvmCoolGray6)
disabledTextColor = Color(uiColor: .mvmWhite)
}
enabledFillColor_inverted = Color(uiColor: .mvmWhite)
enabledTextColor_inverted = Color(uiColor: .mvmBlack)
disabledFillColor_inverted = Color(uiColor: .mvmCoolGray6)
disabledTextColor_inverted = Color(uiColor: .mvmBlack)
}
/// Defines the default appearance for the Secondary style.
func setSecondaryFacade() {
if enabledTextColor == nil && enabledBorderColor == nil {
enabledTextColor = Color(uiColor: .mvmBlack)
enabledBorderColor = Color(uiColor: .mvmBlack)
}
if disabledTextColor == nil && disabledBorderColor == nil {
disabledTextColor = Color(uiColor: .mvmCoolGray6)
disabledBorderColor = Color(uiColor: .mvmCoolGray6)
}
enabledTextColor_inverted = Color(uiColor: .mvmWhite)
enabledBorderColor_inverted = Color(uiColor: .mvmWhite)
disabledTextColor_inverted = Color(uiColor: .mvmCoolGray6)
disabledBorderColor_inverted = Color(uiColor: .mvmCoolGray6)
}
public func setFacade(by style: Styler.Button.Style) {
switch style {
case .primary:
setPrimaryFacade()
case .secondary:
setSecondaryFacade()
}
}
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case moleculeName case moleculeName
case backgroundColor case backgroundColor
case title case title
case inverted
case action case action
case enabled case enabled
case style case style
case size case size
case groupName
case fillColor case fillColor
case textColor case textColor
case borderColor case borderColor
case disabledFillColor case disabledFillColor
case disabledTextColor case disabledTextColor
case disabledBorderColor case disabledBorderColor
case groupName
} }
//--------------------------------------------------
// 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)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
title = try typeContainer.decode(String.self, forKey: .title) title = try typeContainer.decode(String.self, forKey: .title)
action = try typeContainer.decodeModel(codingKey: .action) action = try typeContainer.decodeModel(codingKey: .action)
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
self.groupName = groupName if let style = try typeContainer.decodeIfPresent(Styler.Button.Style.self, forKey: .style) {
}
if let style = try typeContainer.decodeIfPresent(ButtonStyle.self, forKey: .style) {
self.style = style self.style = style
setFacade(by: style)
} }
if let size = try typeContainer.decodeIfPresent(ButtonSize.self, forKey: .size) {
if let size = try typeContainer.decodeIfPresent(Styler.Button.Size.self, forKey: .size) {
self.size = size self.size = size
} }
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
self.enabled = enabled self.enabled = enabled
} }
fillColor = try typeContainer.decodeIfPresent(Color.self, forKey: .fillColor)
textColor = try typeContainer.decodeIfPresent(Color.self, forKey: .textColor) if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) {
borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) self.inverted = inverted
disabledFillColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledFillColor) }
disabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledTextColor)
disabledBorderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledBorderColor) if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
self.groupName = groupName
}
if let enabledFillColor = try typeContainer.decodeIfPresent(Color.self, forKey: .fillColor) {
self.enabledFillColor = enabledFillColor
}
if let enabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .textColor) {
self.enabledTextColor = enabledTextColor
}
if let enabledBorderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) {
self.enabledBorderColor = enabledBorderColor
}
if let disabledFillColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledFillColor) {
self.disabledFillColor = disabledFillColor
}
if let disabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledTextColor) {
self.disabledTextColor = disabledTextColor
}
if let disabledBorderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledBorderColor) {
self.disabledBorderColor = disabledBorderColor
}
} }
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.encode(moleculeName, forKey: .moleculeName) try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(title, forKey: .title) try container.encode(title, forKey: .title)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeModel(action, forKey: .action)
try container.encode(enabled, forKey: .enabled) try container.encode(enabled, forKey: .enabled)
try container.encodeIfPresent(style, forKey: .style) try container.encode(inverted, forKey: .inverted)
try container.encodeIfPresent(size, forKey: .size) try container.encodeModel(action, forKey: .action)
try container.encodeIfPresent(fillColor, forKey: .fillColor) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(textColor, forKey: .textColor) try container.encodeIfPresent(enabledFillColor, forKey: .fillColor)
try container.encodeIfPresent(borderColor, forKey: .borderColor) try container.encodeIfPresent(enabledTextColor, forKey: .textColor)
try container.encodeIfPresent(enabledBorderColor, forKey: .borderColor)
try container.encodeIfPresent(disabledFillColor, forKey: .disabledFillColor) try container.encodeIfPresent(disabledFillColor, forKey: .disabledFillColor)
try container.encodeIfPresent(disabledTextColor, forKey: .disabledTextColor) try container.encodeIfPresent(disabledTextColor, forKey: .disabledTextColor)
try container.encodeIfPresent(disabledBorderColor, forKey: .disabledBorderColor) try container.encodeIfPresent(disabledBorderColor, forKey: .disabledBorderColor)
try container.encodeIfPresent(style, forKey: .style)
try container.encodeIfPresent(size, forKey: .size)
try container.encodeIfPresent(groupName, forKey: .groupName) try container.encodeIfPresent(groupName, forKey: .groupName)
} }
} }

View File

@ -8,80 +8,121 @@
import UIKit import UIKit
open class PillButton: Button, MVMCoreUIViewConstrainingProtocol { open class PillButton: Button, MVMCoreUIViewConstrainingProtocol {
// Used to size the button. //--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
/// Used to size the button.
var size = MVMCoreUIUtility.getWidth() var size = MVMCoreUIUtility.getWidth()
var buttonModel: ButtonModel? { var buttonModel: ButtonModel? {
get { return model as? ButtonModel } get { return model as? ButtonModel }
} }
// Need to re-style on set. /// Need to re-style on set.
open override var isEnabled: Bool { open override var isEnabled: Bool {
didSet { style() }
}
open var buttonSize: Styler.Button.Size = .standard {
didSet { didSet {
style() buttonModel?.size = buttonSize
} }
} }
private enum ButtonHeight: CGFloat { //--------------------------------------------------
case tiny = 20 // MARK: - Initializers
case standard = 42 //--------------------------------------------------
@objc public convenience init(asPrimaryButton isPrimary: Bool, makeTiny istiny: Bool) {
self.init()
buttonSize = istiny ? .tiny : .standard
isPrimary ? stylePrimary() : styleSecondary()
} }
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
public var enabledTitleColor: UIColor? {
get { return titleColor(for: .normal) }
set { setTitleColor(newValue, for: .normal) }
}
public var disabledTitleColor: UIColor? {
get { return titleColor(for: .disabled) }
set { setTitleColor(newValue, for: .disabled) }
}
public var borderColor: UIColor? {
get {
guard let currentColor = layer.borderColor else { return nil }
return UIColor(cgColor: currentColor)
}
set { layer.borderColor = newValue?.cgColor }
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
/// The primary styling for a button. Should be used for main buttons /// The primary styling for a button. Should be used for main buttons
public func stylePrimary() { public func stylePrimary() {
setTitleColor(.white, for: .normal)
setTitleColor(.white, for: .disabled) enabledTitleColor = buttonModel?.enabledColors.text ?? .mvmWhite
disabledTitleColor = buttonModel?.disabledColors.text ?? .mvmWhite
layer.borderWidth = 0 layer.borderWidth = 0
if isEnabled { backgroundColor = isEnabled ? buttonModel?.enabledColors.fill ?? .mvmBlack : buttonModel?.disabledColors.fill ?? .mvmCoolGray6
backgroundColor = .black
} else {
backgroundColor = .mvmCoolGray6
}
} }
/// The secondary styling for a button. Should be used for secondary buttons /// The secondary styling for a button. Should be used for secondary buttons
public func styleSecondary() { public func styleSecondary() {
setTitleColor(.black, for: .normal)
setTitleColor(.mvmCoolGray6, for: .disabled) enabledTitleColor = buttonModel?.enabledColors.text ?? .mvmBlack
disabledTitleColor = buttonModel?.disabledColors.text ?? .mvmCoolGray6
backgroundColor = .clear backgroundColor = .clear
layer.borderWidth = 1 layer.borderWidth = 1
if isEnabled { borderColor = isEnabled ? buttonModel?.enabledColors.border ?? .mvmBlack : buttonModel?.disabledColors.border ?? .mvmCoolGray6
layer.borderColor = UIColor.black.cgColor
} else {
layer.borderColor = UIColor.mvmCoolGray6.cgColor
}
} }
/// Styles the button based on the model style /// Styles the button based on the model style
private func style() { private func style() {
switch buttonModel?.style { switch buttonModel?.style {
case .secondary: case .secondary:
styleSecondary() styleSecondary()
default: default:
stylePrimary() stylePrimary()
} }
if let titleColor = buttonModel?.textColor {
setTitleColor(titleColor.uiColor, for: .normal) if let titleColor = buttonModel?.enabledColors.text {
enabledTitleColor = titleColor
} }
if let disabledTitleColor = buttonModel?.disabledTextColor {
setTitleColor(disabledTitleColor.uiColor, for: .disabled) if let disabledTitleColor = buttonModel?.disabledColors.text {
self.disabledTitleColor = disabledTitleColor
} }
if isEnabled { if isEnabled {
if let fillColor = buttonModel?.fillColor { if let fillColor = buttonModel?.enabledColors.fill {
backgroundColor = fillColor.uiColor backgroundColor = fillColor
} }
if let borderColor = buttonModel?.borderColor {
if let borderColor = buttonModel?.enabledColors.border {
layer.borderWidth = 1 layer.borderWidth = 1
layer.borderColor = borderColor.cgColor self.borderColor = borderColor
} }
} else { } else {
if let fillColor = buttonModel?.disabledFillColor { if let fillColor = buttonModel?.disabledColors.fill {
backgroundColor = fillColor.uiColor backgroundColor = fillColor
} }
if let borderColor = buttonModel?.disabledBorderColor {
if let borderColor = buttonModel?.disabledColors.border {
layer.borderWidth = 1 layer.borderWidth = 1
layer.borderColor = borderColor.cgColor self.borderColor = borderColor
} }
} }
} }
@ -91,41 +132,58 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol {
} }
private func getHeight() -> CGFloat { private func getHeight() -> CGFloat {
PillButton.getHeight(for: buttonModel?.size, size: size) PillButton.getHeight(for: buttonSize, size: size)
} }
public static func getHeight(for buttonSize: ButtonSize?, size: CGFloat) -> CGFloat { public static func getHeight(for buttonSize: Styler.Button.Size?, size: CGFloat) -> CGFloat {
switch buttonSize { switch buttonSize {
case .tiny: case .tiny:
return MFSizeObject(standardSize: ButtonHeight.tiny.rawValue, standardiPadPortraitSize: 34, iPadProLandscapeSize: 38)?.getValueBased(onSize: size) ?? ButtonHeight.tiny.rawValue let tinyHeight = Styler.Button.Size.tiny.getHeight()
return MFSizeObject(standardSize: tinyHeight,
standardiPadPortraitSize: 34,
iPadProLandscapeSize: 38)?.getValueBased(onSize: size) ?? tinyHeight
default: default:
return MFSizeObject(standardSize: ButtonHeight.standard.rawValue, standardiPadPortraitSize: 46, iPadProLandscapeSize: 50)?.getValueBased(onSize: size) ?? ButtonHeight.standard.rawValue let standardHeight = Styler.Button.Size.standard.getHeight()
return MFSizeObject(standardSize: standardHeight,
standardiPadPortraitSize: 46,
iPadProLandscapeSize: 50)?.getValueBased(onSize: size) ?? standardHeight
} }
} }
private func getMinimumWidth() -> CGFloat { private func getMinimumWidth() -> CGFloat {
switch buttonModel?.size {
switch buttonSize {
case .tiny: case .tiny:
return MFSizeObject(standardSize: 49.0, standardiPadPortraitSize: 90.0, iPadProLandscapeSize: 135.0)?.getValueBased(onSize: size) ?? 49.0 return MFSizeObject(standardSize: 49,
default: standardiPadPortraitSize: 90,
return 151.0 iPadProLandscapeSize: 135)?.getValueBased(onSize: size) ?? 49
default: return 151
} }
} }
open override var intrinsicContentSize: CGSize { open override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize let size = super.intrinsicContentSize
let width = size.width + (2 * getInnerPadding()) let width = size.width + (2 * getInnerPadding())
return CGSize(width: max(width, getMinimumWidth()), height: getHeight()) return CGSize(width: max(width, getMinimumWidth()), height: getHeight())
} }
// MARK: - MoleculeViewProtocol //--------------------------------------------------
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
// The button will get styled in the enable check in super. // The button will get styled in the enable check in super.
super.set(with: model, delegateObject, additionalData) super.set(with: model, delegateObject, additionalData)
guard let model = model as? ButtonModel else { return } guard let model = model as? ButtonModel else { return }
setTitle(model.title, for: .normal) setTitle(model.title, for: .normal)
if let size = model.size {
buttonSize = size
}
model.updateUI = { [weak self] in model.updateUI = { [weak self] in
MVMCoreDispatchUtility.performBlock(onMainThread: { MVMCoreDispatchUtility.performBlock(onMainThread: {
self?.enableField(model.enabled) self?.enableField(model.enabled)
@ -134,39 +192,46 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol {
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
} }
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
PillButton.getHeight(for: (model as? ButtonModel)?.size, size: MVMCoreUIUtility.getWidth()) PillButton.getHeight(for: (model as? ButtonModel)?.size, size: MVMCoreUIUtility.getWidth())
} }
// MARK: - MVMCoreViewProtocol
open override func updateView(_ size: CGFloat) { open override func updateView(_ size: CGFloat) {
super.updateView(size) super.updateView(size)
self.size = size self.size = size
invalidateIntrinsicContentSize() invalidateIntrinsicContentSize()
switch buttonModel?.size {
switch buttonSize {
case .tiny: case .tiny:
titleLabel?.font = MFFonts.mfFont75Bd(11 * (intrinsicContentSize.height / ButtonHeight.tiny.rawValue)) titleLabel?.font = MFFonts.mfFont75Bd(11 * (intrinsicContentSize.height / Styler.Button.Size.tiny.getHeight()))
default: default:
titleLabel?.font = MFFonts.mfFont75Bd(13 * (intrinsicContentSize.height / ButtonHeight.standard.rawValue)) titleLabel?.font = MFFonts.mfFont75Bd(13 * (intrinsicContentSize.height / Styler.Button.Size.standard.getHeight()))
} }
layer.cornerRadius = getInnerPadding() layer.cornerRadius = getInnerPadding()
} }
open override func setupView() { open override func setupView() {
super.setupView() super.setupView()
titleLabel?.numberOfLines = 1 titleLabel?.numberOfLines = 1
titleLabel?.lineBreakMode = .byTruncatingTail titleLabel?.lineBreakMode = .byTruncatingTail
titleLabel?.textAlignment = .center titleLabel?.textAlignment = .center
contentHorizontalAlignment = .center contentHorizontalAlignment = .center
stylePrimary() stylePrimary()
} }
//--------------------------------------------------
// MARK: - MVMCoreUIViewConstrainingProtocol // MARK: - MVMCoreUIViewConstrainingProtocol
//--------------------------------------------------
open func horizontalAlignment() -> UIStackView.Alignment { open func horizontalAlignment() -> UIStackView.Alignment {
return .center return .center
} }
public func enableField(_ enable: Bool) { public func enableField(_ enable: Bool) {
isEnabled = enable isEnabled = enable
} }

View File

@ -8,6 +8,7 @@
import UIKit import UIKit
open class Arrow: View { open class Arrow: View {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
@ -19,6 +20,41 @@ open class Arrow: View {
return model as? ArrowModel return model as? ArrowModel
} }
public var direction: ArrowModel.Direction {
get { return ArrowModel.Direction(rawValue: degrees) ?? .right}
set { degrees = newValue.rawValue }
}
open var isEnabled: Bool = true {
didSet {
arrowModel?.enabled = isEnabled
isUserInteractionEnabled = isEnabled
if isEnabled != oldValue {
setNeedsDisplay()
}
}
}
open var disabledColor: UIColor {
get { return arrowModel?.disabledColor.uiColor ?? .mvmCoolGray3 }
set { arrowModel?.disabledColor = Color(uiColor: newValue) }
}
open var color: UIColor {
get { return arrowModel?.color.uiColor ?? .mvmBlack }
set { arrowModel?.color = Color(uiColor: newValue) }
}
open var degrees: Float {
get { return arrowModel?.degrees ?? 0 }
set { arrowModel?.degrees = newValue }
}
open var lineWidth: CGFloat {
get { return arrowModel?.lineWidth ?? 1 }
set { arrowModel?.lineWidth = newValue }
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Constraints // MARK: - Constraints
//-------------------------------------------------- //--------------------------------------------------
@ -34,6 +70,18 @@ open class Arrow: View {
widthConstraint?.isActive = true widthConstraint?.isActive = true
} }
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public override init(frame: CGRect) {
super.init(frame: frame)
}
public required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Lifecycle // MARK: - Lifecycle
//-------------------------------------------------- //--------------------------------------------------
@ -51,33 +99,32 @@ open class Arrow: View {
// MARK: - Drawing // MARK: - Drawing
//-------------------------------------------------- //--------------------------------------------------
/// Draws the arrow pointing to the right and then rotates the arrow x degrees counter-clockwise.
open override func draw(_ rect: CGRect) { open override func draw(_ rect: CGRect) {
super.draw(rect) super.draw(rect)
arrowLayer.transform = CATransform3DIdentity arrowLayer.transform = CATransform3DIdentity
drawShapeLayer() drawShapeLayer()
if let degrees = arrowModel?.degrees { let radians = CGFloat(degrees * Float.pi / 180)
let radians = CGFloat(degrees * Float.pi / 180) arrowLayer.transform = CATransform3DMakeRotation(-radians, 0, 0, 1)
arrowLayer.transform = CATransform3DMakeRotation(-radians, 0.0, 0.0, 1.0)
}
} }
private func drawShapeLayer() { private func drawShapeLayer() {
arrowLayer.frame = bounds arrowLayer.frame = bounds
arrowLayer.strokeColor = arrowModel?.color.cgColor arrowLayer.strokeColor = isEnabled ? color.cgColor : disabledColor.cgColor
arrowLayer.fillColor = UIColor.clear.cgColor arrowLayer.fillColor = UIColor.clear.cgColor
arrowLayer.path = arrowPath() arrowLayer.path = arrowPath()
arrowLayer.lineJoin = .miter arrowLayer.lineJoin = .miter
arrowLayer.lineCap = .butt arrowLayer.lineCap = .butt
arrowLayer.lineWidth = arrowModel?.lineWidth ?? 1 arrowLayer.lineWidth = lineWidth
} }
private func arrowPath() -> CGPath { private func arrowPath() -> CGPath {
let length = max(bounds.size.height, bounds.size.width) let length = max(bounds.size.height, bounds.size.width)
let inset = (arrowModel?.lineWidth ?? 1) / 2 let inset = lineWidth / 2
let midLength = length / 2 let midLength = length / 2
var startPoint = CGPoint(x: midLength, y: inset) var startPoint = CGPoint(x: midLength, y: inset)
@ -97,4 +144,16 @@ open class Arrow: View {
return bezierPath.cgPath return bezierPath.cgPath
} }
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? ArrowModel else { return }
isEnabled = model.enabled
}
} }

View File

@ -8,20 +8,47 @@
import UIKit import UIKit
open class ArrowModel: MoleculeModelProtocol { open class ArrowModel: MoleculeModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
public static var identifier: String = "arrow" public static var identifier: String {
public var backgroundColor: Color? return "arrow"
}
public var moleculeName: String?
public var backgroundColor: Color?
public var disabledColor: Color = Color(uiColor: .mvmCoolGray3)
public var color: Color = Color(uiColor: .mvmBlack) public var color: Color = Color(uiColor: .mvmBlack)
public var degrees: Float = 0 public var degrees: Float = 0
public var lineWidth: CGFloat = 1 public var lineWidth: CGFloat = 1
public var height: CGFloat = 12 public var height: CGFloat = 12
public var width: CGFloat = 12 public var width: CGFloat = 12
public var enabled: Bool = true
//--------------------------------------------------
// MARK: - Enum
//--------------------------------------------------
/// Conveniece for readability of arrow pointing direction.
public enum Direction: Float {
case right = 0
case upperRight = 45
case up = 90
case upperLeft = 135
case left = 180
case bottomLeft = 225
case down = 270
case bottomRight = 315
}
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init() { }
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Keys // MARK: - Keys
@ -30,12 +57,13 @@ open class ArrowModel: MoleculeModelProtocol {
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case moleculeName case moleculeName
case backgroundColor case backgroundColor
case disabledColor
case color case color
case degrees case degrees
case size
case lineWidth case lineWidth
case height case height
case width case width
case enabled
} }
//-------------------------------------------------- //--------------------------------------------------
@ -44,12 +72,21 @@ open class ArrowModel: 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)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
if let disabledColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledColor) {
self.disabledColor = disabledColor
}
if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .color) { if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .color) {
self.color = color self.color = color
} }
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
self.enabled = enabled
}
if let degrees = try typeContainer.decodeIfPresent(Float.self, forKey: .degrees) { if let degrees = try typeContainer.decodeIfPresent(Float.self, forKey: .degrees) {
self.degrees = degrees self.degrees = degrees
} }
@ -59,7 +96,7 @@ open class ArrowModel: MoleculeModelProtocol {
} }
if let height = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .height) { if let height = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .height) {
self.lineWidth = height self.height = height
} }
if let width = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .width) { if let width = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .width) {
@ -69,12 +106,14 @@ open class ArrowModel: MoleculeModelProtocol {
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.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(disabledColor, forKey: .disabledColor)
try container.encode(color, forKey: .color) try container.encode(color, forKey: .color)
try container.encode(degrees, forKey: .degrees) try container.encode(degrees, forKey: .degrees)
try container.encodeIfPresent(backgroundColor, forKey: .lineWidth) try container.encode(lineWidth, forKey: .lineWidth)
try container.encode(width, forKey: .width) try container.encode(width, forKey: .width)
try container.encode(height, forKey: .height) try container.encode(height, forKey: .height)
try container.encode(enabled, forKey: .enabled)
} }
} }

View File

@ -0,0 +1,50 @@
//
// BarsCarouselIndicatorModel.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 3/3/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import UIKit
open class BarsCarouselIndicatorModel: CarouselIndicatorModel {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public class override var identifier: String {
return "barsCarouselIndicator"
}
public var currentIndicatorColor: Color = Color(uiColor: .mvmBlack)
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case currentIndicatorColor
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
public required init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
if let currentIndicatorColor = try typeContainer.decodeIfPresent(Color.self, forKey: .currentIndicatorColor) {
self.currentIndicatorColor = currentIndicatorColor
}
try super.init(from: decoder)
}
public override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(currentIndicatorColor, forKey: .currentIndicatorColor)
try super.encode(to: encoder)
}
}

View File

@ -0,0 +1,177 @@
//
// BarIndicatorView.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 2/3/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import UIKit
open class BarsIndicatorView: CarouselIndicator {
//--------------------------------------------------
// MARK: - Stored Properties
//--------------------------------------------------
public let stackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.isAccessibilityElement = false
stackView.axis = .horizontal
stackView.alignment = .bottom
stackView.distribution = .equalSpacing
stackView.spacing = 6
return stackView
}()
public var barReferences: [(view: View, constraint: NSLayoutConstraint)] = []
// Dimensions are based on InVision Design Guidelines.
public static let indicatorBarWidth: CGFloat = 24
public static let indicatorBarHeight: (selected: CGFloat, unselected: CGFloat) = (selected: 4, unselected: 1)
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
/// Convenience to access the model.
public var barsCarouselIndicatorModel: BarsCarouselIndicatorModel? {
return model as? BarsCarouselIndicatorModel
}
open override var isEnabled: Bool {
didSet {
for (i, bar) in barReferences.enumerated() {
if i == currentIndex {
bar.view.backgroundColor = isEnabled ? currentIndicatorColor : disabledIndicatorColor
} else {
bar.view.backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor
}
}
}
}
/// Colors the currently selected index, unique from other indicators
public var currentIndicatorColor: UIColor {
get { return barsCarouselIndicatorModel?.currentIndicatorColor.uiColor ?? indicatorColor }
set (newColor) {
barsCarouselIndicatorModel?.currentIndicatorColor = Color(uiColor: newColor)
if isEnabled && !barReferences.isEmpty {
barReferences[currentIndex].view.backgroundColor = newColor
}
}
}
public override var indicatorColor: UIColor {
get { return super.indicatorColor }
set (newColor) {
super.indicatorColor = newColor
if isEnabled {
for (i, barTuple) in barReferences.enumerated() {
barTuple.view.backgroundColor = i == currentIndex ? currentIndicatorColor : newColor
}
}
}
}
//--------------------------------------------------
// MARK: - Setup
//--------------------------------------------------
open override func setupView() {
super.setupView()
addSubview(stackView)
isUserInteractionEnabled = false
isAccessibilityElement = false
NSLayoutConstraint.activate([
stackView.heightAnchor.constraint(equalToConstant: 4),
heightAnchor.constraint(equalTo: stackView.heightAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.topAnchor.constraint(equalTo: topAnchor),
bottomAnchor.constraint(equalTo: stackView.bottomAnchor),
trailingAnchor.constraint(equalTo: stackView.trailingAnchor)
])
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
func generateBars() {
var bars = [(View, NSLayoutConstraint)]()
let ordinalFormatter = NumberFormatter()
ordinalFormatter.numberStyle = .ordinal
for i in 0..<numberOfPages {
let bar = View()
bar.accessibilityTraits = .button
bar.isAccessibilityElement = true
if let accessibleValueFormat = accessibilityValueFormat, let accessibleIndex = ordinalFormatter.string(from: NSNumber(value: i + 1)) {
bar.accessibilityLabel = String(format: accessibleValueFormat, accessibleIndex, numberOfPages)
}
bar.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccTabHint")
bar.widthAnchor.constraint(equalToConstant: BarsIndicatorView.indicatorBarWidth).isActive = true
bar.backgroundColor = isEnabled ? (i == currentIndex ? currentIndicatorColor : indicatorColor) : disabledIndicatorColor
let barHeight = i == currentIndex ? BarsIndicatorView.indicatorBarHeight.selected : BarsIndicatorView.indicatorBarHeight.unselected
let heightConstraint = bar.heightAnchor.constraint(equalToConstant: barHeight)
heightConstraint.isActive = true
stackView.addArrangedSubview(bar)
bars.append((bar, heightConstraint))
}
accessibilityElements = stackView.arrangedSubviews
barReferences = bars
}
public override func assessTouchOf(_ touchPoint_X: CGFloat) {
currentIndex = barReferences.firstIndex { $0.0.frame.maxX >= touchPoint_X && $0.0.frame.minX <= touchPoint_X } ?? 0
performAction()
}
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? BarsCarouselIndicatorModel else { return }
currentIndicatorColor = model.currentIndicatorColor.uiColor
}
//--------------------------------------------------
// MARK: - IndicatorViewProtocol
//--------------------------------------------------
public override func reset() {
super.reset()
barReferences.forEach { $0.view.removeFromSuperview() }
barReferences = []
}
public override func updateUI(previousIndex: Int, newIndex: Int, totalCount: Int, isAnimated: Bool) {
guard newIndex < totalCount else { return }
guard !barReferences.isEmpty else {
generateBars()
return
}
let expression = {
self.barReferences[previousIndex].view.backgroundColor = self.isEnabled ? self.indicatorColor : self.disabledIndicatorColor
self.barReferences[newIndex].view.backgroundColor = self.isEnabled ? self.currentIndicatorColor : self.disabledIndicatorColor
self.barReferences[previousIndex].constraint.constant = BarsIndicatorView.indicatorBarHeight.unselected
self.barReferences[newIndex].constraint.constant = BarsIndicatorView.indicatorBarHeight.selected
self.layoutIfNeeded()
}
isAnimated ? UIView.animate(withDuration: 0.3) { expression() } : expression()
}
}

View File

@ -0,0 +1,245 @@
//
// CarouselIndicator.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 1/30/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
open class CarouselIndicator: Control, CarouselPageControlProtocol {
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
public var topConstraint: NSLayoutConstraint?
public var bottomConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var uiGestures: Set<UIGestureRecognizer> = []
/// Convenience to access the model.
public var carouselIndicatorModel: CarouselIndicatorModel? {
return model as? CarouselIndicatorModel
}
/// Set this closure to perform an action when a different indicator was selected.
/// Passes through oldIndex and newIndex, respectively.
public var indicatorTouchAction: ((CarouselPageControlProtocol) -> ())?
open override var isEnabled: Bool {
didSet { isUserInteractionEnabled = isEnabled }
}
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
private(set) var previousIndex = 0
public var currentIndex: Int {
get { return carouselIndicatorModel?.currentIndex ?? 0 }
set (newIndex) {
previousIndex = currentIndex
carouselIndicatorModel?.currentIndex = newIndex
updateUI(previousIndex: previousIndex,
newIndex: newIndex,
totalCount: numberOfPages,
isAnimated: carouselIndicatorModel?.animated ?? true)
}
}
/// Holds the total number of pages displayed by the carousel.
/// Updating this property will potentially update the UI.
public var numberOfPages: Int {
get { return carouselIndicatorModel?.numberOfPages ?? 0 }
set (newTotal) {
guard numberOfPages != newTotal else { return }
carouselIndicatorModel?.numberOfPages = newTotal
reset()
isHidden = (carouselIndicatorModel?.hidesForSinglePage ?? false) && newTotal <= 1
updateUI(previousIndex: previousIndex,
newIndex: currentIndex,
totalCount: newTotal,
isAnimated: carouselIndicatorModel?.animated ?? true)
}
}
public var disabledIndicatorColor: UIColor {
get { return carouselIndicatorModel?.disabledIndicatorColor.uiColor ?? .mvmCoolGray3 }
set { carouselIndicatorModel?.disabledIndicatorColor = Color(uiColor: newValue) }
}
public var indicatorColor: UIColor {
get { return carouselIndicatorModel?.indicatorColor.uiColor ?? .mvmBlack }
set { carouselIndicatorModel?.indicatorColor = Color(uiColor: newValue) }
}
var accessibilityValueFormat: String? {
return MVMCoreUIUtility.hardcodedString(withKey: (carouselIndicatorModel?.accessibilityHasSlidesInsteadOfPage ?? false) ? "MVMCoreUIPageControlslides_currentpage_index" : "MVMCoreUIPageControl_currentpage_index")
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience override init() {
self.init(frame: .zero)
}
required public init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func setupView() {
super.setupView()
isAccessibilityElement = true
accessibilityTraits = .adjustable
setupGestures()
}
//--------------------------------------------------
// MARK: - UITouch
//--------------------------------------------------
private func setupGestures() {
let tap = UITapGestureRecognizer(target: self, action: #selector(indicatorTapped))
let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(swipeLeft))
let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(swipeRight))
leftSwipe.direction = .left
rightSwipe.direction = .right
addGestureRecognizer(tap)
addGestureRecognizer(leftSwipe)
addGestureRecognizer(rightSwipe)
uiGestures.insert(tap)
uiGestures.insert(leftSwipe)
uiGestures.insert(rightSwipe)
}
func incrementCurrentIndex() {
currentIndex = (currentIndex + 1) % numberOfPages
performAction()
}
func decrementCurrentIndex() {
let newIndex = currentIndex - 1
currentIndex = newIndex < 0 ? numberOfPages - 1 : newIndex
performAction()
}
/// Increments the currentIndex value.
@objc func swipeLeft() {
incrementCurrentIndex()
}
/// Decrement the currentIndex value
@objc func swipeRight() {
decrementCurrentIndex()
}
/// Handles tap logic for Indicator
@objc func indicatorTapped(_ tapGesture: UITapGestureRecognizer?) {
let touchPoint = tapGesture?.location(in: self)
let touchPoint_X = touchPoint?.x ?? 0.0
assessTouchOf(touchPoint_X)
}
func assessTouchOf(_ touchPoint_X: CGFloat) { }
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
open func updateUI(previousIndex: Int, newIndex: Int, totalCount: Int, isAnimated: Bool) { }
public func performAction() {
sendActions(for: .valueChanged)
indicatorTouchAction?(self)
}
public func scrollViewDidScroll(_ collectionView: UICollectionView) { }
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? CarouselIndicatorModel else { return }
indicatorColor = model.indicatorColor.uiColor
disabledIndicatorColor = model.disabledIndicatorColor.uiColor
currentIndex = model.currentIndex
isEnabled = model.enabled
formatAccessibilityValue(index: currentIndex + 1, total: numberOfPages)
}
//--------------------------------------------------
// MARK: - Accessibility
//--------------------------------------------------
open override func accessibilityIncrement() {
adjustAccessibility(toPage: currentIndex + 1)
}
open override func accessibilityDecrement() {
adjustAccessibility(toPage: currentIndex - 1)
}
func formatAccessibilityValue(index: Int, total: Int) {
let ordinalFormatter = NumberFormatter()
ordinalFormatter.numberStyle = .ordinal
guard let accessibleFormat = accessibilityValueFormat,
let accessibleIndex = ordinalFormatter.string(from: NSNumber(value: index))
else { return }
accessibilityValue = String(format: accessibleFormat, accessibleIndex, total)
}
func adjustAccessibility(toPage index: Int) {
formatAccessibilityValue(index: index, total: numberOfPages)
if (index < numberOfPages && index >= 0) || carouselIndicatorModel?.alwaysSendAction ?? false {
carouselIndicatorModel?.animated = false
previousIndex = currentIndex
currentIndex = index
performAction()
}
}
func setTopBottomSpace(constant: CGFloat) {
bottomConstraint?.constant = constant
topConstraint?.constant = constant
}
}

View File

@ -0,0 +1,119 @@
//
// CarouselIndicatorModel.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 2/3/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public class var identifier: String {
return ""
}
public var backgroundColor: Color?
public var moleculeName: String?
public var numberOfPages: Int = 0
/// Sets the current Index to focus on.
public var currentIndex: Int = 0
public var animated: Bool = true
public var hidesForSinglePage: Bool = 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 accessibilityHasSlidesInsteadOfPage: Bool = false
public var enabled: Bool = true
public var disabledIndicatorColor: Color = Color(uiColor: .mvmCoolGray3)
public var indicatorColor: Color = Color(uiColor: .mvmBlack)
public var position: Float?
/// Allows sendActions() to trigger even if index is already at min/max index.
public var alwaysSendAction = false
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case moleculeName
case backgroundColor
case currentIndex
case numberOfPages
case alwaysSendAction
case animated
case hidesForSinglePage
case accessibilityHasSlidesInsteadOfPage
case enabled
case disabledIndicatorColor
case indicatorColor
case position
}
//--------------------------------------------------
// 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)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
if let currentIndex = try typeContainer.decodeIfPresent(Int.self, forKey: .currentIndex) {
self.currentIndex = currentIndex
}
if let alwaysSendAction = try typeContainer.decodeIfPresent(Bool.self, forKey: .alwaysSendAction) {
self.alwaysSendAction = alwaysSendAction
}
if let position = try typeContainer.decodeIfPresent(Float.self, forKey: .position) {
self.position = position
}
if let animated = try typeContainer.decodeIfPresent(Bool.self, forKey: .animated) {
self.animated = animated
}
if let hidesForSinglePage = try typeContainer.decodeIfPresent(Bool.self, forKey: .hidesForSinglePage) {
self.hidesForSinglePage = hidesForSinglePage
}
if let accessibilityHasSlidesInsteadOfPage = try typeContainer.decodeIfPresent(Bool.self, forKey: .accessibilityHasSlidesInsteadOfPage) {
self.accessibilityHasSlidesInsteadOfPage = accessibilityHasSlidesInsteadOfPage
}
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
self.enabled = enabled
}
if let disabledIndicatorColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledIndicatorColor) {
self.disabledIndicatorColor = disabledIndicatorColor
}
if let indicatorColor = try typeContainer.decodeIfPresent(Color.self, forKey: .indicatorColor) {
self.indicatorColor = indicatorColor
}
}
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(numberOfPages, forKey: .numberOfPages)
try container.encode(currentIndex, forKey: .currentIndex)
try container.encode(alwaysSendAction, forKey: .alwaysSendAction)
try container.encode(animated, forKey: .animated)
try container.encode(hidesForSinglePage, forKey: .hidesForSinglePage)
try container.encode(accessibilityHasSlidesInsteadOfPage, forKey: .accessibilityHasSlidesInsteadOfPage)
try container.encode(enabled, forKey: .enabled)
try container.encode(disabledIndicatorColor, forKey: .disabledIndicatorColor)
try container.encode(indicatorColor, forKey: .indicatorColor)
try container.encodeIfPresent(position, forKey: .position)
}
}

View File

@ -0,0 +1,20 @@
//
// NumericCarouselIndicatorModel.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 3/3/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import UIKit
open class NumericCarouselIndicatorModel: CarouselIndicatorModel {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public class override var identifier: String {
return "numericCarouselIndicator"
}
}

View File

@ -0,0 +1,126 @@
//
// NumericIndicatorView.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 2/3/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import UIKit
open class NumericIndicatorView: CarouselIndicator {
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
/// Text to display the current count of total pages for viewing.
open var pageCount: Label = {
let label = Label.commonLabelB2(true)
label.isAccessibilityElement = false
label.setContentCompressionResistancePriority(.required, for: .vertical)
label.textAlignment = .center
return label
}()
let leftArrow: Arrow = {
let arrow = Arrow(model: ArrowModel(), nil, nil)
arrow.isAccessibilityElement = false
arrow.direction = .left
arrow.pinHeightAndWidth()
return arrow
}()
let rightArrow: Arrow = {
let arrow = Arrow(model: ArrowModel(), nil, nil)
arrow.pinHeightAndWidth()
return arrow
}()
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
open override var isEnabled: Bool {
didSet { setViewColor(isEnabled ? indicatorColor : disabledIndicatorColor) }
}
/// Sets the color for pageCount text, left arrow and right arrow.
public override var indicatorColor: UIColor {
get { return super.indicatorColor }
set (newColor) {
super.indicatorColor = newColor
if isEnabled {
setViewColor(newColor)
}
}
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func updateView(_ size: CGFloat) {
super.updateView(size)
pageCount.updateView(size)
}
//--------------------------------------------------
// MARK: - Setup
//--------------------------------------------------
open override func setupView() {
super.setupView()
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "swipe_to_select_with_action_hint")
addSubview(pageCount)
addSubview(leftArrow)
addSubview(rightArrow)
NSLayoutConstraint.activate([
pageCount.centerXAnchor.constraint(equalTo: centerXAnchor),
pageCount.topAnchor.constraint(equalTo: topAnchor),
bottomAnchor.constraint(equalTo: pageCount.bottomAnchor),
leftArrow.centerYAnchor.constraint(equalTo: centerYAnchor),
rightArrow.centerYAnchor.constraint(equalTo: centerYAnchor),
leftArrow.leadingAnchor.constraint(equalTo: leadingAnchor),
pageCount.leadingAnchor.constraint(equalTo: leftArrow.trailingAnchor, constant: Padding.Two),
rightArrow.leadingAnchor.constraint(equalTo: pageCount.trailingAnchor, constant: Padding.Two),
trailingAnchor.constraint(equalTo: rightArrow.trailingAnchor)
])
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
public override func assessTouchOf(_ touchPoint_X: CGFloat) {
if touchPoint_X > bounds.width / 2 {
incrementCurrentIndex()
} else {
decrementCurrentIndex()
}
}
private func setViewColor(_ newColor: UIColor) {
pageCount.textColor = newColor
leftArrow.color = newColor
rightArrow.color = newColor
rightArrow.setNeedsDisplay()
leftArrow.setNeedsDisplay()
}
//--------------------------------------------------
// MARK: - IndicatorViewProtocol
//--------------------------------------------------
open override func updateUI(previousIndex oldIndex: Int, newIndex: Int, totalCount: Int, isAnimated: Bool) {
pageCount.text = "\(newIndex + 1)/\(totalCount)"
formatAccessibilityValue(index: newIndex + 1, total: totalCount)
layoutIfNeeded()
}
}

View File

@ -44,6 +44,10 @@ import UIKit
super.init(frame: .zero) super.init(frame: .zero)
} }
public override init(frame: CGRect) {
super.init(frame: frame)
}
public required init?(coder aDecoder: NSCoder) { public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder) super.init(coder: aDecoder)
} }

View File

@ -137,6 +137,8 @@ import Foundation
// Other Organisms // Other Organisms
MoleculeObjectMapping.shared()?.register(viewClass: Carousel.self, viewModelClass: CarouselModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Carousel.self, viewModelClass: CarouselModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: BarsIndicatorView.self, viewModelClass: BarsCarouselIndicatorModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: NumericIndicatorView.self, viewModelClass: NumericCarouselIndicatorModel.self)
// Designed List Items // Designed List Items
MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableIconWithRightCaret.self, viewModelClass: ListLeftVariableIconWithRightCaretModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableIconWithRightCaret.self, viewModelClass: ListLeftVariableIconWithRightCaretModel.self)

View File

@ -22,7 +22,7 @@ public class ListRightVariableButtonAllTextAndLinksModel: ListItemModel, Molecul
override public func setDefaults() { override public func setDefaults() {
super.setDefaults() super.setDefaults()
self.button.size = .tiny self.button.size = .tiny
self.button.style = ButtonStyle.secondary self.button.style = .secondary
} }
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {

View File

@ -9,11 +9,24 @@
import UIKit import UIKit
@objcMembers open class TwoButtonView: View, MVMCoreUIViewConstrainingProtocol { @objcMembers open class TwoButtonView: View, MVMCoreUIViewConstrainingProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open var primaryButton: PillButton = PillButton() open var primaryButton: PillButton = PillButton()
open var secondaryButton: PillButton = PillButton() open var secondaryButton: PillButton = PillButton()
private var stack = UIStackView() private var stack = UIStackView()
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
private var equalWidthConstraint: NSLayoutConstraint? private var equalWidthConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public init() { public init() {
super.init(frame: .zero) super.init(frame: .zero)
} }
@ -26,16 +39,21 @@ import UIKit
super.init(frame: frame) super.init(frame: frame)
} }
public func setDefault() { //--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
public func setDefaultAppearance() {
primaryButton.stylePrimary() primaryButton.stylePrimary()
secondaryButton.styleSecondary() secondaryButton.styleSecondary()
} }
// MARK: - MVMCoreViewProtocol
open override func updateView(_ size: CGFloat) { open override func updateView(_ size: CGFloat) {
super.updateView(size) super.updateView(size)
self.primaryButton.updateView(size)
self.secondaryButton.updateView(size) primaryButton.updateView(size)
secondaryButton.updateView(size)
} }
open override func setupView() { open override func setupView() {
@ -52,58 +70,70 @@ import UIKit
equalWidthConstraint?.isActive = true equalWidthConstraint?.isActive = true
} }
//--------------------------------------------------
// MARK: - Stack Manipulation // MARK: - Stack Manipulation
//--------------------------------------------------
public func showPrimaryButton() { public func showPrimaryButton() {
if !stack.arrangedSubviews.contains(primaryButton) { if !stack.arrangedSubviews.contains(primaryButton) {
stack.addArrangedSubview(primaryButton) stack.addArrangedSubview(primaryButton)
primaryButton.isHidden = false primaryButton.isHidden = false
} }
if secondaryButton.superview != nil { if secondaryButton.superview != nil {
equalWidthConstraint?.isActive = true equalWidthConstraint?.isActive = true
} }
} }
public func showSecondaryButton() { public func showSecondaryButton() {
if !stack.arrangedSubviews.contains(secondaryButton) { if !stack.arrangedSubviews.contains(secondaryButton) {
stack.insertArrangedSubview(secondaryButton, at: 0) stack.insertArrangedSubview(secondaryButton, at: 0)
secondaryButton.isHidden = false secondaryButton.isHidden = false
} }
if primaryButton.superview != nil { if primaryButton.superview != nil {
equalWidthConstraint?.isActive = true equalWidthConstraint?.isActive = true
} }
} }
public func hidePrimaryButton() { public func hidePrimaryButton() {
if primaryButton.superview != nil { if primaryButton.superview != nil {
stack.removeArrangedSubview(primaryButton) stack.removeArrangedSubview(primaryButton)
primaryButton.isHidden = true primaryButton.isHidden = true
} }
equalWidthConstraint?.isActive = false equalWidthConstraint?.isActive = false
} }
public func hideSecondaryButton() { public func hideSecondaryButton() {
if secondaryButton.superview != nil { if secondaryButton.superview != nil {
stack.removeArrangedSubview(secondaryButton) stack.removeArrangedSubview(secondaryButton)
secondaryButton.isHidden = true secondaryButton.isHidden = true
} }
equalWidthConstraint?.isActive = false equalWidthConstraint?.isActive = false
} }
//--------------------------------------------------
// MARK: - MoleculeViewProtocol // MARK: - MoleculeViewProtocol
//--------------------------------------------------
open override func reset() { open override func reset() {
super.reset() super.reset()
setDefault()
} setDefaultAppearance()
// MARK: - MVMCoreUIViewConstrainingProtocol
open func horizontalAlignment() -> UIStackView.Alignment {
return .center
} }
// MARK: - MoleculeViewProtocol
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
guard let model = model as? TwoButtonViewModel, guard let model = model as? TwoButtonViewModel,
let buttonModel = model.primaryButton ?? model.secondaryButton else { return 0 } let buttonModel = model.primaryButton ?? model.secondaryButton
else { return 0 }
return PillButton.estimatedHeight(with: buttonModel, delegateObject) return PillButton.estimatedHeight(with: buttonModel, delegateObject)
} }
@ -118,6 +148,7 @@ import UIKit
} else { } else {
hideSecondaryButton() hideSecondaryButton()
} }
if let primaryModel = model.primaryButton { if let primaryModel = model.primaryButton {
showPrimaryButton() showPrimaryButton()
primaryButton.set(with: primaryModel, delegateObject, additionalData) primaryButton.set(with: primaryModel, delegateObject, additionalData)
@ -125,4 +156,11 @@ import UIKit
hidePrimaryButton() hidePrimaryButton()
} }
} }
//--------------------------------------------------
// MARK: - MVMCoreUIViewConstrainingProtocol
//--------------------------------------------------
open func horizontalAlignment() -> UIStackView.Alignment {
return .center
}
} }

View File

@ -8,35 +8,54 @@
import UIKit import UIKit
public class TwoButtonViewModel: MoleculeModelProtocol { public class TwoButtonViewModel: MoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "twoButtonView" public static var identifier: String = "twoButtonView"
public var backgroundColor: Color? public var backgroundColor: Color?
public var primaryButton: ButtonModel? public var primaryButton: ButtonModel?
public var secondaryButton: ButtonModel? public var secondaryButton: ButtonModel?
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case moleculeName case moleculeName
case backgroundColor case backgroundColor
case primaryButton case primaryButton
case secondaryButton case secondaryButton
} }
//--------------------------------------------------
// MARK: - Initialzer
//--------------------------------------------------
public init(_ primaryButton: ButtonModel?, _ secondaryButton: ButtonModel?) { public init(_ primaryButton: ButtonModel?, _ secondaryButton: ButtonModel?) {
self.primaryButton = primaryButton self.primaryButton = primaryButton
self.secondaryButton = secondaryButton self.secondaryButton = secondaryButton
} }
//--------------------------------------------------
// 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)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
primaryButton = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .primaryButton) primaryButton = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .primaryButton)
if primaryButton?.style == nil {
primaryButton?.style = .primary
}
secondaryButton = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .secondaryButton) secondaryButton = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .secondaryButton)
// Default value
if secondaryButton?.style == nil { if secondaryButton?.style == nil {
secondaryButton?.style = .secondary secondaryButton?.style = .secondary
} }
} }
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.encode(moleculeName, forKey: .moleculeName) try container.encode(moleculeName, forKey: .moleculeName)

View File

@ -10,17 +10,30 @@ import Foundation
@objcMembers public class CarouselItemModel: MoleculeCollectionItemModel, CarouselItemModelProtocol { @objcMembers public class CarouselItemModel: MoleculeCollectionItemModel, CarouselItemModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public override class var identifier: String { public override class var identifier: String {
return "carouselItem" return "carouselItem"
} }
public var peakingUI: Bool? public var peakingUI: Bool?
public var peakingArrowColor: Color? public var peakingArrowColor: Color?
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case peakingUI case peakingUI
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)
peakingUI = try typeContainer.decodeIfPresent(Bool.self, forKey: .peakingUI) peakingUI = try typeContainer.decodeIfPresent(Bool.self, forKey: .peakingUI)

View File

@ -21,11 +21,19 @@ open class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtoco
case backgroundColor case backgroundColor
} }
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(with moleculeModel: MoleculeModelProtocol) { public init(with moleculeModel: MoleculeModelProtocol) {
molecule = moleculeModel molecule = moleculeModel
super.init() super.init()
} }
//--------------------------------------------------
// 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)
molecule = try typeContainer.decodeModel(codingKey: .molecule) molecule = try typeContainer.decodeModel(codingKey: .molecule)

View File

@ -8,6 +8,15 @@
import UIKit import UIKit
/// Contracts behavior between carousel and its page control.
public protocol CarouselPageControlProtocol {
var currentIndex: Int { get set }
var numberOfPages: Int { get set }
var indicatorTouchAction: ((CarouselPageControlProtocol) -> ())? { get set }
func scrollViewDidScroll(_ collectionView: UICollectionView)
}
open class Carousel: View { open class Carousel: View {
public let collectionView = CollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) public let collectionView = CollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
@ -17,10 +26,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.
public var pageIndex: Int { public 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 +37,7 @@ open class Carousel: View {
/// The models for the molecules. /// The models 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%.
public var itemAlignment = UICollectionView.ScrollPosition.left public var itemAlignment = UICollectionView.ScrollPosition.left
@ -41,10 +48,11 @@ open class Carousel: View {
public var collectionViewHeight: NSLayoutConstraint? public var collectionViewHeight: NSLayoutConstraint?
/// The view that we use for paging /// The view that we use for paging
public var pagingView: (UIView & MVMCoreUIPagingProtocol)? public var pagingView: (UIView & CarouselPageControlProtocol)?
/// If the carousel should loop after scrolling past the first and final cells. /// If the carousel should loop after scrolling past the first and final cells.
var loop = false var loop = false
private var dragging = false private var dragging = false
// For adding pager // For adding pager
@ -80,7 +88,10 @@ open class Carousel: View {
} }
} }
//--------------------------------------------------
// MARK: - MVMCoreViewProtocol // MARK: - MVMCoreViewProtocol
//--------------------------------------------------
open override func setupView() { open override func setupView() {
super.setupView() super.setupView()
collectionView.dataSource = self collectionView.dataSource = self
@ -103,16 +114,21 @@ open class Carousel: View {
layoutCollection() layoutCollection()
} }
// MARK: - MoleculeViewProtocol //--------------------------------------------------
// MARK: - MVMCoreUIMoleculeViewProtocol
//--------------------------------------------------
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
self.delegateObject = delegateObject self.delegateObject = delegateObject
super.set(with: model, delegateObject, additionalData) super.set(with: 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)
@ -129,107 +145,119 @@ open class Carousel: View {
setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject) setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject)
pageIndex = carouselModel.index pageIndex = carouselModel.index
pagingView?.setPage(carouselModel.index) pagingView?.currentIndex = carouselModel.index
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
layout.minimumLineSpacing = CGFloat(carouselModel?.spacing ?? 1) layout.minimumLineSpacing = CGFloat(carouselModel?.spacing ?? 1)
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 {
// Sets up the row data with buffer cells on each side (for illusion of endless scroll... also has one more buffer cell on each side in case we can peek that cell). // Sets up the row data with buffer cells on each side (for illusion of endless scroll... also has one more buffer cell on each side in case we can peek that cell).
loop = true loop = true
molecules?.insert(newMolecules.last!, at: 0) molecules?.insert(newMolecules.last!, at: 0)
molecules?.insert(newMolecules[(newMolecules.count - 2)], at: 0) molecules?.insert(newMolecules[(newMolecules.count - 2)], at: 0)
molecules?.append(newMolecules.first!) molecules?.append(newMolecules.first!)
molecules?.append(newMolecules[1]) molecules?.append(newMolecules[1])
} }
pageIndex = 0 pageIndex = 0
} }
/// Sets up the paging molecule /// Sets up the paging molecule
open func setupPagingMolecule(_ molecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)?, delegateObject: MVMCoreUIDelegateObject?) { open func setupPagingMolecule(_ molecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)?, delegateObject: MVMCoreUIDelegateObject?) {
var pagingView: (UIView & MVMCoreUIPagingProtocol)? = nil var pagingView: (UIView & CarouselPageControlProtocol)? = nil
if let molecule = molecule { if let molecule = molecule {
pagingView = MoleculeObjectMapping.shared()?.createMolecule(molecule, delegateObject: delegateObject) as? (UIView & MVMCoreUIPagingProtocol) pagingView = MoleculeObjectMapping.shared()?.createMolecule(molecule, delegateObject: delegateObject) as? (UIView & CarouselPageControlProtocol)
} }
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 {
if let info = getMoleculeInfo(with: molecule, delegateObject: delegateObject) { if let info = getMoleculeInfo(with: molecule, delegateObject: delegateObject) {
collectionView.register(info.class, forCellWithReuseIdentifier: info.identifier) collectionView.register(info.class, forCellWithReuseIdentifier: info.identifier)
} }
} }
} }
//--------------------------------------------------
// 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 = MoleculeObjectMapping.shared()?.getMoleculeClass(molecule) else { guard let className = MoleculeObjectMapping.shared()?.getMoleculeClass(molecule) else { return nil }
return nil
}
return (className.nameForReuse(with: molecule, delegateObject) ?? molecule.moleculeName, className, molecule) return (className.nameForReuse(with: molecule, delegateObject) ?? molecule.moleculeName, className, molecule)
} }
/// Adds a paging view. Centers it horizontally with the collection view. The position is the vertical distance from the center of the page view to the bottom of the collection view. /// Adds a paging view. Centers it horizontally with the collection view. The position is the vertical distance from the center of the page view to the bottom of the collection view.
open func addPaging(view: (UIView & MVMCoreUIPagingProtocol)?, position: CGFloat) { open func addPaging(view: (UIView & CarouselPageControlProtocol)?, position: CGFloat) {
pagingView?.removeFromSuperview() pagingView?.removeFromSuperview()
guard let pagingView = view else { bottomPin?.isActive = false
bottomPin?.isActive = false
guard var pagingView = view else {
bottomPin = bottomAnchor.constraint(equalTo: collectionView.bottomAnchor) bottomPin = bottomAnchor.constraint(equalTo: collectionView.bottomAnchor)
bottomPin?.isActive = true bottomPin?.isActive = true
return return
} }
pagingView.translatesAutoresizingMaskIntoConstraints = false
addSubview(pagingView) addSubview(pagingView)
pagingView.centerXAnchor.constraint(equalTo: collectionView.centerXAnchor).isActive = true pagingView.centerXAnchor.constraint(equalTo: collectionView.centerXAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: pagingView.centerYAnchor, constant: position).isActive = true collectionView.bottomAnchor.constraint(equalTo: pagingView.centerYAnchor, constant: position).isActive = true
bottomAnchor.constraint(greaterThanOrEqualTo: pagingView.bottomAnchor).isActive = true bottomAnchor.constraint(greaterThanOrEqualTo: pagingView.bottomAnchor).isActive = true
bottomPin?.isActive = false
bottomPin = bottomAnchor.constraint(equalTo: collectionView.bottomAnchor) bottomPin = bottomAnchor.constraint(equalTo: collectionView.bottomAnchor)
bottomPin?.priority = .defaultLow bottomPin?.priority = .defaultLow
bottomPin?.isActive = true bottomPin?.isActive = true
pagingView.setNumberOfPages(numberOfPages) pagingView.numberOfPages = numberOfPages
(pagingView as? MVMCoreUIViewConstrainingProtocol)?.alignHorizontal?(.fill) (pagingView as? MVMCoreUIViewConstrainingProtocol)?.alignHorizontal?(.fill)
pagingView.setPagingTouch { [weak self] (pager) in pageIndex = pagingView.currentIndex
MVMCoreDispatchUtility.performBlock(onMainThread: { pagingView.indicatorTouchAction = { [weak self] pager in
guard let localSelf = self else { DispatchQueue.main.async {
return guard let self = self else { return }
} let currentPage = pager.currentIndex
let currentPage = pager.currentPage() self.pageIndex = currentPage
localSelf.pageIndex = currentPage self.updateModelIndex()
localSelf.updateModelIndex() self.goTo(self.currentIndex, animated: !UIAccessibility.isVoiceOverRunning)
localSelf.goTo(localSelf.currentIndex, animated: !UIAccessibility.isVoiceOverRunning) }
})
} }
self.pagingView = pagingView self.pagingView = pagingView
} }
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 }
if let firstItem = visibleItemsPaths.first, firstItem.row != currentIndex { if let firstItem = visibleItemsPaths.first, firstItem.row != currentIndex {
(collectionView.cellForItem(at: firstItem) as? CarouselItem)?.setPeaking(true, animated: true) (collectionView.cellForItem(at: firstItem) as? CarouselItem)?.setPeaking(true, animated: true)
} }
@ -245,9 +273,8 @@ open class Carousel: View {
} }
public func setAccessiblity(_ cell: UICollectionViewCell?, index: Int) { public 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
@ -260,7 +287,7 @@ open class Carousel: View {
} }
} }
self.accessibilityElements = array accessibilityElements = array
} else { } else {
cell.accessibilityElementsHidden = true cell.accessibilityElementsHidden = true
} }
@ -285,9 +312,9 @@ extension Carousel: UICollectionViewDataSource {
open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { open 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? MoleculeViewProtocol { if let protocolCell = cell as? MoleculeViewProtocol {
protocolCell.reset() protocolCell.reset()
@ -302,6 +329,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: currentIndex, section: 0)), index: index) setAccessiblity(collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)), index: index)
currentIndex = index currentIndex = index
@ -317,12 +345,12 @@ extension Carousel: UIScrollViewDelegate {
guard loop else { return } guard loop else { 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?.currentIndex = 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)
@ -333,14 +361,15 @@ extension Carousel: UIScrollViewDelegate {
} }
func checkForDraggingOutOfBounds(_ scrollView: UIScrollView) { 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. // 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)
let index = scrollView.contentOffset.x / (itemWidth + separatorWidth) let index = scrollView.contentOffset.x / (itemWidth + separatorWidth)
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1 let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
if index < 1 { if index < 1 {
currentIndex = 0 currentIndex = 0
updateModelIndex() updateModelIndex()
@ -359,15 +388,17 @@ extension Carousel: UIScrollViewDelegate {
//checkForDraggingOutOfBounds(scrollView) //checkForDraggingOutOfBounds(scrollView)
// Let the pager know our progress if needed. // Let the pager know our progress if needed.
pagingView?.scrollViewDidScroll?(collectionView) pagingView?.scrollViewDidScroll(collectionView)
} }
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
@ -376,11 +407,13 @@ extension Carousel: UIScrollViewDelegate {
// 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 {
cellToSwipeTo = currentIndex - 1 cellToSwipeTo = currentIndex - 1
} }
@ -393,9 +426,7 @@ extension Carousel: UIScrollViewDelegate {
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
// Cycle to other end if on buffer cell. // Cycle to other end if on buffer cell.
handleUserOnBufferCell() handleUserOnBufferCell()
pagingView?.currentIndex = pageIndex
pagingView?.setPage(pageIndex)
showPeaking(true) showPeaking(true)
} }
} }

View File

@ -8,8 +8,16 @@
import UIKit import UIKit
@objcMembers public class CarouselModel: MoleculeModelProtocol { @objcMembers public class CarouselModel: MoleculeModelProtocol {
public static var identifier: String = "carousel" //--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String {
return "carousel"
}
public var backgroundColor: Color? public var backgroundColor: Color?
public var molecules: [CarouselItemModel] public var molecules: [CarouselItemModel]
public var index: Int = 0 public var index: Int = 0
@ -21,11 +29,15 @@ import UIKit
public var itemAlignment: UICollectionView.ScrollPosition? public var itemAlignment: UICollectionView.ScrollPosition?
public var pagingMolecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)? public var pagingMolecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)?
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
@ -37,9 +49,13 @@ 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)
molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules) molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules)
index = try typeContainer.decodeIfPresent(Int.self, forKey: .index) ?? 0 index = try typeContainer.decodeIfPresent(Int.self, forKey: .index) ?? 0
@ -65,5 +81,5 @@ import UIKit
try container.encode(itemWidthPercent, forKey: .itemWidthPercent) try container.encode(itemWidthPercent, forKey: .itemWidthPercent)
try container.encode(itemAlignment, forKey: .itemAlignment) try container.encode(itemAlignment, forKey: .itemAlignment)
try container.encodeModelIfPresent(pagingMolecule, forKey: .pagingMolecule) try container.encodeModelIfPresent(pagingMolecule, forKey: .pagingMolecule)
} }
} }

View File

@ -45,8 +45,8 @@ extension MoleculeViewProtocol {
} }
// Do nothing, optionals. // Do nothing, optionals.
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {} public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { }
public func reset() {} public func reset() { }
public static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { public static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return nil return nil

View File

@ -22,8 +22,5 @@ public extension TemplateProtocol where Self: ViewController {
let templateModel = try decoder.decode(TemplateModel.self, from: data) let templateModel = try decoder.decode(TemplateModel.self, from: data)
self.templateModel = templateModel self.templateModel = templateModel
self.pageModel = templateModel as? MVMControllerModelProtocol self.pageModel = templateModel as? MVMControllerModelProtocol
if let backgroundColor = templateModel.backgroundColor {
view.backgroundColor = backgroundColor.uiColor
}
} }
} }

View File

@ -12,6 +12,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Stored Properties // MARK: - Stored Properties
//-------------------------------------------------- //--------------------------------------------------
public var moleculesInfo: [(identifier: String, class: AnyClass, molecule: (ListItemModelProtocol & MoleculeModelProtocol))]? public var moleculesInfo: [(identifier: String, class: AnyClass, molecule: (ListItemModelProtocol & MoleculeModelProtocol))]?
var observer: NSKeyValueObservation? var observer: NSKeyValueObservation?

View File

@ -8,24 +8,39 @@
import Foundation import Foundation
@objcMembers public class TemplateModel: MVMControllerModelProtocol { @objcMembers public class TemplateModel: MVMControllerModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public class var identifier: String { public class var identifier: String {
return "" return ""
} }
public var pageType: String public var pageType: String
public var template: String { public var template: String {
// Although this is done in the extension, it is needed for the encoding. // Although this is done in the extension, it is needed for the encoding.
return Self.identifier return Self.identifier
} }
public var backgroundColor: Color? public var backgroundColor: Color?
public var screenHeading: String? public var screenHeading: String?
public var navigationItem: (NavigationItemModelProtocol & MoleculeModelProtocol)? public var navigationItem: (NavigationItemModelProtocol & MoleculeModelProtocol)?
public var formRules: [FormGroupRule]? public var formRules: [FormGroupRule]?
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(pageType: String) { public init(pageType: String) {
self.pageType = pageType self.pageType = pageType
} }
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case pageType case pageType
case template case template
@ -34,6 +49,10 @@ import Foundation
case formRules case formRules
case navigationItem case navigationItem
} }
//--------------------------------------------------
// 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)

View File

@ -24,25 +24,25 @@ import UIKit
open override func viewForTop() -> UIView? { open override func viewForTop() -> UIView? {
guard let headerModel = templateModel?.header, guard let headerModel = templateModel?.header,
let molecule = MoleculeObjectMapping.shared()?.createMolecule(headerModel, delegateObject: delegateObjectIVar) else { let molecule = MoleculeObjectMapping.shared()?.createMolecule(headerModel, delegateObject: delegateObjectIVar)
return nil else { return nil }
}
return molecule return molecule
} }
open override func viewForMiddle() -> UIView? { open override func viewForMiddle() -> UIView? {
guard let middleModel = templateModel?.middle, guard let middleModel = templateModel?.middle,
let molecule = MoleculeObjectMapping.shared()?.createMolecule(middleModel, delegateObject: delegateObjectIVar) else { let molecule = MoleculeObjectMapping.shared()?.createMolecule(middleModel, delegateObject: delegateObjectIVar)
return nil else { return nil }
}
return molecule return molecule
} }
override open func viewForBottom() -> UIView? { override open func viewForBottom() -> UIView? {
guard let footerModel = templateModel?.footer, guard let footerModel = templateModel?.footer,
let molecule = MoleculeObjectMapping.shared()?.createMolecule(footerModel, delegateObject: delegateObjectIVar) else { let molecule = MoleculeObjectMapping.shared()?.createMolecule(footerModel, delegateObject: delegateObjectIVar)
return nil else { return nil }
}
return molecule return molecule
} }

View File

@ -0,0 +1,99 @@
//
// ImageView.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 2/13/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import UIKit
open class ImageView: UIImageView, ModelMoleculeViewProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open var model: MoleculeModelProtocol?
private var initialSetupPerformed = false
//--------------------------------------------------
// MARK: - Initialization
//--------------------------------------------------
public override init(frame: CGRect) {
super.init(frame: .zero)
initialSetup()
}
override init(image: UIImage?) {
super.init(image: image)
initialSetup()
}
public convenience init() {
self.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
initialSetup()
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
public func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
setupView()
}
}
//--------------------------------------------------
// MARK: - ModelMoleculeViewProtocol
//--------------------------------------------------
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
self.model = model
if let backgroundColor = model?.backgroundColor {
self.backgroundColor = backgroundColor.uiColor
}
}
open class func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? {
return model?.moleculeName
}
open class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return nil
}
open class func requiredModules(_ molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? {
return nil
}
}
// MARK:- MVMCoreViewProtocol
extension ImageView: MVMCoreViewProtocol {
open func updateView(_ size: CGFloat) { }
/// Will be called only once.
open func setupView() {
translatesAutoresizingMaskIntoConstraints = false
insetsLayoutMarginsFromSafeArea = false
MVMCoreUIUtility.setMarginsFor(self, leading: 0, top: 0, trailing: 0, bottom: 0)
}
}
// MARK:- MVMCoreUIMoleculeViewProtocol
extension ImageView: MVMCoreUIMoleculeViewProtocol {
open func reset() {
backgroundColor = .clear
}
open func setAsMolecule() { }
}

View File

@ -81,6 +81,7 @@ open class TextViewModel: TextEntryFieldModel {
} }
public override func encode(to encoder: Encoder) throws { public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self) var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
try container.encodeIfPresent(height, forKey: .height) try container.encodeIfPresent(height, forKey: .height)

View File

@ -8,11 +8,16 @@
import UIKit import UIKit
@objcMembers open class View: UIView, MoleculeViewProtocol { @objcMembers open class View: UIView, MoleculeViewProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open var model: MoleculeModelProtocol? open var model: MoleculeModelProtocol?
private var initialSetupPerformed = false private var initialSetupPerformed = false
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initialization // MARK: - Initialization
//-------------------------------------------------- //--------------------------------------------------
@ -21,7 +26,7 @@ import UIKit
super.init(frame: .zero) super.init(frame: .zero)
initialSetup() initialSetup()
} }
public convenience init() { public convenience init() {
self.init(frame: .zero) self.init(frame: .zero)
} }
@ -38,12 +43,15 @@ import UIKit
} }
} }
// MARK:- MoleculeViewProtocol //--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
self.model = model self.model = model
if let backgroundColor = model.backgroundColor { if let backgroundColor = model.backgroundColor {
self.backgroundColor = backgroundColor.uiColor self.backgroundColor = backgroundColor.uiColor
} }
} }
open class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { open class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? {
@ -66,8 +74,8 @@ import UIKit
// MARK:- MVMCoreViewProtocol // MARK:- MVMCoreViewProtocol
extension View: MVMCoreViewProtocol { extension View: MVMCoreViewProtocol {
open func updateView(_ size: CGFloat) {} open func updateView(_ size: CGFloat) { }
/// Will be called only once. /// Will be called only once.
open func setupView() { open func setupView() {
translatesAutoresizingMaskIntoConstraints = false translatesAutoresizingMaskIntoConstraints = false

View File

@ -171,6 +171,10 @@ import UIKit
let rules = pageModel?.formRules let rules = pageModel?.formRules
formValidator = FormValidator(rules) formValidator = FormValidator(rules)
} }
if let backgroundColor = pageModel?.backgroundColor {
view.backgroundColor = backgroundColor.uiColor
}
} }
// MARK: - Navigation Item (Move to model base) // MARK: - Navigation Item (Move to model base)
@ -258,7 +262,7 @@ import UIKit
viewRespectsSystemMinimumLayoutMargins = false viewRespectsSystemMinimumLayoutMargins = false
// Presents from the bottom. // Presents from the bottom.
modalPresentationStyle = MVMCoreGetterUtility.isOnIPad() ? UIModalPresentationStyle.formSheet : UIModalPresentationStyle.overCurrentContext modalPresentationStyle = MVMCoreGetterUtility.isOnIPad() ? .formSheet : .overCurrentContext
// Create the default delegate object. // Create the default delegate object.
delegateObjectIVar = MVMCoreUIDelegateObject.create(withDelegateForAll: self) delegateObjectIVar = MVMCoreUIDelegateObject.create(withDelegateForAll: self)
@ -339,18 +343,18 @@ import UIKit
} }
// MARK: - MVMCoreActionDelegateProtocol // MARK: - MVMCoreActionDelegateProtocol
open func handleOpenPage(for requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) { open func handleOpenPage(for requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) {
formValidator?.addFormParams(requestParameters: requestParameters) formValidator?.addFormParams(requestParameters: requestParameters)
requestParameters.parentPageType = loadObject?.pageJSON?.optionalStringForKey("parentPageType") requestParameters.parentPageType = loadObject?.pageJSON?.optionalStringForKey("parentPageType")
MVMCoreActionHandler.defaultHandleOpenPage(for: requestParameters, additionalData: additionalData, delegateObject: delegateObject()) MVMCoreActionHandler.defaultHandleOpenPage(for: requestParameters, additionalData: additionalData, delegateObject: delegateObject())
} }
open func logAction(withActionInformation actionInformation: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) { open func logAction(withActionInformation actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) {
MVMCoreUILoggingHandler.shared()?.defaultLogAction(forController: self, actionInformation: actionInformation, additionalData: additionalData) MVMCoreUILoggingHandler.shared()?.defaultLogAction(forController: self, actionInformation: actionInformation, additionalData: additionalData)
} }
// MARK: - MoleculeDelegateProtocol // MARK: - MoleculeDelegateProtocol
open func getModuleWithName(_ name: String?) -> [AnyHashable : Any]? { open func getModuleWithName(_ name: String?) -> [AnyHashable: Any]? {
guard let name = name else { return nil } guard let name = name else { return nil }
return loadObject?.modulesJSON?.optionalDictionaryForKey(name) return loadObject?.modulesJSON?.optionalDictionaryForKey(name)
} }
@ -391,7 +395,7 @@ import UIKit
view.accessibilityElements = [pickerView, toolBar] view.accessibilityElements = [pickerView, toolBar]
} }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: textField.inputView) UIAccessibility.post(notification: .layoutChanged, argument: textField.inputView)
} }
} }
} }
@ -400,7 +404,7 @@ import UIKit
if textField === selectedField { if textField === selectedField {
if UIAccessibility.isVoiceOverRunning { if UIAccessibility.isVoiceOverRunning {
view.accessibilityElements = nil view.accessibilityElements = nil
UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: textField) UIAccessibility.post(notification: .layoutChanged, argument: textField)
} }
selectedField = nil selectedField = nil
} }

View File

@ -9,8 +9,10 @@
import UIKit import UIKit
open class Container: View, ContainerProtocol { open class Container: View, ContainerProtocol {
public var view: UIView? public var view: UIView?
let containerHelper = ContainerHelper() let containerHelper = ContainerHelper()
var containerModel: ContainerModelProtocol? { var containerModel: ContainerModelProtocol? {
get { return model as? ContainerModelProtocol } get { return model as? ContainerModelProtocol }
} }

View File

@ -55,6 +55,13 @@ public final class Color: Codable {
determineRGBA() determineRGBA()
} }
public init?(uiColor: UIColor?) {
guard let uiColor = uiColor else { return nil }
self.uiColor = uiColor
hex = UIColor.hexString(for: uiColor) ?? ""
determineRGBA()
}
init?(name: String) { init?(name: String) {
guard let colorTuple = UIColor.names[name] else { return nil } guard let colorTuple = UIColor.names[name] else { return nil }
self.uiColor = colorTuple.uiColor self.uiColor = colorTuple.uiColor

View File

@ -172,6 +172,29 @@ open class Styler {
} }
} }
public enum Button {
public enum Style: String, Codable {
case primary
case secondary
}
public enum Size: String, Codable {
case standard
case tiny
func getHeight() -> CGFloat {
switch self {
case .standard:
return 42
case .tiny:
return 20
}
}
}
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Functions // MARK: - Functions
//-------------------------------------------------- //--------------------------------------------------

View File

@ -68,8 +68,8 @@
// MARK: Carousel // MARK: Carousel
"MVMCoreUIPageControl_currentpage_index" = "page %ld of %ld"; "MVMCoreUIPageControl_currentpage_index" = "page %@ of %d";
"MVMCoreUIPageControlslides_currentpage_index" = "slide %ld of %ld"; "MVMCoreUIPageControlslides_currentpage_index" = "slide %@ of %d";
// MARK: Styler // MARK: Styler

View File

@ -48,8 +48,8 @@
"AccOff" = "apagado"; "AccOff" = "apagado";
"AccToggleHint" = "toca dos veces para alternar"; "AccToggleHint" = "toca dos veces para alternar";
// Carousel // Carousel
"MVMCoreUIPageControl_currentpage_index" = "página %ld de %ld"; "MVMCoreUIPageControl_currentpage_index" = "página %@ de %d";
"MVMCoreUIPageControlslides_currentpage_index" = "diapositiva %ld of %ld"; "MVMCoreUIPageControlslides_currentpage_index" = "diapositiva %@ of %d";
//Styler //Styler
"CountDownDay" = " día"; "CountDownDay" = " día";
"CountDownHour" = " hora"; "CountDownHour" = " hora";

View File

@ -50,8 +50,8 @@
"AccOff" = "apagado"; "AccOff" = "apagado";
"AccToggleHint" = "toca dos veces para alternar"; "AccToggleHint" = "toca dos veces para alternar";
// Carousel // Carousel
"MVMCoreUIPageControl_currentpage_index" = "página %ld de %ld"; "MVMCoreUIPageControl_currentpage_index" = "página %@ de %d";
"MVMCoreUIPageControlslides_currentpage_index" = "diapositiva %ld of %ld"; "MVMCoreUIPageControlslides_currentpage_index" = "diapositiva %@ of %d";
//Styler //Styler
"CountDownDay" = " día"; "CountDownDay" = " día";
"CountDownHour" = " hora"; "CountDownHour" = " hora";

View File

@ -188,14 +188,13 @@
self.labelRightConstraint.active = NO; self.labelRightConstraint.active = NO;
// Sets up to use a button action. Always uses the top view controller // Sets up to use a button action. Always uses the top view controller
PillButton *button = [[PillButton alloc] init]; PillButton *button = [[PillButton alloc] initAsPrimaryButton:false makeTiny:true];
[button styleSecondary]; [button styleSecondary];
[button setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal]; [button setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
[button setContentHuggingPriority:800 forAxis:UILayoutConstraintAxisHorizontal]; [button setContentHuggingPriority:800 forAxis:UILayoutConstraintAxisHorizontal];
button.translatesAutoresizingMaskIntoConstraints = NO; button.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:button]; [self addSubview:button];
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=space-[button]->=space-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:@{@"space":@(PaddingFive)} views:NSDictionaryOfVariableBindings(button)]];
[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0].active = YES; [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0].active = YES;
[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.centerView attribute:NSLayoutAttributeRight multiplier:1 constant:PaddingThree].active = YES; [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.centerView attribute:NSLayoutAttributeRight multiplier:1 constant:PaddingThree].active = YES;
[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:button attribute:NSLayoutAttributeRight multiplier:1 constant:(self.closeButton ? PaddingTen : PaddingFive)].active = YES; [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:button attribute:NSLayoutAttributeRight multiplier:1 constant:(self.closeButton ? PaddingTen : PaddingFive)].active = YES;

View File

@ -160,6 +160,7 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed.";
UIColor *statusBarColor = nil; UIColor *statusBarColor = nil;
UIStatusBarStyle statusBarStyle = UIStatusBarStyleDefault; UIStatusBarStyle statusBarStyle = UIStatusBarStyleDefault;
MVMCoreUITopAlertBaseView *view = [self topAlertViewForTopAlertObject:topAlertObject animationDelegate:animationDelegate statusBarColor:&statusBarColor statusBarStyle:&statusBarStyle]; MVMCoreUITopAlertBaseView *view = [self topAlertViewForTopAlertObject:topAlertObject animationDelegate:animationDelegate statusBarColor:&statusBarColor statusBarStyle:&statusBarStyle];
[view updateView:CGRectGetWidth(self.bounds)];
if (!statusBarColor) { if (!statusBarColor) {
statusBarColor = [UIColor whiteColor]; statusBarColor = [UIColor whiteColor];
} }