adding new base class. improving carousel logic.
This commit is contained in:
parent
6ede310cd0
commit
5558cc8324
@ -85,6 +85,7 @@
|
||||
0A4253AF23F5C2C100554656 /* BarsIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A4253AE23F5C2C000554656 /* BarsIndicatorView.swift */; };
|
||||
0A5D59C223AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */; };
|
||||
0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */; };
|
||||
0A7918F523F5E7EA00772FF4 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7918F423F5E7EA00772FF4 /* ImageView.swift */; };
|
||||
0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; };
|
||||
0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; };
|
||||
0A7EF85B23D8A52800B2AAD1 /* EntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF85A23D8A52800B2AAD1 /* EntryFieldModel.swift */; };
|
||||
@ -412,6 +413,7 @@
|
||||
0A4253AE23F5C2C000554656 /* BarsIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarsIndicatorView.swift; sourceTree = "<group>"; };
|
||||
0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleGuidelinesProtocol.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>"; };
|
||||
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>"; };
|
||||
@ -1471,6 +1473,7 @@
|
||||
D2755D7A23689C7500485468 /* TableViewCell.swift */,
|
||||
0A5D59C323AD488600EFD9E9 /* Protocols */,
|
||||
0A14F6A423E4803A00EDF7F7 /* StackView.swift */,
|
||||
0A7918F423F5E7EA00772FF4 /* ImageView.swift */,
|
||||
);
|
||||
path = BaseClasses;
|
||||
sourceTree = "<group>";
|
||||
@ -1819,6 +1822,7 @@
|
||||
01EB3684236097C0006832FA /* MoleculeModelProtocol.swift in Sources */,
|
||||
D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */,
|
||||
D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */,
|
||||
0A7918F523F5E7EA00772FF4 /* ImageView.swift in Sources */,
|
||||
0198F79F225679880066C936 /* FormValidationProtocol.swift in Sources */,
|
||||
D243859923A16B1800332775 /* Container.swift in Sources */,
|
||||
D2C521A923EDE79E00CA2634 /* ViewController.swift in Sources */,
|
||||
|
||||
@ -8,14 +8,24 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Set protocols for all indicator faces of the Carousel Indicator.
|
||||
public protocol IndicatorViewProtocol {
|
||||
func updateUI(previousIndex: Int, newIndex: Int, totalCount: Int, isAnimated: Bool)
|
||||
func reset()
|
||||
var isEnabled: Bool { get set }
|
||||
}
|
||||
|
||||
/// Contracts behavior between carousel and its page control.
|
||||
public protocol CarouselPageControlProtocol {
|
||||
typealias PagingTouchBlock = ((CarouselPageControlProtocol)) -> ()
|
||||
var currentIndex: Int { get set }
|
||||
var numberOfPages: Int { get set }
|
||||
var indicatorTouchAction: PagingTouchBlock? { get set }
|
||||
func scrollViewDidScroll(_ collectionView: UICollectionView)
|
||||
}
|
||||
|
||||
open class CarouselIndicator: Control {
|
||||
|
||||
open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Outlets
|
||||
//--------------------------------------------------
|
||||
@ -45,9 +55,12 @@ open class CarouselIndicator: Control {
|
||||
didSet { assignIndicatorView() }
|
||||
}
|
||||
|
||||
public var uiGestures: Set<UIGestureRecognizer> = []
|
||||
|
||||
/// The currently active indicator view.
|
||||
public var currentIndicator: IndicatorView?
|
||||
|
||||
/// Convenience to access the model.
|
||||
public var carouselIndicatorModel: CarouselIndicatorModel? {
|
||||
return model as? CarouselIndicatorModel
|
||||
}
|
||||
@ -65,14 +78,15 @@ open class CarouselIndicator: Control {
|
||||
}
|
||||
}
|
||||
|
||||
/// The maxmum count of pages before the indicatorView forces a numeric Indicator insead of Bar.
|
||||
/// The maxmum count of pages before the indicatorView forces a Numeric Indicator in place of Bar.
|
||||
public var hybridThreshold: Int = 5
|
||||
|
||||
/// Set this closure to perform an action when a different indicator was selected.
|
||||
public var indicatorTouchAction: ((Int)->())?
|
||||
/// Passes through oldInde and newIndex, respectively.
|
||||
public var indicatorTouchAction: CarouselIndicator.PagingTouchBlock?
|
||||
|
||||
/// Allows sendActions() to trigger even if index is min/max index.
|
||||
public var alwaysSendEvent = false
|
||||
/// Allows sendActions() to trigger even if index is already at min/max index.
|
||||
public var alwaysSendAction = 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 = false
|
||||
@ -91,17 +105,9 @@ open class CarouselIndicator: Control {
|
||||
didSet {
|
||||
isUserInteractionEnabled = isEnabled
|
||||
indicatorView?.isEnabled = isEnabled
|
||||
|
||||
if indicatorType != .bar && numberOfPages > hybridThreshold {
|
||||
|
||||
} else {
|
||||
if let stackView = indicatorView as? BarsIndicatorView {
|
||||
stackView.stackView.arrangedSubviews.forEach { ($0 as? BarsIndicatorView)?.isEnabled = isEnabled }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Computed Properties
|
||||
//--------------------------------------------------
|
||||
@ -112,21 +118,21 @@ open class CarouselIndicator: Control {
|
||||
public var currentIndex: Int {
|
||||
get { return _currentIndex }
|
||||
set (newIndex) {
|
||||
guard _currentIndex != newIndex else { return }
|
||||
if !allowIndexWraparound {
|
||||
guard _currentIndex != newIndex else { return }
|
||||
}
|
||||
|
||||
previousIndex = _currentIndex
|
||||
_currentIndex = newIndex
|
||||
sendActions(for: .valueChanged)
|
||||
indicatorTouchAction?(newIndex)
|
||||
indicatorView?.updateUI(previousIndex: previousIndex,
|
||||
newIndex: newIndex,
|
||||
totalCount: numberOfPages,
|
||||
isAnimated: isAnimated)
|
||||
performAction()
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
|
||||
private var _numberOfPages = 0
|
||||
|
||||
/// Holds the total number of pages displayed by the carousel.
|
||||
/// Updating this property will potentially update the UI.
|
||||
public var numberOfPages: Int {
|
||||
get { return _numberOfPages }
|
||||
set (newTotal) {
|
||||
@ -140,14 +146,7 @@ open class CarouselIndicator: Control {
|
||||
indicatorView = BarsIndicatorView()
|
||||
}
|
||||
|
||||
if alwaysSendEvent {
|
||||
sendActions(for: .valueChanged)
|
||||
}
|
||||
|
||||
indicatorView?.updateUI(previousIndex: previousIndex,
|
||||
newIndex: currentIndex,
|
||||
totalCount: newTotal,
|
||||
isAnimated: isAnimated)
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,6 +158,12 @@ open class CarouselIndicator: Control {
|
||||
get { return _indicatorTintColor }
|
||||
set (newColor) {
|
||||
_indicatorTintColor = newColor
|
||||
|
||||
if isBarIndicator(), let barIndicator = indicatorView as? BarsIndicatorView {
|
||||
for (i, barTuple) in barIndicator.barsReference.enumerated() where i != currentIndex {
|
||||
barTuple.view.backgroundColor = newColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,6 +174,12 @@ open class CarouselIndicator: Control {
|
||||
get { return _currentIndicatorColor }
|
||||
set (newColor) {
|
||||
_currentIndicatorColor = newColor
|
||||
|
||||
if isBarIndicator() {
|
||||
if let barIndicator = indicatorView as? BarsIndicatorView {
|
||||
barIndicator.barsReference[currentIndex].view.backgroundColor = newColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,9 +218,8 @@ open class CarouselIndicator: Control {
|
||||
open override func setupView() {
|
||||
super.setupView()
|
||||
|
||||
guard indicatorView == nil else { return }
|
||||
|
||||
assignIndicatorView()
|
||||
setupGestures()
|
||||
|
||||
if let accessibleValue = MVMCoreUIUtility.hardcodedString(withKey: accessibilityHasSlidesInsteadOfPage ? "MVMCoreUIPageControlslides_currentpage_index" : "MVMCoreUIPageControl_currentpage_index") {
|
||||
accessibilityValue = String(format: accessibleValue, currentIndex + 1, numberOfPages)
|
||||
@ -220,22 +230,49 @@ open class CarouselIndicator: Control {
|
||||
// MARK: - UITouch
|
||||
//--------------------------------------------------
|
||||
|
||||
@objc func pageValueIncrement() {
|
||||
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))
|
||||
|
||||
uiGestures.insert(tap)
|
||||
uiGestures.insert(leftSwipe)
|
||||
uiGestures.insert(rightSwipe)
|
||||
}
|
||||
|
||||
func incrementCurrentIndex() {
|
||||
currentIndex = min(currentIndex + 1, numberOfPages - 1)
|
||||
}
|
||||
|
||||
@objc func pageValueDecrement() {
|
||||
|
||||
func decrementCurrentIndex() {
|
||||
currentIndex = max(0, currentIndex - 1)
|
||||
}
|
||||
|
||||
func indicatorTapped(_ tapGesture: UITapGestureRecognizer?) {
|
||||
/// 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?) {
|
||||
|
||||
if isEnabled, let bars = (indicatorView as? BarsIndicatorView)?.barsReference {
|
||||
let touchPoint_X = tapGesture?.location(in: self).x ?? 0.0
|
||||
|
||||
let touchPoint = tapGesture?.location(in: self)
|
||||
let touchPoint_X = touchPoint?.x ?? 0.0
|
||||
|
||||
if isEnabled, indicatorType == .bar, let bars = (indicatorView as? BarsIndicatorView)?.barsReference {
|
||||
currentIndex = bars.firstIndex { $0.0.frame.maxX >= touchPoint_X && $0.0.frame.minX <= touchPoint_X } ?? 0
|
||||
} else {
|
||||
if touchPoint_X > bounds.width / 2 {
|
||||
incrementCurrentIndex()
|
||||
} else {
|
||||
decrementCurrentIndex()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,6 +280,20 @@ open class CarouselIndicator: Control {
|
||||
// MARK: - Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
public func updateUI() {
|
||||
|
||||
indicatorView?.updateUI(previousIndex: previousIndex,
|
||||
newIndex: currentIndex,
|
||||
totalCount: numberOfPages,
|
||||
isAnimated: isAnimated)
|
||||
}
|
||||
|
||||
public func performAction() {
|
||||
|
||||
sendActions(for: .valueChanged)
|
||||
indicatorTouchAction?(self)
|
||||
}
|
||||
|
||||
/// Sets the indicatorView based on the current indicatorType.
|
||||
func assignIndicatorView() {
|
||||
|
||||
@ -260,9 +311,13 @@ open class CarouselIndicator: Control {
|
||||
|
||||
/// Convenience to determine if current view is displaying bars.
|
||||
func isBarIndicator() -> Bool {
|
||||
|
||||
return indicatorType != .bar && numberOfPages > hybridThreshold
|
||||
}
|
||||
|
||||
public func scrollViewDidScroll(_ collectionView: UICollectionView) {
|
||||
|
||||
}
|
||||
//--------------------------------------------------
|
||||
// MARK: - MoleculeViewProtocol
|
||||
//--------------------------------------------------
|
||||
@ -284,20 +339,22 @@ open class CarouselIndicator: Control {
|
||||
//--------------------------------------------------
|
||||
|
||||
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) || alwaysSendEvent {
|
||||
if (index < numberOfPages && index >= 0) || alwaysSendAction {
|
||||
isAnimated = false
|
||||
previousIndex = currentIndex
|
||||
currentIndex = index
|
||||
sendActions(for: .valueChanged)
|
||||
indicatorTouchAction?(index)
|
||||
performAction()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -54,7 +54,6 @@ public class CarouselIndicatorModel: MoleculeModelProtocol {
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
currentBarColor = try typeContainer.decodeIfPresent(Color.self, forKey: .currentBarColor)
|
||||
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
||||
barsColor = try typeContainer.decodeIfPresent(Color.self, forKey: .barsColor)
|
||||
|
||||
@ -77,8 +77,6 @@ open class BarsIndicatorView: View, IndicatorViewProtocol {
|
||||
open override func setupView() {
|
||||
super.setupView()
|
||||
|
||||
guard subviews.isEmpty else { return }
|
||||
|
||||
addSubview(stackView)
|
||||
isUserInteractionEnabled = false
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import UIKit
|
||||
|
||||
open class NumericIndicatorView: View, IndicatorViewProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
// MARK: - Outlets
|
||||
//--------------------------------------------------
|
||||
|
||||
/// Text to display the current count of total pages for viewing.
|
||||
@ -22,7 +22,41 @@ open class NumericIndicatorView: View, IndicatorViewProtocol {
|
||||
return label
|
||||
}()
|
||||
|
||||
open var isEnabled: Bool = true
|
||||
let leftArrow: ImageView = {
|
||||
let arrow = UIImage(named: "peakingRightArrow")?.withRenderingMode(.alwaysTemplate).withHorizontallyFlippedOrientation()
|
||||
let imageView = ImageView(image: arrow)
|
||||
imageView.isUserInteractionEnabled = true
|
||||
imageView.tintColor = .mvmBlack
|
||||
return imageView
|
||||
}()
|
||||
|
||||
let rightArrow: ImageView = {
|
||||
let arrow = UIImage(named: "peakingRightArrow")?.withRenderingMode(.alwaysTemplate)
|
||||
let imageView = ImageView(image: arrow)
|
||||
imageView.isUserInteractionEnabled = true
|
||||
imageView.tintColor = .mvmBlack
|
||||
return imageView
|
||||
}()
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
open var isEnabled: Bool = true {
|
||||
didSet {
|
||||
titleLabel.isEnabled = isEnabled
|
||||
leftArrow.tintColor = isEnabled ? enabledColor : disabledColor
|
||||
rightArrow.tintColor = isEnabled ? enabledColor : disabledColor
|
||||
}
|
||||
}
|
||||
|
||||
public var enabledColor: UIColor {
|
||||
return (superview as? CarouselIndicator)?.indicatorTintColor ?? .black
|
||||
}
|
||||
|
||||
public var disabledColor: UIColor {
|
||||
return (superview as? CarouselIndicator)?.disabledIndicatorColor ?? .mvmCoolGray3
|
||||
}
|
||||
|
||||
public var parentCarouselIndicator: CarouselIndicator? {
|
||||
return superview as? CarouselIndicator
|
||||
@ -74,23 +108,18 @@ open class NumericIndicatorView: View, IndicatorViewProtocol {
|
||||
open override func setupView() {
|
||||
super.setupView()
|
||||
|
||||
guard subviews.isEmpty else { return }
|
||||
|
||||
isUserInteractionEnabled = false
|
||||
leftArrow.tintColor = isEnabled ? enabledColor : disabledColor
|
||||
rightArrow.tintColor = isEnabled ? enabledColor : disabledColor
|
||||
|
||||
addSubview(titleLabel)
|
||||
titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
|
||||
NSLayoutConstraint.constraintPinSubview(titleLabel, pinTop: true, pinBottom: true, pinLeft: false, pinRight: false)
|
||||
|
||||
let arrow = UIImage(named: "peakingRightArrow")?.withHorizontallyFlippedOrientation()
|
||||
let leftArrow = UIImageView(image: arrow)
|
||||
leftArrow.isUserInteractionEnabled = true
|
||||
|
||||
addSubview(leftArrow)
|
||||
NSLayoutConstraint.constraintPinView(leftArrow, heightConstraint: true, heightConstant: PaddingTwo, widthConstraint: true, widthConstant: PaddingTwo)
|
||||
leftArrow.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
|
||||
|
||||
let rightArrow = UIImageView(image: UIImage(named: "peakingRightArrow"))
|
||||
addSubview(rightArrow)
|
||||
|
||||
NSLayoutConstraint.constraintPinView(rightArrow, heightConstraint: true, heightConstant: PaddingTwo, widthConstraint: true, widthConstant: PaddingTwo)
|
||||
|
||||
102
MVMCoreUI/BaseClasses/ImageView.swift
Normal file
102
MVMCoreUI/BaseClasses/ImageView.swift
Normal file
@ -0,0 +1,102 @@
|
||||
//
|
||||
// ImageView.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Kevin Christiano on 2/13/20.
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ImageView: UIImageView, ModelMoleculeViewProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
open var json: [AnyHashable: Any]?
|
||||
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()
|
||||
}
|
||||
|
||||
public func initialSetup() {
|
||||
if !initialSetupPerformed {
|
||||
initialSetupPerformed = true
|
||||
setupView()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK:- ModelMoleculeViewProtocol
|
||||
open func setWithModel(_ 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 setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
self.json = json
|
||||
|
||||
if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) {
|
||||
backgroundColor = UIColor.mfGet(forHex: backgroundColorString)
|
||||
}
|
||||
}
|
||||
|
||||
open func reset() {
|
||||
backgroundColor = .clear
|
||||
}
|
||||
|
||||
open func setAsMolecule() { }
|
||||
}
|
||||
|
||||
@ -9,6 +9,10 @@
|
||||
import UIKit
|
||||
|
||||
@objcMembers open class View: UIView, ModelMoleculeViewProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
open var json: [AnyHashable: Any]?
|
||||
open var model: MoleculeModelProtocol?
|
||||
|
||||
|
||||
@ -9,6 +9,9 @@
|
||||
import UIKit
|
||||
|
||||
open class Carousel: View {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
|
||||
|
||||
@ -45,13 +48,15 @@ open class Carousel: View {
|
||||
var loop = false
|
||||
private var dragging = false
|
||||
|
||||
// For adding pager
|
||||
/// For adding pager
|
||||
private var bottomPin: NSLayoutConstraint?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - MVMCoreViewProtocol
|
||||
//--------------------------------------------------
|
||||
|
||||
open override func setupView() {
|
||||
super.setupView()
|
||||
guard collectionView.superview == nil else { return }
|
||||
|
||||
collectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
collectionView.dataSource = self
|
||||
@ -68,6 +73,7 @@ open class Carousel: View {
|
||||
|
||||
open override func updateView(_ size: CGFloat) {
|
||||
super.updateView(size)
|
||||
|
||||
collectionView.collectionViewLayout.invalidateLayout()
|
||||
showPeaking(false)
|
||||
|
||||
@ -79,7 +85,9 @@ open class Carousel: View {
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol
|
||||
//--------------------------------------------------
|
||||
|
||||
public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||
super.setWithModel(model, delegateObject, additionalData)
|
||||
@ -106,10 +114,13 @@ open class Carousel: View {
|
||||
collectionView.reloadData()
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - JSON Setters
|
||||
/// Updates the layout being used
|
||||
//--------------------------------------------------
|
||||
|
||||
/// Updates the layout being used
|
||||
func setupLayout(with carouselModel: CarouselModel?) {
|
||||
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.scrollDirection = .horizontal
|
||||
layout.minimumLineSpacing = CGFloat(carouselModel?.spacing ?? 1)
|
||||
@ -126,6 +137,7 @@ open class Carousel: View {
|
||||
|
||||
numberOfPages = newMolecules.count
|
||||
molecules = newMolecules
|
||||
|
||||
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).
|
||||
loop = true
|
||||
@ -134,6 +146,7 @@ open class Carousel: View {
|
||||
molecules?.append(newMolecules.first!)
|
||||
molecules?.append(newMolecules[1])
|
||||
}
|
||||
|
||||
pageIndex = 0
|
||||
}
|
||||
|
||||
@ -144,6 +157,7 @@ open class Carousel: View {
|
||||
if let molecule = molecule {
|
||||
pagingView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(molecule, delegateObject, false) as? (UIView & MVMCoreUIPagingProtocol)
|
||||
}
|
||||
|
||||
addPaging(view: pagingView, position: (CGFloat(molecule?.position ?? 20)))
|
||||
}
|
||||
|
||||
@ -156,7 +170,10 @@ open class Carousel: View {
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Convenience
|
||||
//--------------------------------------------------
|
||||
|
||||
/// Returns the (identifier, class) of the molecule for the given map.
|
||||
func getMoleculeInfo(with molecule: MoleculeModelProtocol, delegateObject: MVMCoreUIDelegateObject?) -> (identifier: String, class: AnyClass, molecule: MoleculeModelProtocol)? {
|
||||
guard let className = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(molecule),
|
||||
@ -182,6 +199,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 & MVMCoreUIPagingProtocol)?, position: CGFloat) {
|
||||
|
||||
pagingView?.removeFromSuperview()
|
||||
guard let pagingView = view else {
|
||||
bottomPin?.isActive = false
|
||||
@ -189,6 +207,7 @@ open class Carousel: View {
|
||||
bottomPin?.isActive = true
|
||||
return
|
||||
}
|
||||
|
||||
pagingView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(pagingView)
|
||||
pagingView.centerXAnchor.constraint(equalTo: collectionView.centerXAnchor).isActive = true
|
||||
@ -201,15 +220,13 @@ open class Carousel: View {
|
||||
|
||||
pagingView.setNumberOfPages(numberOfPages)
|
||||
(pagingView as? MVMCoreUIViewConstrainingProtocol)?.alignHorizontal?(.fill)
|
||||
pagingView.setPagingTouch { [weak self] (pager) in
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
||||
guard let localSelf = self else {
|
||||
return
|
||||
}
|
||||
pagingView.setPagingTouch { [weak self] pager in
|
||||
DispatchQueue.main.async {
|
||||
guard let self = self else { return }
|
||||
let currentPage = pager.currentPage()
|
||||
localSelf.pageIndex = currentPage
|
||||
localSelf.goTo(localSelf.currentIndex, animated: !UIAccessibility.isVoiceOverRunning)
|
||||
})
|
||||
self.pageIndex = currentPage
|
||||
self.goTo(self.currentIndex, animated: !UIAccessibility.isVoiceOverRunning)
|
||||
}
|
||||
}
|
||||
self.pagingView = pagingView
|
||||
}
|
||||
@ -246,7 +263,7 @@ open class Carousel: View {
|
||||
array?.append(pagingView!)
|
||||
}
|
||||
|
||||
self.accessibilityElements = array
|
||||
accessibilityElements = array
|
||||
} else {
|
||||
cell.accessibilityElementsHidden = true
|
||||
}
|
||||
@ -254,6 +271,7 @@ open class Carousel: View {
|
||||
}
|
||||
|
||||
extension Carousel: UICollectionViewDelegateFlowLayout {
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||
let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent)
|
||||
return CGSize(width: itemWidth, height: collectionView.bounds.height)
|
||||
@ -265,6 +283,7 @@ extension Carousel: UICollectionViewDelegateFlowLayout {
|
||||
}
|
||||
|
||||
extension Carousel: UICollectionViewDataSource {
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return molecules?.count ?? 0
|
||||
}
|
||||
@ -280,6 +299,7 @@ extension Carousel: UICollectionViewDataSource {
|
||||
protocolCell.setWithModel(moleculeInfo.molecule, nil, nil)
|
||||
protocolCell.updateView(collectionView.bounds.width)
|
||||
}
|
||||
|
||||
setAccessiblity(cell, index: indexPath.row)
|
||||
return cell
|
||||
}
|
||||
@ -290,20 +310,22 @@ extension Carousel: UIScrollViewDelegate {
|
||||
func goTo(_ index: Int, animated: Bool) {
|
||||
|
||||
showPeaking(false)
|
||||
setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index)
|
||||
self.currentIndex = index
|
||||
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: animated)
|
||||
if let cell = collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)) {
|
||||
setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index)
|
||||
setAccessiblity(collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)), index: index)
|
||||
currentIndex = index
|
||||
collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: itemAlignment, animated: animated)
|
||||
|
||||
if let cell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) {
|
||||
setAccessiblity(collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)), index: index)
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: cell)
|
||||
}
|
||||
}
|
||||
|
||||
func handleUserOnBufferCell() {
|
||||
|
||||
guard loop else { return }
|
||||
|
||||
let lastPageIndex = numberOfPages + 1
|
||||
let goToIndex = {(index: Int) in
|
||||
let goToIndex = { (index: Int) in
|
||||
self.goTo(index, animated: false)
|
||||
self.collectionView.layoutIfNeeded()
|
||||
self.pagingView?.setPage(self.pageIndex)
|
||||
@ -319,6 +341,7 @@ extension Carousel: UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
func checkForDraggingOutOfBounds(_ scrollView: UIScrollView) {
|
||||
|
||||
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.
|
||||
@ -326,10 +349,12 @@ extension Carousel: UIScrollViewDelegate {
|
||||
let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent)
|
||||
let index = scrollView.contentOffset.x / (itemWidth + separatorWidth)
|
||||
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
||||
|
||||
if index < 1 {
|
||||
self.currentIndex = 0
|
||||
currentIndex = 0
|
||||
|
||||
} else if index > CGFloat(lastCellIndex - 1) {
|
||||
self.currentIndex = lastCellIndex
|
||||
currentIndex = lastCellIndex
|
||||
}
|
||||
}
|
||||
|
||||
@ -379,9 +404,7 @@ extension Carousel: UIScrollViewDelegate {
|
||||
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
|
||||
// Cycle to other end if on buffer cell.
|
||||
handleUserOnBufferCell()
|
||||
|
||||
pagingView?.setPage(pageIndex)
|
||||
|
||||
showPeaking(true)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user