From 50920c83e0838ac7d33124e42b83f6607bd245b3 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 16 Mar 2020 09:50:54 -0400 Subject: [PATCH] latest state --- MVMCoreUI.xcodeproj/project.pbxproj | 2 +- MVMCoreUI/Atoms/Views/Arrow.swift | 3 + .../BarsCarouselIndicatorModel.swift | 29 ++++++++ .../CarouselIndicator/BarsIndicatorView.swift | 71 ++++++++++++++++--- .../CarouselIndicator/CarouselIndicator.swift | 62 +++++----------- .../CarouselIndicatorModel.swift | 41 +++++------ .../NumericIndicatorView.swift | 29 +++++++- .../CarouselPagingModelProtocol.swift | 2 +- MVMCoreUI/Organisms/Carousel.swift | 8 ++- 9 files changed, 163 insertions(+), 84 deletions(-) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index c42f1200..f3d4ca1b 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -810,8 +810,8 @@ 0A14F6B023E8C27A00EDF7F7 /* CarouselIndicator */ = { isa = PBXGroup; children = ( - 0A14F69223E349EF00EDF7F7 /* CarouselIndicator.swift */, 0A14F6A823E8750300EDF7F7 /* CarouselIndicatorModel.swift */, + 0A14F69223E349EF00EDF7F7 /* CarouselIndicator.swift */, 0AAF8E29240EA57D008DD263 /* BarsCarouselIndicatorModel.swift */, 0A4253AE23F5C2C000554656 /* BarsIndicatorView.swift */, 0AAF8E2B240EA594008DD263 /* NumericCarouselIndicatorModel.swift */, diff --git a/MVMCoreUI/Atoms/Views/Arrow.swift b/MVMCoreUI/Atoms/Views/Arrow.swift index eea6071d..c37e4c02 100644 --- a/MVMCoreUI/Atoms/Views/Arrow.swift +++ b/MVMCoreUI/Atoms/Views/Arrow.swift @@ -70,6 +70,9 @@ open class Arrow: View { // MARK: - Drawing //-------------------------------------------------- + /** + Draws the arrow pointing to the right and then rotates the arrow x degrees counter-clockwise. + */ open override func draw(_ rect: CGRect) { super.draw(rect) diff --git a/MVMCoreUI/Atoms/Views/CarouselIndicator/BarsCarouselIndicatorModel.swift b/MVMCoreUI/Atoms/Views/CarouselIndicator/BarsCarouselIndicatorModel.swift index 16adfe46..89369987 100644 --- a/MVMCoreUI/Atoms/Views/CarouselIndicator/BarsCarouselIndicatorModel.swift +++ b/MVMCoreUI/Atoms/Views/CarouselIndicator/BarsCarouselIndicatorModel.swift @@ -17,4 +17,33 @@ open class BarsCarouselIndicatorModel: CarouselIndicatorModel { 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) + } } diff --git a/MVMCoreUI/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift b/MVMCoreUI/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift index f9d7ebc9..5724a76e 100644 --- a/MVMCoreUI/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift +++ b/MVMCoreUI/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift @@ -14,7 +14,7 @@ open class BarsIndicatorView: CarouselIndicator { // MARK: - Stored Properties //-------------------------------------------------- - let stackView: StackView = { + public let stackView: StackView = { let stackView = StackView() stackView.axis = .horizontal stackView.alignment = .bottom @@ -30,18 +30,49 @@ open class BarsIndicatorView: CarouselIndicator { public static let indicatorBarWidth: CGFloat = 24 public static let indicatorBarHeight: (selected: CGFloat, unselected: CGFloat) = (selected: 4, unselected: 1) + /// Convenience to access the model. + public var barsCarouselIndicatorModel: BarsCarouselIndicatorModel? { + return model as? BarsCarouselIndicatorModel + } + //-------------------------------------------------- // MARK: - Computed Properties //-------------------------------------------------- - + open override var isEnabled: Bool { didSet { - barReferences.forEach { view, heightConstraint in - view.backgroundColor = isEnabled ? indicatorTintColor : disabledIndicatorColor + barReferences.forEach { view, _ in + view.backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor } } } - + + private(set) var _currentIndicatorColor: UIColor = .black + + /// Colors the currently selected index, unique from other indicators + public var currentIndicatorColor: UIColor { + get { return _currentIndicatorColor } + set (newColor) { + _currentIndicatorColor = newColor + barsCarouselIndicatorModel?.currentIndicatorColor = Color(uiColor: newColor) + + if !barReferences.isEmpty { + barReferences[currentIndex].view.backgroundColor = newColor + } + } + } + + public override var indicatorColor: UIColor { + get { return _indicatorColor } + set (newColor) { + super.indicatorColor = newColor + + for (i, barTuple) in barReferences.enumerated() where i != currentIndex { + barTuple.view.backgroundColor = newColor + } + } + } + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -64,10 +95,15 @@ open class BarsIndicatorView: CarouselIndicator { addSubview(stackView) isUserInteractionEnabled = false - stackView.heightAnchor.constraint(equalToConstant: 4).isActive = true - stackView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true - stackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true - trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true + NSLayoutConstraint.activate([ + stackView.heightAnchor.constraint(equalToConstant: 4), + heightAnchor.constraint(equalTo: stackView.heightAnchor), + stackView.centerXAnchor.constraint(equalTo: centerXAnchor), + stackView.leadingAnchor.constraint(equalTo: leadingAnchor), + stackView.topAnchor.constraint(equalTo: topAnchor), + bottomAnchor.constraint(equalTo: stackView.bottomAnchor), + trailingAnchor.constraint(equalTo: stackView.trailingAnchor) + ]) } //-------------------------------------------------- @@ -81,7 +117,7 @@ open class BarsIndicatorView: CarouselIndicator { for i in 0..= touchPoint_X && $0.0.frame.minX <= touchPoint_X } ?? 0 + } + + 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 //-------------------------------------------------- @@ -111,7 +160,7 @@ open class BarsIndicatorView: CarouselIndicator { } let expression = { - self.barReferences[previousIndex].view.backgroundColor = self.indicatorTintColor + self.barReferences[previousIndex].view.backgroundColor = self.indicatorColor self.barReferences[newIndex].view.backgroundColor = self.currentIndicatorColor self.barReferences[previousIndex].constraint.constant = BarsIndicatorView.indicatorBarHeight.unselected self.barReferences[newIndex].constraint.constant = BarsIndicatorView.indicatorBarHeight.selected diff --git a/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicator.swift b/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicator.swift index 3f95cf43..1e4db496 100644 --- a/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicator.swift +++ b/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicator.swift @@ -48,11 +48,13 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol { public var currentIndex: Int { get { return _currentIndex } set (newIndex) { - + carouselIndicatorModel?.currentIndex = newIndex previousIndex = _currentIndex _currentIndex = newIndex performAction() - updateUI(previousIndex: previousIndex, newIndex: newIndex, totalCount: numberOfPages, isAnimated: carouselIndicatorModel?.isAnimated ?? true) + if previousIndex != newIndex { + updateUI(previousIndex: previousIndex, newIndex: newIndex, totalCount: numberOfPages, isAnimated: carouselIndicatorModel?.isAnimated ?? true) + } } } @@ -64,6 +66,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol { get { return _numberOfPages } set (newTotal) { guard _numberOfPages != newTotal else { return } + carouselIndicatorModel?.numberOfPages = newTotal _numberOfPages = newTotal isHidden = carouselIndicatorModel?.hidesForSinglePage ?? false && newTotal <= 1 @@ -75,33 +78,12 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol { return carouselIndicatorModel?.disabledIndicatorColor.uiColor ?? .mvmCoolGray3 } - public var indicatorTintColor: UIColor { - get { return carouselIndicatorModel?.indicatorColor.uiColor ?? .black } + private(set) var _indicatorColor: UIColor = .black + + public var indicatorColor: UIColor { + get { return _indicatorColor } set (newColor) { carouselIndicatorModel?.indicatorColor = Color(uiColor: newColor) - -// if isBarIndicator(), let barIndicator = indicatorView as? BarsIndicatorView { -// for (i, barTuple) in barIndicator.barReferences.enumerated() where i != currentIndex { -// barTuple.view.backgroundColor = newColor -// } -// } - } - } - - private var _currentIndicatorColor: UIColor = .black - - /// Colors the currently selected index, unique from other indicators - public var currentIndicatorColor: UIColor { - get { return _currentIndicatorColor } - set (newColor) { - _currentIndicatorColor = newColor - carouselIndicatorModel?.currentIndicatorColor = Color(uiColor: newColor) - -// if isBarIndicator() { -// if let barIndicator = indicatorView as? BarsIndicatorView, !barIndicator.barReferences.isEmpty { -// barIndicator.barReferences[currentIndex].view.backgroundColor = newColor -// } -// } } } @@ -184,19 +166,11 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol { let touchPoint = tapGesture?.location(in: self) let touchPoint_X = touchPoint?.x ?? 0.0 -// if isBarIndicator(), let bars = (indicatorView as? BarsIndicatorView)?.barReferences { -// currentIndex = bars.firstIndex { $0.0.frame.maxX >= touchPoint_X && $0.0.frame.minX <= touchPoint_X } ?? 0 -// -// } else { -// // Determine which half of the view was touched. -// if touchPoint_X > bounds.width / 2 { -// incrementCurrentIndex() -// } else { -// decrementCurrentIndex() -// } -// } + assessTouchOf(touchPoint_X) } + func assessTouchOf(_ touchPoint_X: CGFloat) { } + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- @@ -215,15 +189,13 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol { // MARK: - MoleculeViewProtocol //-------------------------------------------------- - open override func set(with model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - - guard let model = model as? CarouselIndicatorModel else { return } - + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) -// indicatorType = IndicatorType(rawValue: model.type) ?? .hybrid - currentIndicatorColor = model.currentIndicatorColor.uiColor - indicatorTintColor = model.indicatorColor.uiColor + guard let model = model as? CarouselIndicatorModel else { return } + + indicatorColor = model.indicatorColor.uiColor + currentIndex = model.currentIndex isEnabled = model.isEnabled if let accessibleValue = MVMCoreUIUtility.hardcodedString(withKey: model.accessibilityHasSlidesInsteadOfPage ? "MVMCoreUIPageControlslides_currentpage_index" : "MVMCoreUIPageControl_currentpage_index") { diff --git a/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift b/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift index aa333f2a..75e5ed58 100644 --- a/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift +++ b/MVMCoreUI/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift @@ -22,8 +22,10 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol { public var moleculeName: String? /// The maxmum count of pages before the indicatorView forces a Numeric Indicator in place of Bar. - public var numberOfPages: Int = 0 + + // Sets the current Index to focus on. + public var currentIndex: Int = 0 public var isAnimated: 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 @@ -31,8 +33,7 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol { public var isEnabled: Bool = true public var disabledIndicatorColor: Color = Color(uiColor: .mvmCoolGray3) public var indicatorColor: Color = Color(uiColor: .mvmBlack) - public var currentIndicatorColor: Color = Color(uiColor: .mvmBlack) - public var padding: Float? + public var position: Float? /// Allows sendActions() to trigger even if index is already at min/max index. public var alwaysSendAction = false @@ -44,7 +45,7 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol { private enum CodingKeys: String, CodingKey { case moleculeName case backgroundColor - case type + case currentIndex case numberOfPages case alwaysSendAction case isAnimated @@ -54,7 +55,7 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol { case disabledIndicatorColor case indicatorColor case currentIndicatorColor - case padding + case position } //-------------------------------------------------- @@ -70,12 +71,16 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol { self.numberOfPages = numberOfPages } + 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 padding = try typeContainer.decodeIfPresent(Float.self, forKey: .padding) { - self.padding = padding + if let position = try typeContainer.decodeIfPresent(Float.self, forKey: .position) { + self.position = position } if let isAnimated = try typeContainer.decodeIfPresent(Bool.self, forKey: .isAnimated) { @@ -101,25 +106,21 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol { if let indicatorColor = try typeContainer.decodeIfPresent(Color.self, forKey: .indicatorColor) { self.indicatorColor = indicatorColor } - - if let currentIndicatorColor = try typeContainer.decodeIfPresent(Color.self, forKey: .currentIndicatorColor) { - self.currentIndicatorColor = currentIndicatorColor - } } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(moleculeName, forKey: .moleculeName) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) - try container.encodeIfPresent(numberOfPages, forKey: .numberOfPages) - try container.encodeIfPresent(alwaysSendAction, forKey: .alwaysSendAction) - try container.encodeIfPresent(isAnimated, forKey: .isAnimated) + try container.encode(numberOfPages, forKey: .numberOfPages) + try container.encode(currentIndex, forKey: .currentIndex) + try container.encode(alwaysSendAction, forKey: .alwaysSendAction) + try container.encode(isAnimated, forKey: .isAnimated) try container.encodeIfPresent(hidesForSinglePage, forKey: .hidesForSinglePage) try container.encodeIfPresent(accessibilityHasSlidesInsteadOfPage, forKey: .accessibilityHasSlidesInsteadOfPage) - try container.encodeIfPresent(isEnabled, forKey: .isEnabled) - try container.encodeIfPresent(disabledIndicatorColor, forKey: .disabledIndicatorColor) - try container.encodeIfPresent(indicatorColor, forKey: .indicatorColor) - try container.encodeIfPresent(currentIndicatorColor, forKey: .currentIndicatorColor) - try container.encodeIfPresent(padding, forKey: .padding) + try container.encode(isEnabled, forKey: .isEnabled) + try container.encode(disabledIndicatorColor, forKey: .disabledIndicatorColor) + try container.encode(indicatorColor, forKey: .indicatorColor) + try container.encodeIfPresent(position, forKey: .position) } } diff --git a/MVMCoreUI/Atoms/Views/CarouselIndicator/NumericIndicatorView.swift b/MVMCoreUI/Atoms/Views/CarouselIndicator/NumericIndicatorView.swift index 4eb16bce..8cd9e05c 100644 --- a/MVMCoreUI/Atoms/Views/CarouselIndicator/NumericIndicatorView.swift +++ b/MVMCoreUI/Atoms/Views/CarouselIndicator/NumericIndicatorView.swift @@ -40,9 +40,23 @@ open class NumericIndicatorView: CarouselIndicator { open override var isEnabled: Bool { didSet { - pageCount.textColor = isEnabled ? indicatorTintColor : disabledIndicatorColor - leftArrow.tintColor = isEnabled ? indicatorTintColor : disabledIndicatorColor - rightArrow.tintColor = isEnabled ? indicatorTintColor : disabledIndicatorColor + pageCount.textColor = isEnabled ? indicatorColor : disabledIndicatorColor + leftArrow.tintColor = isEnabled ? indicatorColor : disabledIndicatorColor + rightArrow.tintColor = isEnabled ? indicatorColor : disabledIndicatorColor + } + } + + /// Sets the color for pageCount text, left arrow and right arrow. + public override var indicatorColor: UIColor { + get { return _indicatorColor } + set (newColor) { + super.indicatorColor = newColor + + if isEnabled { + pageCount.textColor = newColor + leftArrow.tintColor = newColor + rightArrow.tintColor = newColor + } } } @@ -96,6 +110,15 @@ open class NumericIndicatorView: CarouselIndicator { ]) } + public override func assessTouchOf(_ touchPoint_X: CGFloat) { + + if touchPoint_X > bounds.width / 2 { + incrementCurrentIndex() + } else { + decrementCurrentIndex() + } + } + //-------------------------------------------------- // MARK: - IndicatorViewProtocol //-------------------------------------------------- diff --git a/MVMCoreUI/Models/ModelProtocols/CarouselPagingModelProtocol.swift b/MVMCoreUI/Models/ModelProtocols/CarouselPagingModelProtocol.swift index 27a35f87..94b1277a 100644 --- a/MVMCoreUI/Models/ModelProtocols/CarouselPagingModelProtocol.swift +++ b/MVMCoreUI/Models/ModelProtocols/CarouselPagingModelProtocol.swift @@ -10,5 +10,5 @@ import Foundation public protocol CarouselPagingModelProtocol: MoleculeModelProtocol { - var padding: Float? { get } + var position: Float? { get } } diff --git a/MVMCoreUI/Organisms/Carousel.swift b/MVMCoreUI/Organisms/Carousel.swift index 17abe0e6..05266a57 100644 --- a/MVMCoreUI/Organisms/Carousel.swift +++ b/MVMCoreUI/Organisms/Carousel.swift @@ -176,7 +176,7 @@ open class Carousel: View { pagingView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(molecule, delegateObject, false) as? (UIView & CarouselPageControlProtocol) } - addPaging(view: pagingView, padding: (CGFloat(molecule?.padding ?? 20))) + addPaging(view: pagingView, position: (CGFloat(molecule?.position ?? 20))) } /// Registers the cells with the collection view @@ -221,7 +221,7 @@ open class Carousel: 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 & CarouselPageControlProtocol)?, padding: CGFloat) { + open func addPaging(view: (UIView & CarouselPageControlProtocol)?, position: CGFloat) { pagingView?.removeFromSuperview() bottomPin?.isActive = false @@ -234,13 +234,15 @@ open class Carousel: View { addSubview(pagingView) pagingView.centerXAnchor.constraint(equalTo: collectionView.centerXAnchor).isActive = true - collectionView.bottomAnchor.constraint(equalTo: pagingView.bottomAnchor, constant: padding).isActive = true + collectionView.bottomAnchor.constraint(equalTo: pagingView.centerYAnchor, constant: position).isActive = true + bottomAnchor.constraint(greaterThanOrEqualTo: pagingView.bottomAnchor).isActive = true bottomPin = bottomAnchor.constraint(equalTo: collectionView.bottomAnchor) bottomPin?.priority = .defaultLow bottomPin?.isActive = true pagingView.numberOfPages = numberOfPages (pagingView as? MVMCoreUIViewConstrainingProtocol)?.alignHorizontal?(.fill) + pageIndex = pagingView.currentIndex pagingView.indicatorTouchAction = { [weak self] pager in DispatchQueue.main.async { guard let self = self else { return }