latest and greatest.

This commit is contained in:
Kevin G Christiano 2020-02-06 15:51:13 -05:00
parent af54ac2a3c
commit 977bff851e
8 changed files with 719 additions and 353 deletions

View File

@ -60,9 +60,11 @@
01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368D23609801006832FA /* HeadlineBodyModel.swift */; };
01F2A03223A4498200D954D8 /* CaretLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2A03123A4498200D954D8 /* CaretLinkModel.swift */; };
0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; };
0A14F69323E349EF00EDF7F7 /* PageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F69223E349EF00EDF7F7 /* PageControl.swift */; };
0A14F69323E349EF00EDF7F7 /* CarouselIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F69223E349EF00EDF7F7 /* CarouselIndicator.swift */; };
0A14F6A523E4803A00EDF7F7 /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F6A423E4803A00EDF7F7 /* StackView.swift */; };
0A14F6A723E4AB6E00EDF7F7 /* CarouselIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F6A623E4AB6E00EDF7F7 /* CarouselIndicator.swift */; };
0A14F6A923E8750300EDF7F7 /* CarouselIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F6A823E8750300EDF7F7 /* CarouselIndicatorModel.swift */; };
0A14F6B223E8C28D00EDF7F7 /* BarsIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F6B123E8C28D00EDF7F7 /* BarsIndicatorView.swift */; };
0A14F6B423E8C29700EDF7F7 /* NumericIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A14F6B323E8C29700EDF7F7 /* NumericIndicatorView.swift */; };
0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */; };
0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* EntryField.swift */; };
0A21DB83235DFBC500C160A2 /* MdnEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */; };
@ -380,9 +382,11 @@
01EB368D23609801006832FA /* HeadlineBodyModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlineBodyModel.swift; sourceTree = "<group>"; };
01F2A03123A4498200D954D8 /* CaretLinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretLinkModel.swift; sourceTree = "<group>"; };
0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = "<group>"; };
0A14F69223E349EF00EDF7F7 /* PageControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControl.swift; sourceTree = "<group>"; };
0A14F69223E349EF00EDF7F7 /* CarouselIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselIndicator.swift; sourceTree = "<group>"; };
0A14F6A423E4803A00EDF7F7 /* StackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackView.swift; sourceTree = "<group>"; };
0A14F6A623E4AB6E00EDF7F7 /* CarouselIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselIndicator.swift; sourceTree = "<group>"; };
0A14F6A823E8750300EDF7F7 /* CarouselIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselIndicatorModel.swift; sourceTree = "<group>"; };
0A14F6B123E8C28D00EDF7F7 /* BarsIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsIndicatorView.swift; sourceTree = "<group>"; };
0A14F6B323E8C29700EDF7F7 /* NumericIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumericIndicatorView.swift; sourceTree = "<group>"; };
0A209CD223A7E2810068F8B0 /* UIStackViewAlignment+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackViewAlignment+Extension.swift"; sourceTree = "<group>"; };
0A21DB7E235DECC500C160A2 /* EntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryField.swift; sourceTree = "<group>"; };
0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnEntryField.swift; sourceTree = "<group>"; };
@ -706,6 +710,25 @@
path = FormUIHelpers;
sourceTree = "<group>";
};
0A14F6B023E8C27A00EDF7F7 /* CarouselIndicator */ = {
isa = PBXGroup;
children = (
0A14F6B723ECA7F900EDF7F7 /* IndicatorViews */,
0A14F69223E349EF00EDF7F7 /* CarouselIndicator.swift */,
0A14F6A823E8750300EDF7F7 /* CarouselIndicatorModel.swift */,
);
path = CarouselIndicator;
sourceTree = "<group>";
};
0A14F6B723ECA7F900EDF7F7 /* IndicatorViews */ = {
isa = PBXGroup;
children = (
0A14F6B123E8C28D00EDF7F7 /* BarsIndicatorView.swift */,
0A14F6B323E8C29700EDF7F7 /* NumericIndicatorView.swift */,
);
path = IndicatorViews;
sourceTree = "<group>";
};
0A5D59C323AD488600EFD9E9 /* Protocols */ = {
isa = PBXGroup;
children = (
@ -1162,6 +1185,7 @@
D29DF17D21E69E26003B2FB9 /* Views */ = {
isa = PBXGroup;
children = (
0A14F6B023E8C27A00EDF7F7 /* CarouselIndicator */,
9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */,
01509D922327ECFB00EF99AA /* ProgressBar.swift */,
9445890D2385C3F800DE9FD4 /* MultiProgressModel.swift */,
@ -1191,8 +1215,6 @@
0AA33B392398524F0067DD0F /* Toggle.swift */,
D260105423CEA7DC00764D80 /* MVMCoreUISwitch+Model.swift */,
012CA99D2385A2D3003F810F /* MFView+ModelExtension.swift */,
0A14F69223E349EF00EDF7F7 /* PageControl.swift */,
0A14F6A623E4AB6E00EDF7F7 /* CarouselIndicator.swift */,
);
path = Views;
sourceTree = "<group>";
@ -1641,6 +1663,7 @@
01F2A03223A4498200D954D8 /* CaretLinkModel.swift in Sources */,
0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */,
011B58F023A2AA980085F53C /* ListItemModelProtocol.swift in Sources */,
0A14F6A923E8750300EDF7F7 /* CarouselIndicatorModel.swift in Sources */,
D22479962316AF6E003FCCF9 /* HeadlineBodyLink.swift in Sources */,
D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */,
0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */,
@ -1654,7 +1677,7 @@
017BEB7F23676E870024EF95 /* MoleculeObjectMapping.swift in Sources */,
D274CA332236A78900B01B62 /* FooterView.swift in Sources */,
D29DF2BF21E7BEA4003B2FB9 /* MVMCoreUITabBarPageControlViewController.m in Sources */,
0A14F69323E349EF00EDF7F7 /* PageControl.swift in Sources */,
0A14F69323E349EF00EDF7F7 /* CarouselIndicator.swift in Sources */,
014AA72423C501E2006F3E93 /* MoleculeContainerModel.swift in Sources */,
D29DF28321E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.m in Sources */,
011B58F223A2AE2C0085F53C /* DropDownListItemModel.swift in Sources */,
@ -1670,6 +1693,7 @@
9445890E2385C3F800DE9FD4 /* MultiProgressModel.swift in Sources */,
D2A6390522CBCE160052ED1F /* MoleculeCollectionViewCell.swift in Sources */,
D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */,
0A14F6B423E8C29700EDF7F7 /* NumericIndicatorView.swift in Sources */,
D29DF2C721E7BF57003B2FB9 /* MFTabBarInteractor.m in Sources */,
D29DF29521E7ADB8003B2FB9 /* ProgrammaticScrollViewController.m in Sources */,
D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */,
@ -1693,7 +1717,6 @@
01EB3684236097C0006832FA /* MoleculeModelProtocol.swift in Sources */,
D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */,
D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */,
0A14F6A723E4AB6E00EDF7F7 /* CarouselIndicator.swift in Sources */,
0198F79F225679880066C936 /* FormValidationProtocol.swift in Sources */,
D243859923A16B1800332775 /* Container.swift in Sources */,
D260105B23D0BB7100764D80 /* StackModelProtocol.swift in Sources */,
@ -1739,6 +1762,7 @@
D2B18B922361E65A00A9AEDC /* CoreUIObject.swift in Sources */,
D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */,
014AA72E23C5059B006F3E93 /* StackCenteredPageTemplateModel.swift in Sources */,
0A14F6B223E8C28D00EDF7F7 /* BarsIndicatorView.swift in Sources */,
D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */,
D260105923D0A92900764D80 /* ContainerProtocol.swift in Sources */,
C695A69423C9909000BFB94E /* DoughnutChartModel.swift in Sources */,

View File

@ -1,15 +0,0 @@
//
// CarouselIndicator.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 1/31/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
open class CarouselIndicator: PageControl {
}

View File

@ -0,0 +1,301 @@
//
// CarouselIndicator.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 1/30/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
public protocol IndicatorViewProtocol {
func updateUI()
var isEnabled: Bool { get set }
var currentIndex: Int? { get set }
var numberOfPages: Int? { get }
}
open class CarouselIndicator: Control {
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
public var topConstraint: NSLayoutConstraint?
public var bottomConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public typealias IndicatorView = UIView & IndicatorViewProtocol
/// The types of indicators that can appear.
public enum IndicatorType: String {
case bar
case numeric
case hybrid // bar & numeric
}
/// Determines interactivity and appearance of the indicator.
public var indicatorType: IndicatorType = .hybrid {
didSet {
assignIndicatorView()
}
}
/// The view control relative to the state of the indicator type.
private(set) var indicatorView: IndicatorView? {
didSet {
topConstraint = indicatorView!.topAnchor.constraint(equalTo: topAnchor, constant: PaddingTwo)
topConstraint?.isActive = true
bottomConstraint = bottomAnchor.constraint(equalTo: indicatorView!.bottomAnchor, constant: PaddingTwo)
bottomConstraint?.isActive = true
}
}
/// The maxmum count of pages before the indicatorView forces a numeric Indicator insead of Bar.
public var hybridThreshold: Int = 5
/// Spacing used between bars of the Bars Indicator and between the title and arrows of the Numeric Indicator
public var indicatorBarSpacing: CGFloat = 6 {
didSet {
if let stackView = indicatorView as? StackView {
stackView.spacing = indicatorBarSpacing
stackView.layoutIfNeeded()
}
}
}
// private(set) var indicatorBars = [BarIndicator]()
/// Set this closure to perform an action when a different indicator was selected.
public var indicatorTappedBlock: ((Int)->())?
/// Allows sendActions() to trigger even if index is min/max index.
public var alwaysSendControlEvent = false
/// Set true to make the accessibility value as "Slide #currentPage of #totalPage", otherwise will be "Page #currentPage of #totalPage", default is false
public var isSlidesAccessibile = false
public var isAnimated = false
/// Will hide this control if page count is 1.
public var hidesForSinglePage = false {
didSet { isHidden = hidesForSinglePage && numberOfPages <= 1 }
}
/// If true, then index will wraparound, otherwise it will stop paging at min/max index.
public var allowIndexWraparound = false
public override var isEnabled: Bool {
didSet {
isUserInteractionEnabled = isEnabled
indicatorView?.isEnabled = isEnabled
if indicatorType != .bar && numberOfPages > hybridThreshold {
} else {
if let stackView = indicatorView as? BarsIndicatorView {
stackView.stackView.arrangedSubviews.forEach { view in
// if let indicator = {
(view as? BarsIndicatorView)?.isEnabled = isEnabled
// }
}
}
}
}
}
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
/// The currently active indicator view.
public weak var currentIndicator: IndicatorView? {
didSet {
}
}
private var _currentIndex = 0
public var currentIndex: Int {
get { return _currentIndex }
set {
guard _currentIndex != newValue else { return }
_currentIndex = newValue
}
}
private var _numberOfPages = 0
public var numberOfPages: Int {
get { return _numberOfPages }
set {
guard _numberOfPages != newValue else { return }
_numberOfPages = newValue
if hidesForSinglePage && newValue <= 1 {
isHidden = true
} else {
isHidden = false
indicatorView = BarsIndicatorView(numberOfBars: numberOfPages)
}
indicatorView?.updateUI()
}
}
private var _indicatorTintColor: UIColor = .mvmCoolGray6
public var indicatorTintColor: UIColor {
get { return _indicatorTintColor }
set {
_indicatorTintColor = newValue
if isBarIndicator(), let barsView = (indicatorView as? BarsIndicatorView)?.stackView.arrangedSubviews.isEmpty {
indicatorView = BarsIndicatorView(numberOfBars: numberOfPages)
}
(indicatorView as? BarsIndicatorView)?.stackView.arrangedSubviews.forEach { $0.backgroundColor = newValue}
}
}
private var _currentPageIndicatorTintColor: UIColor = .black
public var currentPageIndicatorTintColor: UIColor {
get { return _currentPageIndicatorTintColor }
set {
_currentPageIndicatorTintColor = newValue
if ((indicatorView as? BarsIndicatorView)?.stackView.arrangedSubviews.isEmpty)! {
indicatorView = BarsIndicatorView(numberOfBars: numberOfPages)
}
currentIndicator?.backgroundColor = newValue
}
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience override init() {
self.init(frame: .zero)
}
public init(indicatorType: IndicatorType) {
self.indicatorType = indicatorType
super.init(frame: .zero)
}
required public init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
public override func initialSetup() {
super.initialSetup()
isAccessibilityElement = true
accessibilityTraits = .adjustable
}
open override func setupView() {
super.setupView()
if indicatorView == nil {
assignIndicatorView()
if let accessibleValue = MVMCoreUIUtility.hardcodedString(withKey: isSlidesAccessibile ? "MVMCoreUIPageControlslides_currentpage_index" : "MVMCoreUIPageControl_currentpage_index") {
accessibilityValue = String(format: accessibleValue, currentIndex + 1, numberOfPages)
}
}
}
open override func updateView(_ size: CGFloat) {
super.updateView(size)
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
func assignIndicatorView() {
switch indicatorType {
case .bar:
indicatorView = BarsIndicatorView(numberOfBars: numberOfPages)
case .numeric:
indicatorView = NumericIndicatorView()
case .hybrid:
indicatorView = numberOfPages >= hybridThreshold ? NumericIndicatorView() : BarsIndicatorView(numberOfBars: numberOfPages)
}
}
/// Removes all indicators from their subview and then clears the holding array.
func removeIndicatorView() {
// indicatorBars.forEach { $0.removeFromSuperview() }
// indicatorBars = []
}
/// Convenience to determine if current view is displaying bars.
func isBarIndicator() -> Bool {
return indicatorType != .bar && numberOfPages > hybridThreshold
}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
override open func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.setWithModel(model, delegateObject, additionalData)
guard let model = model as? CarouselIndicatorModel else { return }
if let type = model.type, let indicator = IndicatorType(rawValue: type) {
indicatorType = indicator
}
// backgroundColor = model.backgroundColor?.uiColor
// barsColor = model.barsColor
// pageIndicatorTintColor
// currentPageIndicatorTintColor
}
//--------------------------------------------------
// MARK: - Accessibility
//--------------------------------------------------
open override func accessibilityIncrement() {
accessibilityAdjust(toPage: currentIndex + 1)
}
open override func accessibilityDecrement() {
accessibilityAdjust(toPage: currentIndex - 1)
}
func accessibilityAdjust(toPage index: Int) {
if (index < numberOfPages && index >= 0) || alwaysSendControlEvent {
isAnimated = false
currentIndex = index
sendActions(for: .valueChanged)
indicatorTappedBlock?(index)
}
}
func setTopBottomSpace(constant: CGFloat) {
bottomConstraint?.constant = constant
topConstraint?.constant = constant
}
}

View File

@ -0,0 +1,50 @@
//
// CarouselIndicatorModel.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 2/3/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
public class CarouselIndicatorModel: MoleculeModelProtocol {
public var backgroundColor: Color?
public var barsColor: Color?
public static var identifier: String {
return "carouselIndicator"
}
public var moleculeName: String?
public var type: String? = "hybrid"
private enum CodingKeys: String, CodingKey {
case moleculeName
case backgroundColor
case type
case barsColor
}
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
// if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) {
// self.state = state
// }
// action = try typeContainer.decodeModelIfPresent(codingKey: .action, typeCodingKey: ActionCodingKey.actionType)
// alternateAction = try typeContainer.decodeModelIfPresent(codingKey: .alternateAction, typeCodingKey: ActionCodingKey.actionType)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
barsColor = try typeContainer.decodeIfPresent(Color.self, forKey: .barsColor)
type = try typeContainer.decodeIfPresent(String.self, forKey: .type) ?? "hybrid"
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(barsColor, forKey: .barsColor)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(type, forKey: .type)
}
}

View File

@ -0,0 +1,164 @@
//
// BarIndicatorView.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 2/3/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import UIKit
open class BarsIndicatorView: View, IndicatorViewProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
let stackView: StackView = {
let stackView = StackView()
stackView.axis = .horizontal
stackView.distribution = .equalSpacing
stackView.spacing = PaddingOne
return stackView
}()
var barsReference: [(View, NSLayoutConstraint)] = []
// Dimensions are based on InVision Design Guidelines.
public static let indicatorBarWidth: CGFloat = 24
public static let indicatorBarHeight: (selected: CGFloat, unselected: CGFloat) = (selected: 4, unselected: 1)
public var enabledColor: UIColor = .black
public var disabledColor: UIColor = .mvmCoolGray3
private var oldIndex: Int = 0
/// Returns the currentIndex from its parent CarouselIndicator.
public var currentIndex: Int? {
get { return (superview as? CarouselIndicator)?.currentIndex }
set {
guard let newValue = newValue else { return }
(superview as? CarouselIndicator)?.currentIndex = newValue
}
}
/// Returns the numberOfPages count from its parent CarouselIndicator.
public var numberOfPages: Int? {
return (superview as? CarouselIndicator)?.numberOfPages
}
public var numberOfBars: Int = 0 {
didSet {
// TODO: Generate bars...
}
}
open var isEnabled: Bool = true {
didSet {
if isEnabled {
} else {
}
}
}
open var isAnimated: Bool {
return (superview as? CarouselIndicator)?.isAnimated ?? true
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public init(numberOfBars: Int) {
super.init(frame: .zero)
self.numberOfBars = numberOfBars
}
public required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//--------------------------------------------------
// MARK: - Setup
//--------------------------------------------------
open override func setupView() {
super.setupView()
if subviews.isEmpty {
addSubview(stackView)
stackView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
stackView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor).isActive = true
trailingAnchor.constraint(lessThanOrEqualTo: stackView.trailingAnchor).isActive = true
removeIndicators()
let tapGesture = UITapGestureRecognizer()
tapGesture.addTarget(self, action: #selector(indicatorTapped(_:)))
addGestureRecognizer(tapGesture)
}
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
func generateBars() {
var bars = [(View, NSLayoutConstraint)]()
for i in 0..<numberOfBars {
let bar = View()
bar.widthAnchor.constraint(equalToConstant: BarsIndicatorView.indicatorBarWidth).isActive = true
bar.backgroundColor = enabledColor
// let barHeight = i == currentIndex ? indicatorBarWidth.indicatorBarHeight.selected : indicatorBarWidth.indicatorBarHeight.unselected
// let heightConstraint = bar.heightAnchor.constraint(equalToConstant: barHeight)
// heightConstraint.isActive = true
// stackView.app
// bars.append((bar, heightConstraint))
}
barsReference = bars
}
func removeIndicators() {
// indicatorBars.forEach { $0.removeFromSuperview() }
// indicatorBars = []
}
func indicatorTapped(_ tapGesture: UITapGestureRecognizer?) {
if isEnabled {
let touchPoint_X = tapGesture?.location(in: self).x ?? 0.0
// let index = (indicatorView as? BarsIndicatorView)?.stackView.arrangedSubviews.firstIndex { indicator in
// return indicator.frame.maxX >= touchPoint_X && indicator.frame.minX <= touchPoint_X
// }
//
// if let selectIndex = index {
// currentIndex = selectIndex
// (indicatorView as? BarsIndicatorView)?sendActions(for: .valueChanged)
// indicatorTappedBlock?(selectIndex)
// }
}
}
//--------------------------------------------------
// MARK: - IndicatorViewProtocol
//--------------------------------------------------
public func updateUI() {
let expression = {
// stackView.arrangedSubviews[oldIndex].heightConstraint?.constant = BarsIndicatorView.indicatorBarHeight.unselected
// self.heightConstraint?.constant = BarsIndicatorView.indicatorBarHeight.selected
// self.layoutIfNeeded()
}
// isAnimated ? UIView.animate(withDuration: 0.3) { expression() } : expression()
}
}

View File

@ -0,0 +1,161 @@
//
// NumericIndicatorView.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 2/3/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import UIKit
open class NumericIndicatorView: View, IndicatorViewProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
/// Text to display the current count of total pages for viewing.
open var titleLabel: Label = {
let label = Label.commonLabelB2(true)
label.setContentCompressionResistancePriority(.required, for: .vertical)
label.textAlignment = .center
return label
}()
// Left and right arrows for Numeric indicator
open var leftArrow = MFLoadImageView()
open var rightArrow = MFLoadImageView()
// open var activeColor: (enabled: UIColor, disabled: UIColor)
open var isEnabled: Bool = true {
didSet {
if isEnabled {
} else {
}
}
}
/// Returns the currentIndex from its parent CarouselIndicator.
public var currentIndex: Int? {
get { return (superview as? CarouselIndicator)?.currentIndex }
set {
guard let newValue = newValue else { return }
(superview as? CarouselIndicator)?.currentIndex = newValue
}
}
/// Returns the numberOfPages count from its parent CarouselIndicator.
public var numberOfPages: Int? {
return (superview as? CarouselIndicator)?.numberOfPages
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public convenience init() {
self.init(frame: .zero)
}
public required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func updateView(_ size: CGFloat) {
super.updateView(size)
titleLabel.updateView(size)
}
//--------------------------------------------------
// MARK: - Setup
//--------------------------------------------------
open override func setupView() {
super.setupView()
guard subviews.isEmpty else { return }
addSubview(titleLabel)
titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
NSLayoutConstraint.constraintPinSubview(titleLabel, pinTop: true, pinBottom: true, pinLeft: false, pinRight: false)
addSubview(leftArrow)
NSLayoutConstraint.constraintPinView(leftArrow, heightConstraint: true, heightConstant: PaddingTwo, widthConstraint: true, widthConstant: PaddingTwo)
leftArrow.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
leftArrow.isUserInteractionEnabled = true
leftArrow.loadImage(withName: "peakingRightArrow", width: nil, height: nil) { [weak self] image, _, _ in
DispatchQueue.main.async { [weak self] in
guard let image = image else { return }
self?.leftArrow.imageView.image = image.withHorizontallyFlippedOrientation()
self?.leftArrow.layoutIfNeeded()
}
}
let leftTap = UITapGestureRecognizer()
leftTap.addTarget(self, action: #selector(pageValueDecrement))
leftArrow.addGestureRecognizer(leftTap)
rightArrow.loadImage(withName: "peakingRightArrow")
addSubview(rightArrow)
NSLayoutConstraint.constraintPinView(rightArrow, heightConstraint: true, heightConstant: PaddingTwo, widthConstraint: true, widthConstant: PaddingTwo)
rightArrow.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
rightArrow.isUserInteractionEnabled = true
let rightTap = UITapGestureRecognizer()
rightTap.addTarget(self, action: #selector(pageValueIncrement))
rightArrow.addGestureRecognizer(rightTap)
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[leftArrowView]-(padding)-[titleLabel]-(padding)-[rightArrowView]-0-|",
options: .directionLeadingToTrailing,
metrics: ["padding": PaddingOne],
views: ["leftArrowView": leftArrow,
"titleLabel": titleLabel,
"rightArrowView": rightArrow]))
}
//--------------------------------------------------
// MARK: - Actions
//--------------------------------------------------
@objc func pageValueIncrement() {
guard let currentIndex = currentIndex,
let numberOfPages = numberOfPages
else { return }
self.currentIndex = min(currentIndex + 1, numberOfPages - 1)
}
@objc func pageValueDecrement() {
guard let currentIndex = currentIndex else { return }
self.currentIndex = max(0, currentIndex - 1)
}
//--------------------------------------------------
// MARK: - IndicatorViewProtocol
//--------------------------------------------------
open func updateUI() {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.titleLabel.text = "\(self.currentIndex)/\(self.numberOfPages)"
self.layoutIfNeeded()
(self.superview as? CarouselIndicator)?.sendActions(for: .valueChanged)
}
}
}

View File

@ -1,322 +0,0 @@
//
// PageControl.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 1/30/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
/**
This class is implemented to focus primarily on the page control logic.
Visual flourishes and bespoke behavior should be subclassed from here.
*/
open class PageControl: Control {
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
public var topConstraint: NSLayoutConstraint?
public var bottomConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public enum IndicatorType {
case bar
case numeric
case hybrid // bar & numeric
}
public var indicatorType: IndicatorType = .hybrid
public var indicatorSpacing: CGFloat {
get { return stackView.spacing }
set {
stackView.spacing = newValue
stackView.layoutIfNeeded()
}
}
public let indicatorBarWidth: CGFloat = 24
public let indicatorBarHeight: (selected: CGFloat, unselected: CGFloat) = (selected: 4, unselected: 1)
private(set) var indicators = [BarIndicator]()
var stackView: StackView = {
let stack = StackView()
stack.axis = .horizontal
stack.distribution = .equalSpacing
stack.spacing = 6
return stack
}()
public var pagingTouchBlock: ((Int)->())?
// a flag to allow to send UIControlEventValueChanged actions all the time
// e.g. going to previous element at first place and going to next at last place
// While current rectangle won't change, need update current page
// When awlaysSenfingControlEvent is false, and user is already at first or final index, if user try to increment or decrement, won't do action
// while self.awlaysSenfingControlEven is YES, it still send control event, while the rectangle won't change, need set currentPage again.
public var alwaysSendingControlEvent = false
/// Set true to make the accessibility value as "Slide #currentPage of #totalPage", otherwise will be "Page #currentPage of #totalPage", default is false
public var isSlidesAccessibile = false
public var isAnimated = false
public var hidesForSinglePage = false
/// If true, then index will wraparound, otherwise it will stop paging at min/max index.
public var allowIndexWraparound = false
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
/// The currently active indicator view.
public weak var currentIndicator: BarIndicator? {
didSet {
let expression = {
oldValue?.heightConstraint?.constant = self.indicatorBarHeight.unselected
self.currentIndicator?.heightConstraint?.constant = self.indicatorBarHeight.selected
self.layoutIfNeeded()
}
isAnimated ? UIView.animate(withDuration: 0.3) { expression() } : expression()
}
}
private var _currentPage = 0
public var currentPage: Int {
get { return _currentPage }
set {
guard _currentPage != newValue else { return }
_currentPage = newValue
currentIndicator = indicators[newValue]
}
}
private var _numberOfPages = 0
public var numberOfPages: Int {
get { return _numberOfPages }
set {
guard _numberOfPages != newValue else { return }
_numberOfPages = newValue
setupIndicators()
}
}
private var _indicatorTintColor: UIColor = .mvmCoolGray6
public var indicatorTintColor: UIColor {
get { return _indicatorTintColor }
set {
_indicatorTintColor = newValue
if indicators.isEmpty { setupIndicators() }
indicators.forEach { $0.backgroundColor = newValue}
}
}
private var _currentPageIndicatorTintColor: UIColor = .black
public var currentPageIndicatorTintColor: UIColor {
get { return _currentPageIndicatorTintColor }
set {
_currentPageIndicatorTintColor = newValue
if indicators.isEmpty { setupIndicators() }
currentIndicator?.backgroundColor = newValue
}
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience override init() {
self.init(frame: .zero)
}
convenience init(indicatorType: IndicatorType) {
self.init(frame: .zero)
self.indicatorType = indicatorType
}
required public init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
public override func initialSetup() {
super.initialSetup()
isAccessibilityElement = true
accessibilityTraits = .adjustable
}
open override func setupView() {
super.setupView()
if stackView.subviews.isEmpty {
if let accessibleValue = MVMCoreUIUtility.hardcodedString(withKey: isSlidesAccessibile ? "MVMCoreUIPageControlslides_currentpage_index" : "MVMCoreUIPageControl_currentpage_index") {
accessibilityValue = String(format: accessibleValue, currentPage + 1, numberOfPages)
}
addSubview(stackView)
stackView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
stackView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor).isActive = true
trailingAnchor.constraint(lessThanOrEqualTo: stackView.trailingAnchor).isActive = true
topConstraint = stackView.topAnchor.constraint(equalTo: topAnchor, constant: PaddingThree)
topConstraint?.priority = .defaultHigh
topConstraint?.isActive = true
bottomConstraint = bottomAnchor.constraint(equalTo: stackView.bottomAnchor, constant: PaddingThree)
bottomConstraint?.priority = .defaultHigh
bottomConstraint?.isActive = true
setupIndicators()
let tapGesture = UITapGestureRecognizer()
tapGesture.addTarget(self, action: #selector(indicatorTapped(_:)))
addGestureRecognizer(tapGesture)
}
}
open override func updateView(_ size: CGFloat) { }
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
// func setPagingTouchBlock(_ pagingTouchBlock: PageControl.PagingTouchBlock?) { }
//
// func scrollViewDidScroll(_ collectionView: UICollectionView) { }
func setupIndicators() {
removeIndicators()
var newIndicators = [BarIndicator]()
for i in 0..<numberOfPages {
let indicator = BarIndicator()
newIndicators.append(indicator)
indicator.heightAnchor.constraint(equalToConstant: i == currentPage ? indicatorBarHeight.selected : indicatorBarHeight.unselected).isActive = true
indicator.widthAnchor.constraint(equalToConstant: indicatorBarWidth).isActive = true
indicator.backgroundColor = indicatorTintColor
}
self.indicators = newIndicators
}
/// Removes all indicators from their subview and then clears the holding array.
func removeIndicators() {
indicators.forEach { $0.removeFromSuperview() }
indicators = []
}
//--------------------------------------------------
// MARK: - Actions
//--------------------------------------------------
func indicatorTapped(_ tapGesture: UITapGestureRecognizer?) {
if isUserInteractionEnabled {
let touchPoint_X = tapGesture?.location(in: stackView).x ?? 0.0
let index = indicators.firstIndex { indicator in
return indicator.frame.maxX >= touchPoint_X && indicator.frame.minX <= touchPoint_X
}
if let selectIndex = index {
currentPage = selectIndex
sendActions(for: .valueChanged)
pagingTouchBlock?(selectIndex)
}
}
}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
override open func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
// TODO
/*
var colorString = json?.string(KeyBackgroundColor)
if colorString != nil {
backgroundColor = UIColor.mfGet(forHex: colorString)
}
colorString = json?.string("barsColor")
if colorString != nil {
let color = UIColor.mfGet(forHex: colorString)
pageIndicatorTintColor = color
currentPageIndicatorTintColor = color
}
colorString = json?.string("currentBarColor")
if colorString != nil {
currentPageIndicatorTintColor = UIColor.mfGet(forHex: colorString)
}
*/
}
//--------------------------------------------------
// MARK: - Accessibility
//--------------------------------------------------
open override func accessibilityIncrement() {
accessibilityAdjust(toPage: currentPage + 1)
}
open override func accessibilityDecrement() {
accessibilityAdjust(toPage: currentPage - 1)
}
func accessibilityAdjust(toPage index: Int) {
if (index < numberOfPages && index >= 0) || alwaysSendingControlEvent {
isAnimated = false
currentPage = index
sendActions(for: .valueChanged)
pagingTouchBlock?(index)
}
}
func setTopBottomSpace(constant: CGFloat) {
bottomConstraint?.constant = constant
topConstraint?.constant = constant
}
public class BarIndicator: View {
var heightConstraint: NSLayoutConstraint?
override init(frame: CGRect) {
super.init(frame: .zero)
}
convenience init() {
self.init(frame: .zero)
}
public required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}

View File

@ -16,9 +16,9 @@ open class StackView: UIStackView, ModelMoleculeViewProtocol {
open var json: [AnyHashable: Any]?
open var model: MoleculeModelProtocol?
private var initialSetupPerformed = false
//--------------------------------------------------
// MARK: - Initialization
//--------------------------------------------------
@ -27,7 +27,7 @@ open class StackView: UIStackView, ModelMoleculeViewProtocol {
super.init(frame: .zero)
initialSetup()
}
public convenience init() {
self.init(frame: .zero)
}
@ -44,12 +44,15 @@ open class StackView: UIStackView, ModelMoleculeViewProtocol {
}
}
// MARK:- ModelMoleculeViewProtocol
//--------------------------------------------------
// MARK: - ModelMoleculeViewProtocol
//--------------------------------------------------
open func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
self.model = model
if let backgroundColor = model?.backgroundColor {
self.model = model
if let backgroundColor = model?.backgroundColor {
self.backgroundColor = backgroundColor.uiColor
}
}
}
open class func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? {
@ -69,7 +72,7 @@ open class StackView: UIStackView, ModelMoleculeViewProtocol {
extension StackView: MVMCoreViewProtocol {
open func updateView(_ size: CGFloat) {}
/// Will be called only once.
open func setupView() {
translatesAutoresizingMaskIntoConstraints = false