Merge branch 'develop' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui into feature/audio_player_behavior
# Conflicts: # MVMCoreUI/Behaviors/GetContactBehavior.swift
This commit is contained in:
commit
37f506cd0e
@ -348,6 +348,7 @@
|
|||||||
D236E5B4241FEB1000C38625 /* ListTwoColumnPriceDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B2241FEB1000C38625 /* ListTwoColumnPriceDescription.swift */; };
|
D236E5B4241FEB1000C38625 /* ListTwoColumnPriceDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B2241FEB1000C38625 /* ListTwoColumnPriceDescription.swift */; };
|
||||||
D236E5B5241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B3241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift */; };
|
D236E5B5241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B3241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift */; };
|
||||||
D236E5B7242007C500C38625 /* MVMControllerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B6242007C500C38625 /* MVMControllerModelProtocol.swift */; };
|
D236E5B7242007C500C38625 /* MVMControllerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B6242007C500C38625 /* MVMControllerModelProtocol.swift */; };
|
||||||
|
D23A8FD9260CE004007E14CE /* MFStyler+PaddingExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FD5260CDF01007E14CE /* MFStyler+PaddingExtension.swift */; };
|
||||||
D23A8FEB26122F69007E14CE /* VisibleBehaviorForVideoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FEA26122F69007E14CE /* VisibleBehaviorForVideoModel.swift */; };
|
D23A8FEB26122F69007E14CE /* VisibleBehaviorForVideoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FEA26122F69007E14CE /* VisibleBehaviorForVideoModel.swift */; };
|
||||||
D23A8FEE26122F7D007E14CE /* VisibleBehaviorForVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */; };
|
D23A8FEE26122F7D007E14CE /* VisibleBehaviorForVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */; };
|
||||||
D23A8FF82612308D007E14CE /* PageBehaviorProtocolRequirer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */; };
|
D23A8FF82612308D007E14CE /* PageBehaviorProtocolRequirer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */; };
|
||||||
@ -912,6 +913,7 @@
|
|||||||
D236E5B2241FEB1000C38625 /* ListTwoColumnPriceDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTwoColumnPriceDescription.swift; sourceTree = "<group>"; };
|
D236E5B2241FEB1000C38625 /* ListTwoColumnPriceDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTwoColumnPriceDescription.swift; sourceTree = "<group>"; };
|
||||||
D236E5B3241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTwoColumnPriceDescriptionModel.swift; sourceTree = "<group>"; };
|
D236E5B3241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTwoColumnPriceDescriptionModel.swift; sourceTree = "<group>"; };
|
||||||
D236E5B6242007C500C38625 /* MVMControllerModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMControllerModelProtocol.swift; sourceTree = "<group>"; };
|
D236E5B6242007C500C38625 /* MVMControllerModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMControllerModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
D23A8FD5260CDF01007E14CE /* MFStyler+PaddingExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MFStyler+PaddingExtension.swift"; sourceTree = "<group>"; };
|
||||||
D23A8FEA26122F69007E14CE /* VisibleBehaviorForVideoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleBehaviorForVideoModel.swift; sourceTree = "<group>"; };
|
D23A8FEA26122F69007E14CE /* VisibleBehaviorForVideoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleBehaviorForVideoModel.swift; sourceTree = "<group>"; };
|
||||||
D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleBehaviorForVideo.swift; sourceTree = "<group>"; };
|
D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleBehaviorForVideo.swift; sourceTree = "<group>"; };
|
||||||
D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorProtocolRequirer.swift; sourceTree = "<group>"; };
|
D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorProtocolRequirer.swift; sourceTree = "<group>"; };
|
||||||
@ -2041,6 +2043,7 @@
|
|||||||
D29DF13921E68637003B2FB9 /* MFStyler.m */,
|
D29DF13921E68637003B2FB9 /* MFStyler.m */,
|
||||||
0A6682A92435125F00AD3CA1 /* Styler.swift */,
|
0A6682A92435125F00AD3CA1 /* Styler.swift */,
|
||||||
0A6682AB243531C300AD3CA1 /* Padding.swift */,
|
0A6682AB243531C300AD3CA1 /* Padding.swift */,
|
||||||
|
D23A8FD5260CDF01007E14CE /* MFStyler+PaddingExtension.swift */,
|
||||||
);
|
);
|
||||||
path = Styles;
|
path = Styles;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2888,6 +2891,7 @@
|
|||||||
D253BB9C245874F8002DE544 /* BGImageMolecule.swift in Sources */,
|
D253BB9C245874F8002DE544 /* BGImageMolecule.swift in Sources */,
|
||||||
D2C78CD224228BBD00B69FDE /* ActionOpenPanelModel.swift in Sources */,
|
D2C78CD224228BBD00B69FDE /* ActionOpenPanelModel.swift in Sources */,
|
||||||
AA617AB02453010A00910B8F /* ListDeviceComplexLinkSmall.swift in Sources */,
|
AA617AB02453010A00910B8F /* ListDeviceComplexLinkSmall.swift in Sources */,
|
||||||
|
D23A8FD9260CE004007E14CE /* MFStyler+PaddingExtension.swift in Sources */,
|
||||||
C695A68123C9830D00BFB94E /* NumberedListModel.swift in Sources */,
|
C695A68123C9830D00BFB94E /* NumberedListModel.swift in Sources */,
|
||||||
01EB3684236097C0006832FA /* MoleculeModelProtocol.swift in Sources */,
|
01EB3684236097C0006832FA /* MoleculeModelProtocol.swift in Sources */,
|
||||||
D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */,
|
D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */,
|
||||||
|
|||||||
@ -59,12 +59,6 @@ open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol {
|
|||||||
// MARK: - Lifecycle
|
// MARK: - Lifecycle
|
||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
|
|
||||||
override open func layoutSubviews() {
|
|
||||||
|
|
||||||
addCaretImageView()
|
|
||||||
super.layoutSubviews()
|
|
||||||
}
|
|
||||||
|
|
||||||
override public var isEnabled: Bool {
|
override public var isEnabled: Bool {
|
||||||
didSet { changeCaretColor() }
|
didSet { changeCaretColor() }
|
||||||
}
|
}
|
||||||
@ -135,7 +129,7 @@ open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol {
|
|||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
|
|
||||||
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||||
|
addCaretImageView()
|
||||||
guard let model = model as? CaretLinkModel else { return }
|
guard let model = model as? CaretLinkModel else { return }
|
||||||
|
|
||||||
if let color = model.backgroundColor {
|
if let color = model.backgroundColor {
|
||||||
@ -150,6 +144,11 @@ open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol {
|
|||||||
setTitle(model.title, for: .normal)
|
setTitle(model.title, for: .normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open override func reset() {
|
||||||
|
super.reset()
|
||||||
|
rightView?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
public func needsToBeConstrained() -> Bool { true }
|
public func needsToBeConstrained() -> Bool { true }
|
||||||
|
|
||||||
open func horizontalAlignment() -> UIStackView.Alignment { .leading }
|
open func horizontalAlignment() -> UIStackView.Alignment { .leading }
|
||||||
|
|||||||
@ -168,10 +168,14 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open override var intrinsicContentSize: CGSize {
|
open override var intrinsicContentSize: CGSize {
|
||||||
|
if buttonSize == .tiny {
|
||||||
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())
|
||||||
|
} else {
|
||||||
|
let width = Padding.Component.gutterForApplicationWidth + (2.0 * Padding.Component.columnFor(size: MVMCoreUISplitViewController.getApplicationViewWidth()))
|
||||||
|
return CGSize(width: min(292, width), height: getHeight())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|||||||
@ -14,7 +14,7 @@ open class RadioBoxes: View {
|
|||||||
public var collectionViewHeight: NSLayoutConstraint!
|
public var collectionViewHeight: NSLayoutConstraint!
|
||||||
private let boxWidth: CGFloat = 151.0
|
private let boxWidth: CGFloat = 151.0
|
||||||
private let boxHeight: CGFloat = 64.0
|
private let boxHeight: CGFloat = 64.0
|
||||||
private let itemSpacing: CGFloat = 8.0
|
private var itemSpacing: CGFloat = 12.0
|
||||||
private var numberOfColumns: CGFloat = 2.0
|
private var numberOfColumns: CGFloat = 2.0
|
||||||
private var radioBoxesModel: RadioBoxesModel? {
|
private var radioBoxesModel: RadioBoxesModel? {
|
||||||
return model as? RadioBoxesModel
|
return model as? RadioBoxesModel
|
||||||
@ -71,6 +71,7 @@ open class RadioBoxes: View {
|
|||||||
@objc override open func updateView(_ size: CGFloat) {
|
@objc override open func updateView(_ size: CGFloat) {
|
||||||
super.updateView(size)
|
super.updateView(size)
|
||||||
self.size = size
|
self.size = size
|
||||||
|
itemSpacing = Padding.Component.gutterFor(size: size)
|
||||||
collectionView.updateView(size)
|
collectionView.updateView(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,37 @@ open class BarsIndicatorView: CarouselIndicator {
|
|||||||
// MARK: - Stored Properties
|
// MARK: - Stored Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
/// Represents the data of each individual bar.
|
||||||
|
private class IndicatorBar: View {
|
||||||
|
// Dimensions are based on InVision Design Guidelines.
|
||||||
|
static let width: CGFloat = 24
|
||||||
|
static let selectedHeight: CGFloat = 4
|
||||||
|
static let unselectedHeight: CGFloat = 1
|
||||||
|
|
||||||
|
var constraint: NSLayoutConstraint?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
super.init(frame: .zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
public required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
public required init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
|
||||||
|
fatalError("init(model:_:_:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func setupView() {
|
||||||
|
super.setupView()
|
||||||
|
isAccessibilityElement = true
|
||||||
|
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccTabHint")
|
||||||
|
widthAnchor.constraint(equalToConstant: BarsIndicatorView.IndicatorBar.width).isActive = true
|
||||||
|
accessibilityTraits = .button
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Structured container to hold and layout the indicator bars.
|
||||||
public let stackView: UIStackView = {
|
public let stackView: UIStackView = {
|
||||||
let stackView = UIStackView()
|
let stackView = UIStackView()
|
||||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
@ -25,11 +56,8 @@ open class BarsIndicatorView: CarouselIndicator {
|
|||||||
return stackView
|
return stackView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
public var barReferences: [(view: View, constraint: NSLayoutConstraint)] = []
|
/// Reference to each bar displayed.
|
||||||
|
private var barReferences: [IndicatorBar] = []
|
||||||
// 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
|
// MARK: - Computed Properties
|
||||||
@ -37,16 +65,16 @@ open class BarsIndicatorView: CarouselIndicator {
|
|||||||
|
|
||||||
/// Convenience to access the model.
|
/// Convenience to access the model.
|
||||||
public var barsCarouselIndicatorModel: BarsCarouselIndicatorModel? {
|
public var barsCarouselIndicatorModel: BarsCarouselIndicatorModel? {
|
||||||
return model as? BarsCarouselIndicatorModel
|
model as? BarsCarouselIndicatorModel
|
||||||
}
|
}
|
||||||
|
|
||||||
open override var isEnabled: Bool {
|
open override var isEnabled: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
for (i, bar) in barReferences.enumerated() {
|
for (i, indicatorBar) in barReferences.enumerated() {
|
||||||
if i == currentIndex {
|
if i == currentIndex {
|
||||||
bar.view.backgroundColor = isEnabled ? currentIndicatorColor : disabledIndicatorColor
|
indicatorBar.backgroundColor = isEnabled ? currentIndicatorColor : disabledIndicatorColor
|
||||||
} else {
|
} else {
|
||||||
bar.view.backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor
|
indicatorBar.backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,21 +82,22 @@ open class BarsIndicatorView: CarouselIndicator {
|
|||||||
|
|
||||||
/// Colors the currently selected index, unique from other indicators
|
/// Colors the currently selected index, unique from other indicators
|
||||||
public var currentIndicatorColor: UIColor {
|
public var currentIndicatorColor: UIColor {
|
||||||
get { return barsCarouselIndicatorModel?.currentIndicatorColor.uiColor ?? indicatorColor }
|
get { barsCarouselIndicatorModel?.currentIndicatorColor.uiColor ?? indicatorColor }
|
||||||
set (newColor) {
|
set {
|
||||||
barsCarouselIndicatorModel?.currentIndicatorColor = Color(uiColor: newColor)
|
barsCarouselIndicatorModel?.currentIndicatorColor = Color(uiColor: newValue)
|
||||||
|
|
||||||
if isEnabled && !barReferences.isEmpty {
|
if isEnabled && !barReferences.isEmpty {
|
||||||
barReferences[currentIndex].view.backgroundColor = newColor
|
barReferences[currentIndex].backgroundColor = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The color of the indicator bars.
|
||||||
public override var indicatorColor: UIColor {
|
public override var indicatorColor: UIColor {
|
||||||
get { return super.indicatorColor }
|
get { super.indicatorColor }
|
||||||
set (newColor) {
|
set {
|
||||||
super.indicatorColor = newColor
|
super.indicatorColor = newValue
|
||||||
refreshBarColors(with: newColor)
|
refreshBarColors(with: newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +113,7 @@ open class BarsIndicatorView: CarouselIndicator {
|
|||||||
isAccessibilityElement = false
|
isAccessibilityElement = false
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
stackView.heightAnchor.constraint(equalToConstant: 4),
|
stackView.heightAnchor.constraint(equalToConstant: Padding.One),
|
||||||
heightAnchor.constraint(equalTo: stackView.heightAnchor),
|
heightAnchor.constraint(equalTo: stackView.heightAnchor),
|
||||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
stackView.topAnchor.constraint(equalTo: topAnchor),
|
stackView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
@ -97,44 +126,95 @@ open class BarsIndicatorView: CarouselIndicator {
|
|||||||
// MARK: - Methods
|
// MARK: - Methods
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
/// Updates the color of all indicator bars.
|
||||||
private func refreshBarColors(with color: UIColor) {
|
private func refreshBarColors(with color: UIColor) {
|
||||||
|
|
||||||
if isEnabled {
|
guard isEnabled else { return }
|
||||||
for (i, barTuple) in barReferences.enumerated() {
|
|
||||||
barTuple.view.backgroundColor = i == currentIndex ? currentIndicatorColor : color
|
for (i, indicatorBar) in barReferences.enumerated() {
|
||||||
}
|
indicatorBar.backgroundColor = i == currentIndex ? currentIndicatorColor : color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates all the indicato bars for display.
|
||||||
func generateBars() {
|
func generateBars() {
|
||||||
|
|
||||||
var bars = [(View, NSLayoutConstraint)]()
|
var bars = [IndicatorBar]()
|
||||||
|
|
||||||
for i in 0..<numberOfPages {
|
for i in 0..<numberOfPages {
|
||||||
let bar = View()
|
let indicatorBar = createIndicatorBar(index: i)
|
||||||
bar.isAccessibilityElement = true
|
stackView.addArrangedSubview(indicatorBar)
|
||||||
if let accessibleValueFormat = accessibilityValueFormat, let accessibleIndex = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: i + 1)) {
|
bars.append(indicatorBar)
|
||||||
bar.accessibilityLabel = String(format: accessibleValueFormat, accessibleIndex, numberOfPages)
|
|
||||||
}
|
|
||||||
bar.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccTabHint")
|
|
||||||
bar.accessibilityTraits = .button
|
|
||||||
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
|
accessibilityElements = stackView.arrangedSubviews
|
||||||
barReferences = bars
|
barReferences = bars
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes an indicator bar from the view.
|
||||||
|
private func removeBar() {
|
||||||
|
let lastView = barReferences.removeLast()
|
||||||
|
stackView.removeArrangedSubview(lastView)
|
||||||
|
lastView.removeFromSuperview()
|
||||||
|
accessibilityElements = stackView.arrangedSubviews
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Based of the sign of the difference indicator bars will be removed or added.
|
||||||
|
private func balanceBarCount(_ difference: Int) {
|
||||||
|
|
||||||
|
if difference > 0 {
|
||||||
|
// If positive, add n bars.
|
||||||
|
var copyBars = barReferences
|
||||||
|
|
||||||
|
for _ in 0..<difference {
|
||||||
|
let indicatorBar = createIndicatorBar(index: barReferences.count)
|
||||||
|
indicatorBar.constraint?.constant = IndicatorBar.unselectedHeight
|
||||||
|
stackView.addArrangedSubview(indicatorBar)
|
||||||
|
copyBars.append(indicatorBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
accessibilityElements = stackView.arrangedSubviews
|
||||||
|
barReferences = copyBars
|
||||||
|
|
||||||
|
} else if difference < 0 {
|
||||||
|
// If negative, remove n bars.
|
||||||
|
for _ in 0..<(difference * -1) {
|
||||||
|
removeBar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createIndicatorBar(index: Int) -> IndicatorBar {
|
||||||
|
|
||||||
|
let bar = IndicatorBar()
|
||||||
|
setAccessibilityLabel(view: bar, index: index)
|
||||||
|
bar.backgroundColor = isEnabled ? (index == currentIndex ? currentIndicatorColor : indicatorColor) : disabledIndicatorColor
|
||||||
|
let barHeight = index == currentIndex ? BarsIndicatorView.IndicatorBar.selectedHeight : BarsIndicatorView.IndicatorBar.unselectedHeight
|
||||||
|
let heightConstraint = bar.heightAnchor.constraint(equalToConstant: barHeight)
|
||||||
|
heightConstraint.isActive = true
|
||||||
|
bar.constraint = heightConstraint
|
||||||
|
|
||||||
|
return bar
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refreshes the accessibility labels to read "x of n".
|
||||||
|
private func refreshAccessibilityLabels() {
|
||||||
|
for i in 0..<barReferences.count {
|
||||||
|
setAccessibilityLabel(view: barReferences[i], index: i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setAccessibilityLabel(view: UIView, index: Int) {
|
||||||
|
guard let accessibleValueFormat = accessibilityValueFormat,
|
||||||
|
let accessibleIndex = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: index + 1))
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
view.accessibilityLabel = String(format: accessibleValueFormat, accessibleIndex, numberOfPages)
|
||||||
|
}
|
||||||
|
|
||||||
public override func assessTouchOf(_ touchPoint_X: CGFloat) {
|
public override func assessTouchOf(_ touchPoint_X: CGFloat) {
|
||||||
|
|
||||||
currentIndex = barReferences.firstIndex { $0.0.frame.maxX >= touchPoint_X && $0.0.frame.minX <= touchPoint_X } ?? 0
|
currentIndex = barReferences.firstIndex { $0.frame.maxX >= touchPoint_X && $0.frame.minX <= touchPoint_X } ?? 0
|
||||||
performAction()
|
performAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,27 +232,40 @@ open class BarsIndicatorView: CarouselIndicator {
|
|||||||
|
|
||||||
public override func reset() {
|
public override func reset() {
|
||||||
super.reset()
|
super.reset()
|
||||||
barReferences.forEach { $0.view.removeFromSuperview() }
|
barReferences.forEach { $0.removeFromSuperview() }
|
||||||
barReferences = []
|
barReferences = []
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func updateUI(previousIndex: Int, newIndex: Int, totalCount: Int, isAnimated: Bool) {
|
public override func updateUI(previousIndex: Int, newIndex: Int, totalCount: Int, isAnimated: Bool) {
|
||||||
|
|
||||||
guard newIndex < totalCount else { return }
|
// Ensure that at least one bar exists.
|
||||||
|
|
||||||
guard !barReferences.isEmpty else {
|
guard !barReferences.isEmpty else {
|
||||||
generateBars()
|
generateBars()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let expression = {
|
guard newIndex < totalCount else { return }
|
||||||
self.barReferences[previousIndex].view.backgroundColor = self.isEnabled ? self.indicatorColor : self.disabledIndicatorColor
|
|
||||||
self.barReferences[newIndex].view.backgroundColor = self.isEnabled ? self.currentIndicatorColor : self.disabledIndicatorColor
|
// Ensure the number of pages matches the number of bar references.
|
||||||
self.barReferences[previousIndex].constraint.constant = BarsIndicatorView.indicatorBarHeight.unselected
|
if (totalCount - barReferences.count) != 0 {
|
||||||
self.barReferences[newIndex].constraint.constant = BarsIndicatorView.indicatorBarHeight.selected
|
barReferences.forEach {
|
||||||
self.layoutIfNeeded()
|
$0.backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor
|
||||||
|
$0.constraint?.constant = IndicatorBar.unselectedHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
balanceBarCount(numberOfPages - barReferences.count)
|
||||||
|
refreshAccessibilityLabels()
|
||||||
}
|
}
|
||||||
|
|
||||||
isAnimated ? UIView.animate(withDuration: 0.3) { expression() } : expression()
|
let expression = { [self] in
|
||||||
|
barReferences[previousIndex].backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor
|
||||||
|
barReferences[previousIndex].constraint?.constant = IndicatorBar.unselectedHeight
|
||||||
|
barReferences[newIndex].backgroundColor = isEnabled ? currentIndicatorColor : disabledIndicatorColor
|
||||||
|
barReferences[newIndex].constraint?.constant = IndicatorBar.selectedHeight
|
||||||
|
layoutIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the animation.
|
||||||
|
isAnimated ? UIView.animate(withDuration: 0.25) { expression() } : expression()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,8 +6,6 @@
|
|||||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
|
|
||||||
open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -25,7 +23,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
|||||||
|
|
||||||
/// Convenience to access the model.
|
/// Convenience to access the model.
|
||||||
public var carouselIndicatorModel: CarouselIndicatorModel? {
|
public var carouselIndicatorModel: CarouselIndicatorModel? {
|
||||||
return model as? CarouselIndicatorModel
|
model as? CarouselIndicatorModel
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set this closure to perform an action when a different indicator was selected.
|
/// Set this closure to perform an action when a different indicator was selected.
|
||||||
@ -43,7 +41,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
|||||||
private(set) var previousIndex = 0
|
private(set) var previousIndex = 0
|
||||||
|
|
||||||
public var currentIndex: Int {
|
public var currentIndex: Int {
|
||||||
get { return carouselIndicatorModel?.currentIndex ?? 0 }
|
get { carouselIndicatorModel?.currentIndex ?? 0 }
|
||||||
set (newIndex) {
|
set (newIndex) {
|
||||||
previousIndex = currentIndex
|
previousIndex = currentIndex
|
||||||
carouselIndicatorModel?.currentIndex = newIndex
|
carouselIndicatorModel?.currentIndex = newIndex
|
||||||
@ -58,22 +56,22 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
|||||||
/// Holds the total number of pages displayed by the carousel.
|
/// Holds the total number of pages displayed by the carousel.
|
||||||
/// Updating this property will potentially update the UI.
|
/// Updating this property will potentially update the UI.
|
||||||
public var numberOfPages: Int {
|
public var numberOfPages: Int {
|
||||||
get { return carouselIndicatorModel?.numberOfPages ?? 0 }
|
get { carouselIndicatorModel?.numberOfPages ?? 0 }
|
||||||
set (newTotal) {
|
set {
|
||||||
guard numberOfPages != newTotal else { return }
|
guard numberOfPages != newValue else { return }
|
||||||
|
|
||||||
carouselIndicatorModel?.numberOfPages = newTotal
|
carouselIndicatorModel?.numberOfPages = newValue
|
||||||
reset()
|
reset()
|
||||||
isHidden = (carouselIndicatorModel?.hidesForSinglePage ?? false) && newTotal <= 1
|
isHidden = (carouselIndicatorModel?.hidesForSinglePage ?? false) && newValue <= 1
|
||||||
updateUI(previousIndex: previousIndex,
|
updateUI(previousIndex: previousIndex,
|
||||||
newIndex: currentIndex,
|
newIndex: currentIndex,
|
||||||
totalCount: newTotal,
|
totalCount: newValue,
|
||||||
isAnimated: carouselIndicatorModel?.animated ?? true)
|
isAnimated: carouselIndicatorModel?.animated ?? true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var disabledIndicatorColor: UIColor {
|
public var disabledIndicatorColor: UIColor {
|
||||||
get { return carouselIndicatorModel?.disabledIndicatorColor.uiColor ?? .mvmCoolGray3 }
|
get { carouselIndicatorModel?.disabledIndicatorColor.uiColor ?? .mvmCoolGray3 }
|
||||||
set { carouselIndicatorModel?.disabledIndicatorColor = Color(uiColor: newValue) }
|
set { carouselIndicatorModel?.disabledIndicatorColor = Color(uiColor: newValue) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +90,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var accessibilityValueFormat: String? {
|
var accessibilityValueFormat: String? {
|
||||||
return MVMCoreUIUtility.hardcodedString(withKey: (carouselIndicatorModel?.accessibilityHasSlidesInsteadOfPage ?? false) ? "MVMCoreUIPageControlslides_currentpage_index" : "MVMCoreUIPageControl_currentpage_index")
|
MVMCoreUIUtility.hardcodedString(withKey: (carouselIndicatorModel?.accessibilityHasSlidesInsteadOfPage ?? false) ? "MVMCoreUIPageControlslides_currentpage_index" : "MVMCoreUIPageControl_currentpage_index")
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -175,12 +173,14 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
|||||||
assessTouchOf(touchPoint_X)
|
assessTouchOf(touchPoint_X)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determines where a touch in the indicator translate to an index selection.
|
||||||
func assessTouchOf(_ touchPoint_X: CGFloat) { }
|
func assessTouchOf(_ touchPoint_X: CGFloat) { }
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Methods
|
// MARK: - Methods
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
/// Called to update the Indicator UI with the latest values.
|
||||||
open func updateUI(previousIndex: Int, newIndex: Int, totalCount: Int, isAnimated: Bool) { }
|
open func updateUI(previousIndex: Int, newIndex: Int, totalCount: Int, isAnimated: Bool) { }
|
||||||
|
|
||||||
public func performAction() {
|
public func performAction() {
|
||||||
@ -200,6 +200,7 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
|||||||
|
|
||||||
guard let model = model as? CarouselIndicatorModel else { return }
|
guard let model = model as? CarouselIndicatorModel else { return }
|
||||||
|
|
||||||
|
previousIndex = 0
|
||||||
indicatorColor = model.inverted ? model.indicatorColor_inverted.uiColor : model.indicatorColor.uiColor
|
indicatorColor = model.inverted ? model.indicatorColor_inverted.uiColor : model.indicatorColor.uiColor
|
||||||
disabledIndicatorColor = model.disabledIndicatorColor.uiColor
|
disabledIndicatorColor = model.disabledIndicatorColor.uiColor
|
||||||
currentIndex = model.currentIndex
|
currentIndex = model.currentIndex
|
||||||
@ -214,20 +215,18 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol {
|
|||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
open override func accessibilityIncrement() {
|
open override func accessibilityIncrement() {
|
||||||
|
|
||||||
adjustAccessibility(toPage: currentIndex + 1)
|
adjustAccessibility(toPage: currentIndex + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func accessibilityDecrement() {
|
open override func accessibilityDecrement() {
|
||||||
|
|
||||||
adjustAccessibility(toPage: currentIndex - 1)
|
adjustAccessibility(toPage: currentIndex - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatAccessibilityValue(index: Int, total: Int) {
|
func formatAccessibilityValue(index: Int, total: Int) {
|
||||||
|
|
||||||
guard let accessibleFormat = accessibilityValueFormat,
|
guard let accessibleFormat = accessibilityValueFormat,
|
||||||
let accessibleIndex = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: index))
|
let accessibleIndex = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: index))
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
accessibilityValue = String(format: accessibleFormat, accessibleIndex, total)
|
accessibilityValue = String(format: accessibleFormat, accessibleIndex, total)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,7 +47,7 @@ open class NumericIndicatorView: CarouselIndicator {
|
|||||||
|
|
||||||
/// Sets the color for pageCount text, left arrow and right arrow.
|
/// Sets the color for pageCount text, left arrow and right arrow.
|
||||||
public override var indicatorColor: UIColor {
|
public override var indicatorColor: UIColor {
|
||||||
get { return super.indicatorColor }
|
get { super.indicatorColor }
|
||||||
set (newColor) {
|
set (newColor) {
|
||||||
super.indicatorColor = newColor
|
super.indicatorColor = newColor
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,8 @@
|
|||||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
public class ListLeftVariableIconWithRightCaretAllTextLinksModel: ListItemModel, MoleculeModelProtocol {
|
public class ListLeftVariableIconWithRightCaretAllTextLinksModel: ListItemModel, ParentMoleculeModelProtocol {
|
||||||
|
|
||||||
//-----------------------------------------------------
|
//-----------------------------------------------------
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//-----------------------------------------------------
|
//-----------------------------------------------------
|
||||||
@ -16,6 +17,10 @@ public class ListLeftVariableIconWithRightCaretAllTextLinksModel: ListItemModel,
|
|||||||
public var eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel
|
public var eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel
|
||||||
public var rightLabel: LabelModel
|
public var rightLabel: LabelModel
|
||||||
|
|
||||||
|
public var children: [MoleculeModelProtocol] {
|
||||||
|
return [image, eyebrowHeadlineBodyLink, rightLabel]
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------
|
//-----------------------------------------------------
|
||||||
// MARK: - Methods
|
// MARK: - Methods
|
||||||
//-----------------------------------------------------
|
//-----------------------------------------------------
|
||||||
|
|||||||
@ -20,7 +20,6 @@
|
|||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
|
|
||||||
let containingStack: Stack<StackModel>
|
let containingStack: Stack<StackModel>
|
||||||
let stackSpacing: CGFloat = 5.0
|
|
||||||
|
|
||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
// MARK: - Initializers
|
// MARK: - Initializers
|
||||||
@ -31,7 +30,7 @@
|
|||||||
(view: rightHeadlineBodyLink, model: StackItemModel(percent: 50, verticalAlignment: .leading))],
|
(view: rightHeadlineBodyLink, model: StackItemModel(percent: 50, verticalAlignment: .leading))],
|
||||||
axis: .horizontal)
|
axis: .horizontal)
|
||||||
|
|
||||||
containingStack = Stack<StackModel>.createStack(with: [stackHeadline1], spacing: 0)
|
containingStack = Stack<StackModel>.createStack(with: [stackHeadline1], spacing: Padding.Component.gutterForApplicationWidth)
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
stack = Stack<StackModel>.createStack(with: [(view: leftDropDown, model: StackItemModel(percent: 50, horizontalAlignment: .fill)),
|
stack = Stack<StackModel>.createStack(with: [(view: leftDropDown, model: StackItemModel(percent: 50, horizontalAlignment: .fill)),
|
||||||
(view: rightDropDown, model: StackItemModel(percent: 50, horizontalAlignment: .fill))],
|
(view: rightDropDown, model: StackItemModel(percent: 50, horizontalAlignment: .fill))],
|
||||||
axis: .horizontal, spacing: 9)
|
axis: .horizontal, spacing: Padding.Component.gutterForApplicationWidth)
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@ import UIKit
|
|||||||
|
|
||||||
if let rightView = createRightView() {
|
if let rightView = createRightView() {
|
||||||
addSubview(rightView)
|
addSubview(rightView)
|
||||||
rightView.leftAnchor.constraint(equalTo: radioButton.rightAnchor, constant: PaddingHorizontalBetweenRelatedItems).isActive = true
|
rightView.leftAnchor.constraint(equalTo: radioButton.rightAnchor, constant: Padding.Component.gutterForApplicationWidth).isActive = true
|
||||||
rightView.rightAnchor.constraint(equalTo: layoutMarginsGuide.rightAnchor, constant: 0).isActive = true
|
rightView.rightAnchor.constraint(equalTo: layoutMarginsGuide.rightAnchor, constant: 0).isActive = true
|
||||||
|
|
||||||
var constraint = rightView.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor, constant: PaddingOne)
|
var constraint = rightView.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor, constant: PaddingOne)
|
||||||
|
|||||||
@ -198,11 +198,13 @@ extension Tabs: UICollectionViewDelegateFlowLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
|
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
|
||||||
if !paddingBeforeFirstTab && section == 0 {
|
guard section == 0 else {
|
||||||
return .zero
|
|
||||||
} else {
|
|
||||||
return UIEdgeInsets(top: 0, left: sectionPadding, bottom: 0, right: 0)
|
return UIEdgeInsets(top: 0, left: sectionPadding, bottom: 0, right: 0)
|
||||||
}
|
}
|
||||||
|
guard paddingBeforeFirstTab else {
|
||||||
|
return .zero
|
||||||
|
}
|
||||||
|
return UIEdgeInsets(top: 0, left: Padding.Component.horizontalPaddingForApplicationWidth, bottom: 0, right: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
|
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
|
||||||
|
|||||||
@ -51,7 +51,7 @@ import UIKit
|
|||||||
stack.addArrangedSubview(primaryButton)
|
stack.addArrangedSubview(primaryButton)
|
||||||
NSLayoutConstraint.constraintPinSubview(toSuperview: stack)
|
NSLayoutConstraint.constraintPinSubview(toSuperview: stack)
|
||||||
stack.axis = .horizontal
|
stack.axis = .horizontal
|
||||||
stack.spacing = 10
|
stack.spacing = Padding.Component.gutterForApplicationWidth
|
||||||
equalWidthConstraint = secondaryButton.widthAnchor.constraint(equalTo: primaryButton.widthAnchor, multiplier: 1)
|
equalWidthConstraint = secondaryButton.widthAnchor.constraint(equalTo: primaryButton.widthAnchor, multiplier: 1)
|
||||||
equalWidthConstraint?.isActive = true
|
equalWidthConstraint?.isActive = true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -160,7 +160,7 @@ import Foundation
|
|||||||
|
|
||||||
/// Collapse if focus is no longer on this top alert.
|
/// Collapse if focus is no longer on this top alert.
|
||||||
@objc func accessibilityFocusChanged(notification: Notification) {
|
@objc func accessibilityFocusChanged(notification: Notification) {
|
||||||
if !MVMCoreUIUtility.viewContainsAccessiblityFocus(self) {
|
if (notification.userInfo?[UIAccessibility.focusedElementUserInfoKey] != nil) && !MVMCoreUIUtility.viewContainsAccessiblityFocus(self) {
|
||||||
NotificationCenter.default.removeObserver(self, name: UIAccessibility.elementFocusedNotification, object: nil)
|
NotificationCenter.default.removeObserver(self, name: UIAccessibility.elementFocusedNotification, object: nil)
|
||||||
collapse()
|
collapse()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,8 +24,8 @@ open class CollapsableNotificationModel: NotificationModel {
|
|||||||
super.init(with: headline)
|
super.init(with: headline)
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func setDefault() {
|
open override func setDefaults() {
|
||||||
super.setDefault()
|
super.setDefaults()
|
||||||
if topLabel.textColor == nil {
|
if topLabel.textColor == nil {
|
||||||
topLabel.textColor = Color(uiColor: .white)
|
topLabel.textColor = Color(uiColor: .white)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
@objcMembers open class NotificationView: View {
|
@objcMembers open class NotificationView: Container {
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Outlets
|
// MARK: - Outlets
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -43,19 +43,13 @@ import Foundation
|
|||||||
|
|
||||||
labelStack = Stack<StackModel>.createStack(with: [headline, body], spacing: 0)
|
labelStack = Stack<StackModel>.createStack(with: [headline, body], spacing: 0)
|
||||||
horizontalStack = Stack<StackModel>.createStack(with: [(view: labelStack, model: StackItemModel()),(view: button, model: StackItemModel(horizontalAlignment: .fill)),(view: closeButton, model: StackItemModel(horizontalAlignment: .fill))], axis: .horizontal)
|
horizontalStack = Stack<StackModel>.createStack(with: [(view: labelStack, model: StackItemModel()),(view: button, model: StackItemModel(horizontalAlignment: .fill)),(view: closeButton, model: StackItemModel(horizontalAlignment: .fill))], axis: .horizontal)
|
||||||
addSubview(horizontalStack)
|
addAndContain(horizontalStack)
|
||||||
NSLayoutConstraint.constraintPinSubview(horizontalStack, pinTop: true, topConstant: PaddingTwo, pinBottom: true, bottomConstant: PaddingTwo, pinLeft: true, leftConstant: PaddingThree, pinRight: true, rightConstant: PaddingThree)
|
|
||||||
labelStack.restack()
|
labelStack.restack()
|
||||||
horizontalStack.restack()
|
horizontalStack.restack()
|
||||||
|
|
||||||
heightAnchor.constraint(equalToConstant: Self.viewHeight).isActive = true
|
heightAnchor.constraint(equalToConstant: Self.viewHeight).isActive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func updateView(_ size: CGFloat) {
|
|
||||||
super.updateView(size)
|
|
||||||
horizontalStack.updateView(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
open override func reset() {
|
open override func reset() {
|
||||||
super.reset()
|
super.reset()
|
||||||
backgroundColor = .mvmGreen()
|
backgroundColor = .mvmGreen()
|
||||||
|
|||||||
@ -7,7 +7,22 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
open class NotificationModel: MoleculeModelProtocol {
|
open class NotificationModel: ContainerModel, MoleculeModelProtocol {
|
||||||
|
|
||||||
|
/**
|
||||||
|
The style of the notification:
|
||||||
|
- success, green background, white content
|
||||||
|
- error, orange background, black content
|
||||||
|
- \warning, yellow background, black content
|
||||||
|
- information, blue background, white content
|
||||||
|
*/
|
||||||
|
public enum Style: String, Codable {
|
||||||
|
case success
|
||||||
|
case error
|
||||||
|
case warning
|
||||||
|
case information
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -19,6 +34,7 @@ open class NotificationModel: MoleculeModelProtocol {
|
|||||||
public var body: LabelModel?
|
public var body: LabelModel?
|
||||||
public var button: ButtonModel?
|
public var button: ButtonModel?
|
||||||
public var closeButton: NotificationXButtonModel?
|
public var closeButton: NotificationXButtonModel?
|
||||||
|
public var style: NotificationModel.Style = .success
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Initializer
|
// MARK: - Initializer
|
||||||
@ -26,28 +42,77 @@ open class NotificationModel: MoleculeModelProtocol {
|
|||||||
|
|
||||||
public init(with headline: LabelModel) {
|
public init(with headline: LabelModel) {
|
||||||
self.headline = headline
|
self.headline = headline
|
||||||
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Default
|
// MARK: - Default
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
open func setDefault() {
|
open override func setDefaults() {
|
||||||
|
useHorizontalMargins = true
|
||||||
|
useVerticalMargins = true
|
||||||
|
topPadding = PaddingTwo
|
||||||
|
bottomPadding = PaddingTwo
|
||||||
|
|
||||||
if backgroundColor == nil {
|
if backgroundColor == nil {
|
||||||
backgroundColor = Color(uiColor: .mvmGreen)
|
switch style {
|
||||||
|
case .error:
|
||||||
|
backgroundColor = Color(uiColor: .mvmOrange)
|
||||||
|
case .warning:
|
||||||
|
backgroundColor = Color(uiColor: .mvmYellow)
|
||||||
|
case .information:
|
||||||
|
backgroundColor = Color(uiColor: .mvmBlue)
|
||||||
|
default:
|
||||||
|
backgroundColor = Color(uiColor: .mvmGreen)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if headline.textColor == nil {
|
if headline.textColor == nil {
|
||||||
headline.textColor = Color(uiColor: .mvmWhite)
|
switch style {
|
||||||
|
case .error, .warning:
|
||||||
|
headline.textColor = Color(uiColor: .mvmBlack)
|
||||||
|
default:
|
||||||
|
headline.textColor = Color(uiColor: .mvmWhite)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if body?.textColor == nil {
|
if body?.textColor == nil {
|
||||||
body?.textColor = Color(uiColor: .mvmWhite)
|
switch style {
|
||||||
|
case .error, .warning:
|
||||||
|
body?.textColor = Color(uiColor: .mvmBlack)
|
||||||
|
default:
|
||||||
|
body?.textColor = Color(uiColor: .mvmWhite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button?.size = .tiny
|
||||||
|
if button?.enabledTextColor == nil {
|
||||||
|
switch style {
|
||||||
|
case .error, .warning:
|
||||||
|
button?.enabledTextColor = Color(uiColor: .mvmBlack)
|
||||||
|
default:
|
||||||
|
button?.enabledTextColor = Color(uiColor: .mvmWhite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if button?.enabledBorderColor == nil {
|
||||||
|
switch style {
|
||||||
|
case .error, .warning:
|
||||||
|
button?.enabledBorderColor = Color(uiColor: .mvmBlack)
|
||||||
|
default:
|
||||||
|
button?.enabledBorderColor = Color(uiColor: .mvmWhite)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if button?.style == nil {
|
if button?.style == nil {
|
||||||
button?.style = .secondary
|
button?.style = .secondary
|
||||||
}
|
}
|
||||||
button?.size = .tiny
|
|
||||||
button?.enabledTextColor = Color(uiColor: .mvmWhite)
|
if closeButton?.color == nil {
|
||||||
button?.enabledBorderColor = Color(uiColor: .mvmWhite)
|
switch style {
|
||||||
|
case .error, .warning:
|
||||||
|
closeButton?.color = Color(uiColor: .mvmBlack)
|
||||||
|
default:
|
||||||
|
closeButton?.color = Color(uiColor: .mvmWhite)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -62,6 +127,7 @@ open class NotificationModel: MoleculeModelProtocol {
|
|||||||
case body
|
case body
|
||||||
case button
|
case button
|
||||||
case closeButton
|
case closeButton
|
||||||
|
case style
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -76,10 +142,13 @@ open class NotificationModel: MoleculeModelProtocol {
|
|||||||
body = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body)
|
body = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body)
|
||||||
button = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .button)
|
button = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .button)
|
||||||
closeButton = try typeContainer.decodeIfPresent(NotificationXButtonModel.self, forKey: .closeButton)
|
closeButton = try typeContainer.decodeIfPresent(NotificationXButtonModel.self, forKey: .closeButton)
|
||||||
setDefault()
|
if let style = try typeContainer.decodeIfPresent(NotificationModel.Style.self, forKey: .style) {
|
||||||
|
self.style = style
|
||||||
|
}
|
||||||
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
open func encode(to encoder: Encoder) throws {
|
open override 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.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||||
@ -88,5 +157,6 @@ open class NotificationModel: MoleculeModelProtocol {
|
|||||||
try container.encodeIfPresent(body, forKey: .body)
|
try container.encodeIfPresent(body, forKey: .body)
|
||||||
try container.encodeIfPresent(button, forKey: .button)
|
try container.encodeIfPresent(button, forKey: .button)
|
||||||
try container.encodeIfPresent(closeButton, forKey: .closeButton)
|
try container.encodeIfPresent(closeButton, forKey: .closeButton)
|
||||||
|
try container.encode(style, forKey: .style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,7 @@ import Foundation
|
|||||||
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
|
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
|
||||||
super.set(with: model, delegateObject, additionalData)
|
super.set(with: model, delegateObject, additionalData)
|
||||||
guard let model = model as? NotificationXButtonModel else { return }
|
guard let model = model as? NotificationXButtonModel else { return }
|
||||||
tintColor = model.color.uiColor
|
tintColor = model.color?.uiColor ?? .white
|
||||||
|
|
||||||
// TODO: Temporary, consider action for dismissing top alert
|
// TODO: Temporary, consider action for dismissing top alert
|
||||||
if model.action.actionType == ActionNoopModel.identifier {
|
if model.action.actionType == ActionNoopModel.identifier {
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import Foundation
|
|||||||
public class NotificationXButtonModel: ButtonModelProtocol, MoleculeModelProtocol {
|
public class NotificationXButtonModel: ButtonModelProtocol, MoleculeModelProtocol {
|
||||||
public static var identifier: String = "notificationXButton"
|
public static var identifier: String = "notificationXButton"
|
||||||
public var backgroundColor: Color?
|
public var backgroundColor: Color?
|
||||||
public var color = Color(uiColor: .white)
|
public var color: Color?
|
||||||
public var action: ActionModelProtocol = ActionNoopModel()
|
public var action: ActionModelProtocol = ActionNoopModel()
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
@ -24,7 +24,7 @@ public class NotificationXButtonModel: ButtonModelProtocol, MoleculeModelProtoco
|
|||||||
|
|
||||||
public required init(from decoder: Decoder) throws {
|
public required init(from decoder: Decoder) throws {
|
||||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
color = try typeContainer.decodeIfPresent(Color.self, forKey: .color) ?? Color(uiColor: .white)
|
color = try typeContainer.decodeIfPresent(Color.self, forKey: .color)
|
||||||
if let action: ActionModelProtocol = try typeContainer.decodeModelIfPresent(codingKey: .action) {
|
if let action: ActionModelProtocol = try typeContainer.decodeModelIfPresent(codingKey: .action) {
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
@ -33,7 +33,7 @@ public class NotificationXButtonModel: ButtonModelProtocol, MoleculeModelProtoco
|
|||||||
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(color, forKey: .color)
|
try container.encodeIfPresent(color, forKey: .color)
|
||||||
try container.encodeModel(action, forKey: .action)
|
try container.encodeModel(action, forKey: .action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,7 +73,7 @@ public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol, ParentMolecule
|
|||||||
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
||||||
eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow)
|
eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow)
|
||||||
headline = try typeContainer.decodeMoleculeIfPresent(codingKey: .headline)
|
headline = try typeContainer.decodeMoleculeIfPresent(codingKey: .headline)
|
||||||
body = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body)
|
body = try typeContainer.decodeMoleculeIfPresent(codingKey: .body)
|
||||||
link = try typeContainer.decodeIfPresent(LinkModel.self, forKey: .link)
|
link = try typeContainer.decodeIfPresent(LinkModel.self, forKey: .link)
|
||||||
setDefaults()
|
setDefaults()
|
||||||
// TODO: This class initializers should ensure that atleast one item is set.
|
// TODO: This class initializers should ensure that atleast one item is set.
|
||||||
|
|||||||
@ -17,7 +17,7 @@ public protocol CarouselPageControlProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open class Carousel: View {
|
open class Carousel: View {
|
||||||
|
|
||||||
public let collectionView: CollectionView = {
|
public let collectionView: CollectionView = {
|
||||||
let layout = CarouselCollectionLayout()
|
let layout = CarouselCollectionLayout()
|
||||||
layout.scrollDirection = .horizontal
|
layout.scrollDirection = .horizontal
|
||||||
@ -25,7 +25,7 @@ open class Carousel: View {
|
|||||||
layout.minimumLineSpacing = 0
|
layout.minimumLineSpacing = 0
|
||||||
return CollectionView(frame: .zero, collectionViewLayout: layout)
|
return CollectionView(frame: .zero, collectionViewLayout: layout)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
/// The current index of the collection view. Includes dummy cells when looping.
|
/// The current index of the collection view. Includes dummy cells when looping.
|
||||||
public var currentIndex = 0
|
public var currentIndex = 0
|
||||||
|
|
||||||
@ -53,8 +53,8 @@ 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 & CarouselPageControlProtocol)?
|
public var pagingView: (MoleculeViewProtocol & 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.
|
||||||
public var loop = false
|
public var loop = false
|
||||||
|
|
||||||
@ -85,10 +85,10 @@ open class Carousel: View {
|
|||||||
open func layoutCollection() {
|
open func layoutCollection() {
|
||||||
collectionView.collectionViewLayout.invalidateLayout()
|
collectionView.collectionViewLayout.invalidateLayout()
|
||||||
showPeaking(false)
|
showPeaking(false)
|
||||||
|
|
||||||
// Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled.
|
// Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled.
|
||||||
guard let model = model as? CarouselModel,
|
guard let model = model as? CarouselModel,
|
||||||
(model.paging == true || loop == true) else { return }
|
(model.paging == true || loop == true) else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false)
|
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false)
|
||||||
self.collectionView.layoutIfNeeded()
|
self.collectionView.layoutIfNeeded()
|
||||||
@ -152,12 +152,12 @@ open class Carousel: View {
|
|||||||
collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0
|
collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0
|
||||||
(collectionView.collectionViewLayout as? CarouselCollectionLayout)?.useLines = carouselModel.border ?? false
|
(collectionView.collectionViewLayout as? CarouselCollectionLayout)?.useLines = carouselModel.border ?? false
|
||||||
(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing = carouselModel.spacing ?? 0
|
(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing = carouselModel.spacing ?? 0
|
||||||
|
|
||||||
itemWidthPercent = carouselModel.itemWidthPercent / 100.0
|
itemWidthPercent = carouselModel.itemWidthPercent / 100.0
|
||||||
if let alignment = carouselModel.itemAlignment {
|
if let alignment = carouselModel.itemAlignment {
|
||||||
itemAlignment = alignment
|
itemAlignment = alignment
|
||||||
}
|
}
|
||||||
|
|
||||||
if let height = carouselModel.height {
|
if let height = carouselModel.height {
|
||||||
collectionViewHeight?.constant = height
|
collectionViewHeight?.constant = height
|
||||||
collectionViewHeight?.isActive = true
|
collectionViewHeight?.isActive = true
|
||||||
@ -168,7 +168,7 @@ open class Carousel: View {
|
|||||||
registerCells(with: carouselModel, delegateObject: delegateObject)
|
registerCells(with: carouselModel, delegateObject: delegateObject)
|
||||||
prepareMolecules(with: carouselModel)
|
prepareMolecules(with: carouselModel)
|
||||||
FormValidator.setupValidation(for: carouselModel, delegate: delegateObject?.formHolderDelegate)
|
FormValidator.setupValidation(for: carouselModel, delegate: delegateObject?.formHolderDelegate)
|
||||||
|
|
||||||
setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject)
|
setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject)
|
||||||
|
|
||||||
pageIndex = carouselModel.index
|
pageIndex = carouselModel.index
|
||||||
@ -197,7 +197,7 @@ open class Carousel: View {
|
|||||||
if carouselModel?.loop ?? false && newMolecules.count > 1 && !UIAccessibility.isVoiceOverRunning {
|
if carouselModel?.loop ?? false && newMolecules.count > 1 && !UIAccessibility.isVoiceOverRunning {
|
||||||
// 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(contentsOf: newMolecules.suffix(2), at: 0)
|
molecules?.insert(contentsOf: newMolecules.suffix(2), at: 0)
|
||||||
molecules?.append(contentsOf: newMolecules.prefix(2))
|
molecules?.append(contentsOf: newMolecules.prefix(2))
|
||||||
} else {
|
} else {
|
||||||
@ -207,12 +207,25 @@ open class Carousel: View {
|
|||||||
pageIndex = 0
|
pageIndex = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pagingMoleculeName: String?
|
||||||
|
|
||||||
/// 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 & CarouselPageControlProtocol)? = nil
|
|
||||||
if let molecule = molecule,
|
if let molecule = molecule,
|
||||||
(!molecule.hidesForSinglePage || numberOfPages > 1) {
|
molecule.moleculeName == pagingMoleculeName {
|
||||||
pagingView = ModelRegistry.createMolecule(molecule, delegateObject: delegateObject) as? (UIView & CarouselPageControlProtocol)
|
pagingView?.set(with: molecule, delegateObject, nil)
|
||||||
|
pagingView?.numberOfPages = numberOfPages
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var pagingView: (MoleculeViewProtocol & CarouselPageControlProtocol)? = nil
|
||||||
|
if let molecule = molecule,
|
||||||
|
(!molecule.hidesForSinglePage || numberOfPages > 1) {
|
||||||
|
pagingView = ModelRegistry.createMolecule(molecule, delegateObject: delegateObject) as? (MoleculeViewProtocol & CarouselPageControlProtocol)
|
||||||
|
pagingMoleculeName = molecule.moleculeName
|
||||||
|
} else {
|
||||||
|
pagingMoleculeName = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
addPaging(view: pagingView, position: molecule?.position ?? 20)
|
addPaging(view: pagingView, position: molecule?.position ?? 20)
|
||||||
@ -239,7 +252,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.
|
/// 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)?, position: CGFloat) {
|
open func addPaging(view: (MoleculeViewProtocol & CarouselPageControlProtocol)?, position: CGFloat) {
|
||||||
|
|
||||||
pagingView?.removeFromSuperview()
|
pagingView?.removeFromSuperview()
|
||||||
bottomPin?.isActive = false
|
bottomPin?.isActive = false
|
||||||
@ -296,7 +309,7 @@ open class Carousel: View {
|
|||||||
|
|
||||||
func trackSwipeActionAnalyticsforIndex(_ index : Int){
|
func trackSwipeActionAnalyticsforIndex(_ index : Int){
|
||||||
guard let itemModel = molecules?[index],
|
guard let itemModel = molecules?[index],
|
||||||
let viewControllerObject = delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol else { return }
|
let viewControllerObject = delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol else { return }
|
||||||
MVMCoreUILoggingHandler.shared()?.defaultLogAction(forController: viewControllerObject, actionInformation: itemModel.toJSON(), additionalData: nil)
|
MVMCoreUILoggingHandler.shared()?.defaultLogAction(forController: viewControllerObject, actionInformation: itemModel.toJSON(), additionalData: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,7 +365,7 @@ open class Carousel: View {
|
|||||||
carouselAccessibilityElement.accessibilityFrameInContainerSpace = collectionView.frame
|
carouselAccessibilityElement.accessibilityFrameInContainerSpace = collectionView.frame
|
||||||
self.carouselAccessibilityElement = carouselAccessibilityElement
|
self.carouselAccessibilityElement = carouselAccessibilityElement
|
||||||
}
|
}
|
||||||
|
|
||||||
if let currentCell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) {
|
if let currentCell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) {
|
||||||
_accessibilityElements = [currentCell, carouselAccessibilityElement]
|
_accessibilityElements = [currentCell, carouselAccessibilityElement]
|
||||||
} else {
|
} else {
|
||||||
@ -378,11 +391,11 @@ extension Carousel: UICollectionViewDataSource {
|
|||||||
open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
return molecules?.count ?? 0
|
return molecules?.count ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
let moleculeInfo = getMoleculeInfo(with: molecule, delegateObject: nil)
|
||||||
else { 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 {
|
||||||
@ -445,22 +458,22 @@ extension Carousel: UIScrollViewDelegate {
|
|||||||
if translatedPoint > 0 {
|
if translatedPoint > 0 {
|
||||||
// Moving left, see if we are moving passed the first left buffer card and adjust
|
// Moving left, see if we are moving passed the first left buffer card and adjust
|
||||||
if let threshold = collectionView.layoutAttributesForItem(at: IndexPath(item: 1, section: 0))?.frame.minX,
|
if let threshold = collectionView.layoutAttributesForItem(at: IndexPath(item: 1, section: 0))?.frame.minX,
|
||||||
scrollView.contentOffset.x < threshold,
|
scrollView.contentOffset.x < threshold,
|
||||||
let newOffset = collectionView.layoutAttributesForItem(at: IndexPath(item: numberOfPages + 1, section: 0))?.frame.minX {
|
let newOffset = collectionView.layoutAttributesForItem(at: IndexPath(item: numberOfPages + 1, section: 0))?.frame.minX {
|
||||||
scrollView.contentOffset.x = newOffset
|
scrollView.contentOffset.x = newOffset
|
||||||
}
|
}
|
||||||
} else if translatedPoint < 0 {
|
} else if translatedPoint < 0 {
|
||||||
// Moving right, see if we are moving passed the first right buffer card and adjust
|
// Moving right, see if we are moving passed the first right buffer card and adjust
|
||||||
if let threshold = collectionView.layoutAttributesForItem(at: IndexPath(item: numberOfPages + 2, section: 0))?.frame.maxX,
|
if let threshold = collectionView.layoutAttributesForItem(at: IndexPath(item: numberOfPages + 2, section: 0))?.frame.maxX,
|
||||||
scrollView.contentOffset.x + scrollView.bounds.width > threshold,
|
scrollView.contentOffset.x + scrollView.bounds.width > threshold,
|
||||||
let newEndOffset = collectionView.layoutAttributesForItem(at: IndexPath(item: 2, section: 0))?.frame.maxX {
|
let newEndOffset = collectionView.layoutAttributesForItem(at: IndexPath(item: 2, section: 0))?.frame.maxX {
|
||||||
scrollView.contentOffset.x = newEndOffset - scrollView.bounds.width
|
scrollView.contentOffset.x = newEndOffset - scrollView.bounds.width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
open func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
|
||||||
// Adjust for looping
|
// Adjust for looping
|
||||||
if loop == true {
|
if loop == true {
|
||||||
adjustOffsetForLooping(scrollView)
|
adjustOffsetForLooping(scrollView)
|
||||||
@ -474,7 +487,7 @@ extension Carousel: UIScrollViewDelegate {
|
|||||||
|
|
||||||
// Disable peaking when dragging.
|
// Disable peaking when dragging.
|
||||||
dragging = true
|
dragging = true
|
||||||
|
|
||||||
showPeaking(false)
|
showPeaking(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -484,7 +497,7 @@ extension Carousel: UIScrollViewDelegate {
|
|||||||
|
|
||||||
// This is for setting up smooth custom paging. (Since UICollectionView only handles paging based on collection view size and not cell size). Math requires that we are using UICollectionViewFlowLayout.
|
// This is for setting up smooth custom paging. (Since UICollectionView only handles paging based on collection view size and not cell size). Math requires that we are using UICollectionViewFlowLayout.
|
||||||
guard (model as? CarouselModel)?.paging == true,
|
guard (model as? CarouselModel)?.paging == true,
|
||||||
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
|
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
|
||||||
|
|
||||||
let separatorWidth = layout.minimumLineSpacing
|
let separatorWidth = layout.minimumLineSpacing
|
||||||
let itemWidth = collectionView.bounds.width * itemWidthPercent
|
let itemWidth = collectionView.bounds.width * itemWidthPercent
|
||||||
@ -529,7 +542,7 @@ extension Carousel: UIScrollViewDelegate {
|
|||||||
} else {
|
} else {
|
||||||
targetContentOffset.pointee = scrollView.contentOffset
|
targetContentOffset.pointee = scrollView.contentOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cap the index.
|
// Cap the index.
|
||||||
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
||||||
goTo(min(max(cellToSwipeTo, 0), lastCellIndex), animated: true)
|
goTo(min(max(cellToSwipeTo, 0), lastCellIndex), animated: true)
|
||||||
@ -548,35 +561,35 @@ extension Carousel: UIScrollViewDelegate {
|
|||||||
// Adapted from: https://developer.apple.com/documentation/uikit/accessibility_for_ios_and_tvos/delivering_an_exceptional_accessibility_experience
|
// Adapted from: https://developer.apple.com/documentation/uikit/accessibility_for_ios_and_tvos/delivering_an_exceptional_accessibility_experience
|
||||||
/// Ensures a good accessibility experience. Adds adjustable swiping for cards.
|
/// Ensures a good accessibility experience. Adds adjustable swiping for cards.
|
||||||
class CarouselAccessibilityElement: UIAccessibilityElement {
|
class CarouselAccessibilityElement: UIAccessibilityElement {
|
||||||
|
|
||||||
/// This indicates to the user what exactly this element is supposed to be.
|
/// This indicates to the user what exactly this element is supposed to be.
|
||||||
override var accessibilityLabel: String? {
|
override var accessibilityLabel: String? {
|
||||||
get {
|
get {
|
||||||
guard let containerView = accessibilityContainer as? Carousel,
|
guard let containerView = accessibilityContainer as? Carousel,
|
||||||
let accessibilityLabel = containerView.accessibilityLabel else { return super.accessibilityLabel }
|
let accessibilityLabel = containerView.accessibilityLabel else { return super.accessibilityLabel }
|
||||||
return accessibilityLabel
|
return accessibilityLabel
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
super.accessibilityLabel = newValue
|
super.accessibilityLabel = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override var accessibilityValue: String? {
|
override var accessibilityValue: String? {
|
||||||
get {
|
get {
|
||||||
// Read which card we are on.
|
// Read which card we are on.
|
||||||
guard let containerView = accessibilityContainer as? Carousel,
|
guard let containerView = accessibilityContainer as? Carousel,
|
||||||
let format = MVMCoreUIUtility.hardcodedString(withKey: "index_string_of_total"),
|
let format = MVMCoreUIUtility.hardcodedString(withKey: "index_string_of_total"),
|
||||||
let indexString = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: containerView.currentIndex + 1)) else {
|
let indexString = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: containerView.currentIndex + 1)) else {
|
||||||
return super.accessibilityValue
|
return super.accessibilityValue
|
||||||
}
|
}
|
||||||
return String(format: format, indexString, containerView.numberOfPages)
|
return String(format: format, indexString, containerView.numberOfPages)
|
||||||
}
|
}
|
||||||
|
|
||||||
set {
|
set {
|
||||||
super.accessibilityValue = newValue
|
super.accessibilityValue = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This tells VoiceOver that our element will support the increment and decrement callbacks.
|
// This tells VoiceOver that our element will support the increment and decrement callbacks.
|
||||||
/// - Tag: accessibility_traits
|
/// - Tag: accessibility_traits
|
||||||
override var accessibilityTraits: UIAccessibilityTraits {
|
override var accessibilityTraits: UIAccessibilityTraits {
|
||||||
@ -587,41 +600,41 @@ class CarouselAccessibilityElement: UIAccessibilityElement {
|
|||||||
super.accessibilityTraits = newValue
|
super.accessibilityTraits = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A convenience for forward scrolling in both `accessibilityIncrement` and `accessibilityScroll`.
|
A convenience for forward scrolling in both `accessibilityIncrement` and `accessibilityScroll`.
|
||||||
It returns a `Bool` because `accessibilityScroll` needs to know if the scroll was successful.
|
It returns a `Bool` because `accessibilityScroll` needs to know if the scroll was successful.
|
||||||
*/
|
*/
|
||||||
func accessibilityScrollForward() -> Bool {
|
func accessibilityScrollForward() -> Bool {
|
||||||
guard let containerView = accessibilityContainer as? Carousel else { return false }
|
guard let containerView = accessibilityContainer as? Carousel else { return false }
|
||||||
|
|
||||||
let newIndex = containerView.currentIndex + 1
|
let newIndex = containerView.currentIndex + 1
|
||||||
guard newIndex < containerView.numberOfPages else { return false }
|
guard newIndex < containerView.numberOfPages else { return false }
|
||||||
|
|
||||||
containerView.goTo(newIndex, animated: false)
|
containerView.goTo(newIndex, animated: false)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A convenience for backward scrolling in both `accessibilityIncrement` and `accessibilityScroll`.
|
A convenience for backward scrolling in both `accessibilityIncrement` and `accessibilityScroll`.
|
||||||
It returns a `Bool` because `accessibilityScroll` needs to know if the scroll was successful.
|
It returns a `Bool` because `accessibilityScroll` needs to know if the scroll was successful.
|
||||||
*/
|
*/
|
||||||
func accessibilityScrollBackward() -> Bool {
|
func accessibilityScrollBackward() -> Bool {
|
||||||
guard let containerView = accessibilityContainer as? Carousel else { return false }
|
guard let containerView = accessibilityContainer as? Carousel else { return false }
|
||||||
|
|
||||||
let newIndex = containerView.currentIndex - 1
|
let newIndex = containerView.currentIndex - 1
|
||||||
guard newIndex >= 0 else { return false }
|
guard newIndex >= 0 else { return false }
|
||||||
|
|
||||||
containerView.goTo(newIndex, animated: false)
|
containerView.goTo(newIndex, animated: false)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Overriding the following two methods allows the user to perform increment and decrement actions
|
Overriding the following two methods allows the user to perform increment and decrement actions
|
||||||
(done by swiping up or down).
|
(done by swiping up or down).
|
||||||
*/
|
*/
|
||||||
/// - Tag: accessibility_increment_decrement
|
/// - Tag: accessibility_increment_decrement
|
||||||
override func accessibilityIncrement() {
|
override func accessibilityIncrement() {
|
||||||
// This causes the picker to move forward one if the user swipes up.
|
// This causes the picker to move forward one if the user swipes up.
|
||||||
@ -632,12 +645,12 @@ class CarouselAccessibilityElement: UIAccessibilityElement {
|
|||||||
// This causes the picker to move back one if the user swipes down.
|
// This causes the picker to move back one if the user swipes down.
|
||||||
_ = accessibilityScrollBackward()
|
_ = accessibilityScrollBackward()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This will cause the picker to move forward or backwards on when the user does a 3-finger swipe,
|
This will cause the picker to move forward or backwards on when the user does a 3-finger swipe,
|
||||||
depending on the direction of the swipe. The return value indicates whether or not the scroll was successful,
|
depending on the direction of the swipe. The return value indicates whether or not the scroll was successful,
|
||||||
so that VoiceOver can alert the user if it was not.
|
so that VoiceOver can alert the user if it was not.
|
||||||
*/
|
*/
|
||||||
override func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool {
|
override func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool {
|
||||||
if direction == .left {
|
if direction == .left {
|
||||||
return accessibilityScrollForward()
|
return accessibilityScrollForward()
|
||||||
|
|||||||
@ -13,14 +13,12 @@
|
|||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
static let defaultSpacing: CGFloat = 16.0
|
|
||||||
|
|
||||||
public class var identifier: String { "stack" }
|
public class var identifier: String { "stack" }
|
||||||
|
|
||||||
public var backgroundColor: Color?
|
public var backgroundColor: Color?
|
||||||
public var molecules: [StackItemModelProtocol & MoleculeModelProtocol]
|
public var molecules: [StackItemModelProtocol & MoleculeModelProtocol]
|
||||||
public var axis: NSLayoutConstraint.Axis = .vertical
|
public var axis: NSLayoutConstraint.Axis = .vertical
|
||||||
public var spacing: CGFloat = StackModel.defaultSpacing
|
public var spacing: CGFloat = Padding.Four
|
||||||
public var useStackSpacingBeforeFirstItem = false
|
public var useStackSpacingBeforeFirstItem = false
|
||||||
|
|
||||||
public var children: [MoleculeModelProtocol] {
|
public var children: [MoleculeModelProtocol] {
|
||||||
@ -38,6 +36,8 @@
|
|||||||
}
|
}
|
||||||
if let spacing = spacing {
|
if let spacing = spacing {
|
||||||
self.spacing = spacing
|
self.spacing = spacing
|
||||||
|
} else if axis == .horizontal {
|
||||||
|
self.spacing = Padding.Component.gutterForApplicationWidth
|
||||||
}
|
}
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
@ -66,6 +66,8 @@
|
|||||||
}
|
}
|
||||||
if let spacing = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .spacing) {
|
if let spacing = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .spacing) {
|
||||||
self.spacing = spacing
|
self.spacing = spacing
|
||||||
|
} else if axis == .horizontal {
|
||||||
|
self.spacing = Padding.Component.gutterForApplicationWidth
|
||||||
}
|
}
|
||||||
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
||||||
try super.init(from: decoder)
|
try super.init(from: decoder)
|
||||||
|
|||||||
@ -79,7 +79,7 @@ public typealias ButtonAction = (Button) -> ()
|
|||||||
|
|
||||||
addActionBlock(event: .touchUpInside) { [weak self] sender in
|
addActionBlock(event: .touchUpInside) { [weak self] sender in
|
||||||
guard let self = self, let actionModel = actionModel else { return }
|
guard let self = self, let actionModel = actionModel else { return }
|
||||||
Self.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData)
|
Self.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: self.model)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,8 +34,7 @@
|
|||||||
[view addSubview:activityIndicatorView];
|
[view addSubview:activityIndicatorView];
|
||||||
self.activityIndicator = activityIndicatorView;
|
self.activityIndicator = activityIndicatorView;
|
||||||
self.activityIndicator.accessibilityIdentifier = @"Loader";
|
self.activityIndicator.accessibilityIdentifier = @"Loader";
|
||||||
|
[activityIndicatorView pinWidthAndHeight];
|
||||||
[NSLayoutConstraint constraintPinView:activityIndicatorView heightConstraint:YES heightConstant:PaddingSix widthConstraint:YES widthConstant:PaddingSix];
|
|
||||||
|
|
||||||
// Sets the constraints for the activityIndicatorView
|
// Sets the constraints for the activityIndicatorView
|
||||||
[NSLayoutConstraint constraintPinSubview:activityIndicatorView pinCenterX:YES pinCenterY:YES];
|
[NSLayoutConstraint constraintPinSubview:activityIndicatorView pinCenterX:YES pinCenterY:YES];
|
||||||
|
|||||||
@ -449,8 +449,8 @@ import UIKit
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Override this method to avoid adding form params.
|
/// Override this method to avoid adding form params.
|
||||||
open func addFormParams(_ requestParameters: MVMCoreRequestParameters) {
|
open func addFormParams(requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) {
|
||||||
formValidator?.addFormParams(requestParameters: requestParameters)
|
formValidator?.addFormParams(requestParameters: requestParameters, model: additionalData?[KeySourceModel] as? MoleculeModelProtocol & FormItemProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func handleFieldErrors(_ fieldErrors: [Any]?, loadObject: MVMCoreLoadObject) {
|
public func handleFieldErrors(_ fieldErrors: [Any]?, loadObject: MVMCoreLoadObject) {
|
||||||
@ -472,7 +472,7 @@ import UIKit
|
|||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
open func handleOpenPage(for requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) {
|
open func handleOpenPage(for requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) {
|
||||||
addFormParams(requestParameters)
|
addFormParams(requestParameters: requestParameters, actionInformation: actionInformation, additionalData: additionalData)
|
||||||
requestParameters.parentPageType = loadObject?.pageJSON?.optionalStringForKey("parentPageType")
|
requestParameters.parentPageType = loadObject?.pageJSON?.optionalStringForKey("parentPageType")
|
||||||
var pageForwardedData = additionalData ?? [:]
|
var pageForwardedData = additionalData ?? [:]
|
||||||
|
|
||||||
@ -657,7 +657,7 @@ import UIKit
|
|||||||
// MARK: - Behavior Execution
|
// MARK: - Behavior Execution
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
func executeBehaviors<T>(_ behaviorBlock: (_ behavior: T) -> Void) {
|
public func executeBehaviors<T>(_ behaviorBlock: (_ behavior: T) -> Void) {
|
||||||
behaviors?.compactMap { $0 as? T }.forEach { behaviorBlock($0) }
|
behaviors?.compactMap { $0 as? T }.forEach { behaviorBlock($0) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ public class PageGetContactBehaviorModel: PageBehaviorModelProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class PageGetContactBehavior: PageVisibilityBehavior {
|
public class PageGetContactBehavior: PageVisibilityBehavior {
|
||||||
|
|
||||||
var delegate: MVMCoreUIDelegateObject?
|
var delegate: MVMCoreUIDelegateObject?
|
||||||
|
|
||||||
public required init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) {
|
public required init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) {
|
||||||
@ -36,21 +37,38 @@ public class PageGetContactBehavior: PageVisibilityBehavior {
|
|||||||
error == nil,
|
error == nil,
|
||||||
let rootMolecules = self?.delegate?.moleculeDelegate?.getRootMolecules() else { return }
|
let rootMolecules = self?.delegate?.moleculeDelegate?.getRootMolecules() else { return }
|
||||||
// Iterate models and provide contact
|
// Iterate models and provide contact
|
||||||
let store = CNContactStore()
|
self?.getContacts(for: rootMolecules)
|
||||||
let consumers: [PageGetContactBehaviorConsumerProtocol] = rootMolecules.allMoleculesOfType()
|
|
||||||
for consumer in consumers {
|
|
||||||
guard let parameters = consumer.getMatchParameters(),
|
|
||||||
let contacts = try? store.unifiedContacts(matching: parameters.0, keysToFetch: parameters.1) else { return }
|
|
||||||
consumer.consume(contacts: contacts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tell template to update
|
// Tell template to update
|
||||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
||||||
// TODO: move to protocol function instead
|
// TODO: move to protocol function instead
|
||||||
(self?.delegate?.moleculeDelegate as? ViewController)?.handleNewData()
|
guard let controller = self?.delegate?.moleculeDelegate as? ViewController else { return }
|
||||||
|
controller.handleNewDataAndUpdateUI()
|
||||||
|
|
||||||
|
if MVMCoreUIUtility.getCurrentVisibleController() == controller {
|
||||||
|
// Update navigation bar if showing.
|
||||||
|
controller.setNavigationBar()
|
||||||
|
controller.manager?.refreshNavigationUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update splitview properties
|
||||||
|
if controller == MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() {
|
||||||
|
MVMCoreUISplitViewController.main()?.setBottomProgressBarProgress(controller.bottomProgress() ?? 0)
|
||||||
|
controller.updateTabBar()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func onPageHidden(_ delegateObject: MVMCoreUIDelegateObject?) { }
|
public func getContacts(for molecules: [MoleculeModelProtocol]) {
|
||||||
|
let consumers: [PageGetContactBehaviorConsumerProtocol] = molecules.allMoleculesOfType()
|
||||||
|
let store = CNContactStore()
|
||||||
|
for consumer in consumers {
|
||||||
|
guard let parameters = consumer.getMatchParameters(),
|
||||||
|
let contacts = try? store.unifiedContacts(matching: parameters.0, keysToFetch: parameters.1) else { return }
|
||||||
|
consumer.consume(contacts: contacts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func onPageHidden(_ delegateObject: MVMCoreUIDelegateObject?) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,6 +55,6 @@ public extension NSLayoutConstraint {
|
|||||||
}
|
}
|
||||||
leftView.leadingAnchor.constraint(equalTo: superView.layoutMarginsGuide.leadingAnchor).isActive = true
|
leftView.leadingAnchor.constraint(equalTo: superView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||||
superView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: rightView.trailingAnchor).isActive = true
|
superView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: rightView.trailingAnchor).isActive = true
|
||||||
rightView.leftAnchor.constraint(greaterThanOrEqualTo: leftView.rightAnchor, constant: PaddingHorizontalBetweenRelatedItems).isActive = true
|
rightView.leftAnchor.constraint(greaterThanOrEqualTo: leftView.rightAnchor, constant: Padding.Component.gutterForApplicationWidth).isActive = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -96,8 +96,13 @@ import MVMCore
|
|||||||
public func validateGroup(_ group: FormGroupRule) -> Bool {
|
public func validateGroup(_ group: FormGroupRule) -> Bool {
|
||||||
// Validate each rule.
|
// Validate each rule.
|
||||||
var valid = true
|
var valid = true
|
||||||
|
var previousValidity: [String: Bool] = [:]
|
||||||
for rule in group.rules {
|
for rule in group.rules {
|
||||||
valid = valid && rule.validate(fields)
|
let tuple = rule.validate(fields, previousValidity)
|
||||||
|
let isValidRule = tuple.valid
|
||||||
|
let returnedValidity = tuple.fieldValidity
|
||||||
|
previousValidity = previousValidity.merging(returnedValidity) { (_, new) in new }
|
||||||
|
valid = valid && isValidRule
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify the group watchers of validity.
|
// Notify the group watchers of validity.
|
||||||
@ -112,20 +117,20 @@ import MVMCore
|
|||||||
|
|
||||||
// MARK: Form params
|
// MARK: Form params
|
||||||
// TODO: Temporary hacks, rewrite architecture to support this.
|
// TODO: Temporary hacks, rewrite architecture to support this.
|
||||||
@objc public extension FormValidator {
|
public extension FormValidator {
|
||||||
|
|
||||||
@objc func addFormParams(requestParameters: MVMCoreRequestParameters) {
|
func addFormParams(requestParameters: MVMCoreRequestParameters, model: (MoleculeModelProtocol & FormItemProtocol)?) {
|
||||||
let groupName = getGroupName(forPageType: requestParameters.pageType) ?? FormValidator.defaultGroupName
|
let groupName = model?.groupName ?? getGroupName(forPageType: requestParameters.pageType) ?? FormValidator.defaultGroupName
|
||||||
let formParams = self.getFormParams(forGroup: groupName)
|
let formParams = getFormParams(forGroup: groupName)
|
||||||
requestParameters.add(formParams)
|
requestParameters.add(formParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func getFormParams( forGroup groupName: String) -> [String: Any] {
|
@objc func getFormParams(forGroup groupName: String) -> [String: Any] {
|
||||||
|
|
||||||
var extraParam: [String: Any] = [:]
|
var extraParam: [String: Any] = [:]
|
||||||
for (fieldKey, field) in fields {
|
for (fieldKey, field) in fields {
|
||||||
if let formFieldValue = field.formFieldValue(),
|
if let formFieldValue = field.formFieldValue(),
|
||||||
groupName == field.groupName {
|
groupName == field.groupName {
|
||||||
extraParam[fieldKey] = formFieldValue
|
extraParam[fieldKey] = formFieldValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,16 +141,17 @@ import MVMCore
|
|||||||
|
|
||||||
// TODO: Temporary hacks, rewrite architecture to support this.
|
// TODO: Temporary hacks, rewrite architecture to support this.
|
||||||
public extension FormValidator {
|
public extension FormValidator {
|
||||||
|
|
||||||
func getGroupName(forPageType pageType: String?) -> String? {
|
func getGroupName(forPageType pageType: String?) -> String? {
|
||||||
|
|
||||||
for actionItem in groupWatchers {
|
for actionItem in groupWatchers {
|
||||||
if let buttonModel = actionItem as? ButtonModel,
|
if let buttonModel = actionItem as? ButtonModel,
|
||||||
pageType == (buttonModel.action as? ActionOpenPageModel)?.pageType {
|
pageType == (buttonModel.action as? ActionOpenPageProtocol)?.pageType {
|
||||||
return buttonModel.groupName
|
return buttonModel.groupName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,15 +36,23 @@ public class RuleAnyRequiredModel: RulesProtocol {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool {
|
public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
|
||||||
|
|
||||||
|
var previousValidity: [String: Bool] = [:]
|
||||||
for formKey in fields {
|
for formKey in fields {
|
||||||
guard let formField = fieldMolecules[formKey] else { continue }
|
guard let formField = fieldMolecules[formKey] else { continue }
|
||||||
|
|
||||||
if isValid(formField) {
|
var fieldValidity = isValid(formField)
|
||||||
return true
|
// If past rule is invalid for a field, the current rule should not flip the validity of a field
|
||||||
|
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
|
||||||
|
fieldValidity = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fieldValidity {
|
||||||
|
return (fieldValidity, previousValidity)
|
||||||
|
}
|
||||||
|
previousValidity[formKey] = false
|
||||||
}
|
}
|
||||||
return false
|
return (valid: false, fieldValidity: previousValidity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,13 +27,21 @@ public class RuleAnyValueChangedModel: RulesProtocol {
|
|||||||
return formField.baseValue != formField.formFieldValue()
|
return formField.baseValue != formField.formFieldValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool {
|
public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
|
||||||
|
var previousValidity: [String: Bool] = [:]
|
||||||
for formKey in fields {
|
for formKey in fields {
|
||||||
guard let formField = fieldMolecules[formKey] else { continue }
|
guard let formField = fieldMolecules[formKey] else { continue }
|
||||||
if isValid(formField) {
|
var fieldValidity = isValid(formField)
|
||||||
return true
|
// If past rule is invalid forr a field, the current rule should not flip the validity of a field
|
||||||
|
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
|
||||||
|
fieldValidity = false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false
|
if fieldValidity {
|
||||||
|
return (true, previousValidity)
|
||||||
|
}
|
||||||
|
previousValidity[formKey] = false
|
||||||
|
}
|
||||||
|
return (valid: false, fieldValidity: previousValidity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,10 +27,11 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool {
|
public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
|
||||||
var valid = false
|
var valid = false
|
||||||
var compareText: String?
|
var compareText: String?
|
||||||
|
|
||||||
|
var previousValidity: [String: Bool] = [:]
|
||||||
for formKey in fields {
|
for formKey in fields {
|
||||||
guard let formField = fieldMolecules[formKey] else { continue }
|
guard let formField = fieldMolecules[formKey] else { continue }
|
||||||
|
|
||||||
@ -42,16 +43,23 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol {
|
|||||||
if let fieldValue = formField.formFieldValue() as? String,
|
if let fieldValue = formField.formFieldValue() as? String,
|
||||||
compareString.caseInsensitiveCompare(fieldValue) == .orderedSame {
|
compareString.caseInsensitiveCompare(fieldValue) == .orderedSame {
|
||||||
valid = true
|
valid = true
|
||||||
|
|
||||||
|
var fieldValidity = valid
|
||||||
|
// If past rule is invalid for a field, the current rule should not flip the validity of a field
|
||||||
|
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
|
||||||
|
fieldValidity = false
|
||||||
|
}
|
||||||
|
|
||||||
for formKey in fields {
|
for formKey in fields {
|
||||||
guard let formField = fieldMolecules[formKey] else { continue }
|
guard let formField = fieldMolecules[formKey] else { continue }
|
||||||
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(true, rule: self)
|
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(true, rule: self)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previousValidity[formKey] = valid
|
||||||
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self)
|
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self)
|
||||||
}
|
}
|
||||||
|
return (valid: valid, fieldValidity: previousValidity)
|
||||||
return valid
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,9 +27,10 @@ public class RuleEqualsModel: RulesProtocol {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool {
|
public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
|
||||||
var valid = true
|
var valid = true
|
||||||
var compareValue: AnyHashable?
|
var compareValue: AnyHashable?
|
||||||
|
var previousValidity: [String: Bool] = [:]
|
||||||
|
|
||||||
for formKey in fields {
|
for formKey in fields {
|
||||||
guard let formField = fieldMolecules[formKey] else { continue }
|
guard let formField = fieldMolecules[formKey] else { continue }
|
||||||
@ -41,13 +42,18 @@ public class RuleEqualsModel: RulesProtocol {
|
|||||||
|
|
||||||
if compareValue != formField.formFieldValue() {
|
if compareValue != formField.formFieldValue() {
|
||||||
valid = false
|
valid = false
|
||||||
|
previousValidity[formKey] = valid
|
||||||
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self)
|
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self)
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self)
|
var fieldValidity = valid
|
||||||
|
// If past rule is invalid for a field, the current rule should not flip the validity of a field
|
||||||
|
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
|
||||||
|
fieldValidity = false
|
||||||
|
}
|
||||||
|
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(fieldValidity, rule: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return (valid: valid, fieldValidity: previousValidity)
|
||||||
return valid
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ public protocol RulesProtocol: ModelProtocol {
|
|||||||
func isValid(_ formField: FormFieldProtocol) -> Bool
|
func isValid(_ formField: FormFieldProtocol) -> Bool
|
||||||
|
|
||||||
// Validates the rule and returns the result.
|
// Validates the rule and returns the result.
|
||||||
func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool
|
func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool])
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension RulesProtocol {
|
public extension RulesProtocol {
|
||||||
@ -38,14 +38,21 @@ public extension RulesProtocol {
|
|||||||
static var categoryName: String { "\(RulesProtocol.self)" }
|
static var categoryName: String { "\(RulesProtocol.self)" }
|
||||||
|
|
||||||
// Individual rule can override the function to validate based on the rule type.
|
// Individual rule can override the function to validate based on the rule type.
|
||||||
func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool {
|
func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
|
||||||
var valid = true
|
var valid = true
|
||||||
for formKey in fields {
|
var previousValidity: [String: Bool] = [:]
|
||||||
guard let formField = fieldMolecules[formKey] else { continue }
|
for formKey in fields {
|
||||||
let fieldValidity = isValid(formField)
|
guard let formField = fieldMolecules[formKey] else { continue }
|
||||||
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(fieldValidity, rule: self)
|
|
||||||
valid = valid && fieldValidity
|
var fieldValidity = isValid(formField)
|
||||||
}
|
// If past rule is invalid for a field, the current rule should not flip the validity of a field
|
||||||
return valid
|
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
|
||||||
|
fieldValidity = false
|
||||||
|
}
|
||||||
|
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(fieldValidity, rule: self)
|
||||||
|
valid = valid && fieldValidity
|
||||||
|
previousValidity[formKey] = fieldValidity
|
||||||
|
}
|
||||||
|
return (valid: valid, fieldValidity: previousValidity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
44
MVMCoreUI/Styles/MFStyler+PaddingExtension.swift
Normal file
44
MVMCoreUI/Styles/MFStyler+PaddingExtension.swift
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// MFStyler+PaddingExtension.swift
|
||||||
|
// MVMCoreUI
|
||||||
|
//
|
||||||
|
// Created by Scott Pfeil on 3/25/21.
|
||||||
|
// Copyright © 2021 Verizon Wireless. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
@objc public extension MFStyler {
|
||||||
|
|
||||||
|
@objc static func bridgeGetGutterSizeForApplicationWidth() -> CGFloat {
|
||||||
|
Padding.Component.gutterForApplicationWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc static func bridgeGetGutterSize(for size: CGFloat) -> CGFloat {
|
||||||
|
Padding.Component.gutterFor(size: size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc static func bridgeGetColumnSizeForApplicationWidth() -> CGFloat {
|
||||||
|
Padding.Component.columnFor(size: MVMCoreUISplitViewController.getApplicationViewWidth())
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc static func bridgeGetColumnSize(for size: CGFloat) -> CGFloat {
|
||||||
|
Padding.Component.columnFor(size: size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc static func bridgeHorizontalPadding(for size: CGFloat) -> CGFloat {
|
||||||
|
Padding.Component.horizontalPaddingForSize(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc static func bridgeVerticalPadding(for size: CGFloat) -> CGFloat {
|
||||||
|
Padding.Component.verticalPaddingForSize(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc static func bridgeSetDefaultMargins(for view: UIView, size: CGFloat, horizontal: Bool = true, vertical: Bool = false) {
|
||||||
|
Styler.setDefaultMarginsFor(view, size: size, horizontal: horizontal, vertical: vertical)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc static func bridgeSetMargins(for view: UIView, size: CGFloat, horizontal: Bool, top: CGFloat, bottom: CGFloat) {
|
||||||
|
Styler.setMarginsFor(view, size: size, horizontal: horizontal ? nil : 0, top: top, bottom: bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -57,6 +57,8 @@ extern CGFloat const HeightTableSeperatorHeight;
|
|||||||
extern CGFloat const MFHeightForSwitch;
|
extern CGFloat const MFHeightForSwitch;
|
||||||
extern CGFloat const MFMFWidthForSwitch;
|
extern CGFloat const MFMFWidthForSwitch;
|
||||||
|
|
||||||
|
extern CGFloat const HeaderMaxWidth;
|
||||||
|
|
||||||
// Color constants
|
// Color constants
|
||||||
extern CGFloat const DisableOppacity;
|
extern CGFloat const DisableOppacity;
|
||||||
extern CGFloat const PaymentMethodViewHeightWidthMultiplier;
|
extern CGFloat const PaymentMethodViewHeightWidthMultiplier;
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
#import "MVMCoreUISplitViewController.h"
|
#import "MVMCoreUISplitViewController.h"
|
||||||
@import MVMCore.MVMCoreDispatchUtility;
|
@import MVMCore.MVMCoreDispatchUtility;
|
||||||
#import <MVMCoreUI/MVMCoreUIUtility.h>
|
#import <MVMCoreUI/MVMCoreUIUtility.h>
|
||||||
|
#import <MVMCoreUI/MVMCoreUI-Swift.h>
|
||||||
|
|
||||||
CGFloat const PaddingDefault = 24;
|
CGFloat const PaddingDefault = 24;
|
||||||
CGFloat const PaddingDefaultHorizontalSpacing = 32;
|
CGFloat const PaddingDefaultHorizontalSpacing = 32;
|
||||||
@ -26,7 +27,7 @@ CGFloat const PaddingVerticalWhiteGrayView = 72;
|
|||||||
CGFloat const PaddingVerticalHeadlineAlternate = 48;
|
CGFloat const PaddingVerticalHeadlineAlternate = 48;
|
||||||
CGFloat const PaddingPrimaryButtonTop = 36;
|
CGFloat const PaddingPrimaryButtonTop = 36;
|
||||||
|
|
||||||
CGFloat const PaddingHorizontalBetweenRelatedItems = 16;
|
CGFloat const PaddingHorizontalBetweenRelatedItems = 12;
|
||||||
CGFloat const PaddingOne = 6;
|
CGFloat const PaddingOne = 6;
|
||||||
CGFloat const PaddingTwo = 12;
|
CGFloat const PaddingTwo = 12;
|
||||||
CGFloat const PaddingThree = 18;
|
CGFloat const PaddingThree = 18;
|
||||||
@ -45,6 +46,9 @@ CGFloat const HeightTableSeperatorHeight = 1;
|
|||||||
|
|
||||||
CGFloat const MFHeightForSwitch = 22;
|
CGFloat const MFHeightForSwitch = 22;
|
||||||
CGFloat const MFWidthForSwitch = 42;
|
CGFloat const MFWidthForSwitch = 42;
|
||||||
|
|
||||||
|
CGFloat const HeaderMaxWidth = 596;
|
||||||
|
|
||||||
CGFloat const DisableOppacity = 0.5;
|
CGFloat const DisableOppacity = 0.5;
|
||||||
CGFloat const PaymentMethodViewHeightWidthMultiplier = 0.55;
|
CGFloat const PaymentMethodViewHeightWidthMultiplier = 0.55;
|
||||||
CGFloat const MinCellHeight = 96;
|
CGFloat const MinCellHeight = 96;
|
||||||
@ -77,19 +81,19 @@ CGFloat const LabelWithInternalButtonLineSpace = 2;
|
|||||||
|
|
||||||
#pragma mark - Spacing Defaults
|
#pragma mark - Spacing Defaults
|
||||||
+ (CGFloat)defaultHorizontalPaddingForApplicationWidth {
|
+ (CGFloat)defaultHorizontalPaddingForApplicationWidth {
|
||||||
return [[MFSizeObject sizeObjectWithScalingStandardSize:PaddingDefaultHorizontalSpacing] getValueBasedOnApplicationWidth];
|
return [self bridgeHorizontalPaddingFor:[MVMCoreUISplitViewController getApplicationViewWidth]];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (CGFloat)defaultVerticalPaddingForApplicationWidth {
|
+ (CGFloat)defaultVerticalPaddingForApplicationWidth {
|
||||||
return [[MFSizeObject sizeObjectWithScalingStandardSize:PaddingDefaultVerticalSpacing] getValueBasedOnApplicationWidth];
|
return [self bridgeVerticalPaddingFor:[MVMCoreUISplitViewController getApplicationViewWidth]];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (CGFloat)defaultHorizontalPaddingForSize:(CGFloat)size {
|
+ (CGFloat)defaultHorizontalPaddingForSize:(CGFloat)size {
|
||||||
return [[MFSizeObject sizeObjectWithScalingStandardSize:PaddingDefaultHorizontalSpacing] getValueBasedOnSize:size];
|
return [self bridgeHorizontalPaddingFor:size];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (CGFloat)defaultVerticalPaddingForSize:(CGFloat)size {
|
+ (CGFloat)defaultVerticalPaddingForSize:(CGFloat)size {
|
||||||
return [[MFSizeObject sizeObjectWithScalingStandardSize:PaddingDefaultVerticalSpacing] getValueBasedOnSize:size];
|
return [self bridgeVerticalPaddingFor:size];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (void)setDefaultMarginsForView:(nullable UIView *)view size:(CGFloat)size {
|
+ (void)setDefaultMarginsForView:(nullable UIView *)view size:(CGFloat)size {
|
||||||
@ -97,18 +101,13 @@ CGFloat const LabelWithInternalButtonLineSpace = 2;
|
|||||||
}
|
}
|
||||||
|
|
||||||
+ (void)setDefaultMarginsForView:(nullable UIView *)view size:(CGFloat)size horizontal:(BOOL)horizontal vertical:(BOOL)vertical {
|
+ (void)setDefaultMarginsForView:(nullable UIView *)view size:(CGFloat)size horizontal:(BOOL)horizontal vertical:(BOOL)vertical {
|
||||||
CGFloat horizontalPadding = horizontal ? [MFStyler defaultHorizontalPaddingForSize:size] : 0;
|
if (!view) { return; }
|
||||||
CGFloat verticalPadding = vertical ? PaddingDefaultVerticalSpacing3 : 0;
|
[self bridgeSetDefaultMarginsFor:view size:size horizontal:horizontal vertical:vertical];
|
||||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
|
||||||
[MVMCoreUIUtility setMarginsForView:view leading:horizontalPadding top:verticalPadding trailing:horizontalPadding bottom:verticalPadding];
|
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (void)setMarginsForView:(nullable UIView *)view size:(CGFloat)size defaultHorizontal:(BOOL)horizontal top:(CGFloat)top bottom:(CGFloat)bottom {
|
+ (void)setMarginsForView:(nullable UIView *)view size:(CGFloat)size defaultHorizontal:(BOOL)horizontal top:(CGFloat)top bottom:(CGFloat)bottom {
|
||||||
CGFloat horizontalPadding = horizontal ? [MFStyler defaultHorizontalPaddingForSize:size] : 0;
|
if (!view) { return; }
|
||||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
[self bridgeSetMarginsFor:view size:size horizontal:horizontal top:top bottom:bottom];
|
||||||
[MVMCoreUIUtility setMarginsForView:view leading:horizontalPadding top:top trailing:horizontalPadding bottom:bottom];
|
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - 3.0 fonts
|
#pragma mark - 3.0 fonts
|
||||||
|
|||||||
@ -23,24 +23,41 @@ public struct Padding {
|
|||||||
|
|
||||||
public struct Component {
|
public struct Component {
|
||||||
public static let Standard: CGFloat = 24
|
public static let Standard: CGFloat = 24
|
||||||
public static let HorizontalMarginSpacing: CGFloat = 32
|
public static let HorizontalMarginSpacing: CGFloat = 16
|
||||||
public static let LargeVerticalMarginSpacing: CGFloat = 32
|
public static let LargeHorizontalMarginSpacing: CGFloat = 32
|
||||||
public static let VerticalMarginSpacing: CGFloat = 24
|
public static let VerticalMarginSpacing: CGFloat = 24
|
||||||
|
public static let LargeVerticalMarginSpacing: CGFloat = 32
|
||||||
|
public static let HorizontalGutterSpacing: CGFloat = 12
|
||||||
|
public static let LargeHorizontalGutterSpacing: CGFloat = 24
|
||||||
|
|
||||||
public static var horizontalPaddingForApplicationWidth: CGFloat {
|
public static var horizontalPaddingForApplicationWidth: CGFloat {
|
||||||
MFSizeObject(scalingStandardSize: HorizontalMarginSpacing)?.getValueBasedOnApplicationWidth() ?? HorizontalMarginSpacing
|
horizontalPaddingForSize(MVMCoreUISplitViewController.getApplicationViewWidth())
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var verticalPaddingForApplicationWidth: CGFloat {
|
public static var verticalPaddingForApplicationWidth: CGFloat {
|
||||||
MFSizeObject(scalingStandardSize: VerticalMarginSpacing)?.getValueBasedOnApplicationWidth() ?? VerticalMarginSpacing
|
verticalPaddingForSize(MVMCoreUISplitViewController.getApplicationViewWidth())
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var gutterForApplicationWidth: CGFloat {
|
||||||
|
gutterFor(size: MVMCoreUISplitViewController.getApplicationViewWidth())
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func horizontalPaddingForSize(_ size: CGFloat) -> CGFloat {
|
public static func horizontalPaddingForSize(_ size: CGFloat) -> CGFloat {
|
||||||
MFSizeObject(scalingStandardSize: HorizontalMarginSpacing)?.getValueBased(onSize: size) ?? HorizontalMarginSpacing
|
let sizeObject = MFSizeObject(standardSize: HorizontalMarginSpacing)!
|
||||||
|
return sizeObject.getValueBased(onSize: size)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func verticalPaddingForSize(_ size: CGFloat) -> CGFloat {
|
public static func verticalPaddingForSize(_ size: CGFloat) -> CGFloat {
|
||||||
MFSizeObject(scalingStandardSize: VerticalMarginSpacing)?.getValueBased(onSize: size) ?? VerticalMarginSpacing
|
MFSizeObject(scalingStandardSize: VerticalMarginSpacing)?.getValueBased(onSize: size) ?? VerticalMarginSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func gutterFor(size: CGFloat) -> CGFloat {
|
||||||
|
let sizeObject = MFSizeObject(standardSize: HorizontalGutterSpacing)!
|
||||||
|
return sizeObject.getValueBased(onSize: size)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func columnFor(size: CGFloat) -> CGFloat {
|
||||||
|
return (size - (3.0 * gutterFor(size: size)) - (2.0 * horizontalPaddingForSize(size))) / 4.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -423,7 +423,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)accessibilityFocusChanged:(NSNotification *)notification {
|
- (void)accessibilityFocusChanged:(NSNotification *)notification {
|
||||||
if (![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) {
|
if (notification.userInfo[UIAccessibilityFocusedElementKey] && ![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) {
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityElementFocusedNotification object:nil];
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityElementFocusedNotification object:nil];
|
||||||
[self collapse];
|
[self collapse];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -164,18 +164,19 @@
|
|||||||
[self.iconView removeFromSuperview];
|
[self.iconView removeFromSuperview];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CGFloat horizontalPadding = [MFStyler defaultHorizontalPaddingForApplicationWidth];
|
||||||
if (imageURL) {
|
if (imageURL) {
|
||||||
LoadImageView *imageView = [[LoadImageView alloc] init];
|
LoadImageView *imageView = [[LoadImageView alloc] init];
|
||||||
imageView.translatesAutoresizingMaskIntoConstraints = NO;
|
imageView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||||
[imageView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
|
[imageView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
|
||||||
[self addSubview:imageView];
|
[self addSubview:imageView];
|
||||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=space-[imageView]->=space-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:@{@"space":@(PaddingFive)} views:NSDictionaryOfVariableBindings(imageView)]];
|
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=space-[imageView]->=space-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:@{@"space":@(PaddingFive)} views:NSDictionaryOfVariableBindings(imageView)]];
|
||||||
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-space-[imageView]-space-[centerView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:@{@"space":@(PaddingThree)} views:NSDictionaryOfVariableBindings(imageView,centerView)]];
|
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-space-[imageView]-space-[centerView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:@{@"space":@(horizontalPadding)} views:NSDictionaryOfVariableBindings(imageView,centerView)]];
|
||||||
[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0].active = YES;
|
[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0].active = YES;
|
||||||
self.iconView = imageView;
|
self.iconView = imageView;
|
||||||
[imageView loadImageWithName:imageURL width:@(32)];
|
[imageView loadImageWithName:imageURL width:@(32)];
|
||||||
} else {
|
} else {
|
||||||
[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:PaddingThree].active = YES;
|
[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:horizontalPadding].active = YES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,6 +194,7 @@
|
|||||||
- (void)setupWithButton:(BOOL)showButton {
|
- (void)setupWithButton:(BOOL)showButton {
|
||||||
|
|
||||||
// Setup the button.
|
// Setup the button.
|
||||||
|
CGFloat horizontalPadding = [MFStyler defaultHorizontalPaddingForApplicationWidth];
|
||||||
if (showButton) {
|
if (showButton) {
|
||||||
|
|
||||||
if (!self.button) {
|
if (!self.button) {
|
||||||
@ -210,7 +212,7 @@
|
|||||||
[self addSubview:button];
|
[self addSubview: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 : horizontalPadding)].active = YES;
|
||||||
self.button = button;
|
self.button = button;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -222,7 +224,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!self.labelRightConstraint) {
|
if (!self.labelRightConstraint) {
|
||||||
self.labelRightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.centerView attribute:NSLayoutAttributeRight multiplier:1 constant:(self.closeButton ? PaddingTen : PaddingFive)];
|
self.labelRightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.centerView attribute:NSLayoutAttributeRight multiplier:1 constant:(self.closeButton ? PaddingTen : horizontalPadding)];
|
||||||
}
|
}
|
||||||
self.labelRightConstraint.active = YES;
|
self.labelRightConstraint.active = YES;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -335,7 +335,7 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed.";
|
|||||||
|
|
||||||
/// If the voice over user leaves top alert focus, hide.
|
/// If the voice over user leaves top alert focus, hide.
|
||||||
- (void)accessibilityFocusChanged:(NSNotification *)notification {
|
- (void)accessibilityFocusChanged:(NSNotification *)notification {
|
||||||
if (![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) {
|
if (notification.userInfo[UIAccessibilityFocusedElementKey] && ![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) {
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityElementFocusedNotification object:nil];
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityElementFocusedNotification object:nil];
|
||||||
[self hideAlertView:YES completionHandler:self.hideCompletionHandler];
|
[self hideAlertView:YES completionHandler:self.hideCompletionHandler];
|
||||||
self.hideCompletionHandler = nil;
|
self.hideCompletionHandler = nil;
|
||||||
|
|||||||
@ -23,11 +23,11 @@ public extension MVMCoreUICommonViewsUtility {
|
|||||||
view.addSubview(button)
|
view.addSubview(button)
|
||||||
var constraints: [NSLayoutConstraint] = []
|
var constraints: [NSLayoutConstraint] = []
|
||||||
if centeredVertically {
|
if centeredVertically {
|
||||||
constraints.append(view.rightAnchor.constraint(equalTo: button.rightAnchor, constant: PaddingTwo))
|
constraints.append(view.rightAnchor.constraint(equalTo: button.rightAnchor, constant: Padding.Component.horizontalPaddingForApplicationWidth))
|
||||||
constraints.append(view.centerYAnchor.constraint(equalTo: button.centerYAnchor))
|
constraints.append(view.centerYAnchor.constraint(equalTo: button.centerYAnchor))
|
||||||
} else {
|
} else {
|
||||||
constraints.append(button.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: PaddingOne))
|
constraints.append(button.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: PaddingOne))
|
||||||
constraints.append(view.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: button.rightAnchor, constant: PaddingTwo))
|
constraints.append(view.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: button.rightAnchor, constant: Padding.Component.horizontalPaddingForApplicationWidth))
|
||||||
}
|
}
|
||||||
NSLayoutConstraint.activate(constraints)
|
NSLayoutConstraint.activate(constraints)
|
||||||
return button
|
return button
|
||||||
|
|||||||
@ -17,6 +17,7 @@ extern CGFloat const MFSizeLargeiPhoneThreshold;
|
|||||||
extern CGFloat const MFSizeStandardiPadPortraitThreshold;
|
extern CGFloat const MFSizeStandardiPadPortraitThreshold;
|
||||||
extern CGFloat const MFSizeStandardiPadLandscapeThreshold;
|
extern CGFloat const MFSizeStandardiPadLandscapeThreshold;
|
||||||
extern CGFloat const MFSizeiPadProLandscapeThreshold;
|
extern CGFloat const MFSizeiPadProLandscapeThreshold;
|
||||||
|
extern CGFloat const MFSizeMostlySweetSpotThreshold;
|
||||||
|
|
||||||
@interface MFSizeObject : NSObject
|
@interface MFSizeObject : NSObject
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,8 @@ CGFloat const MFSizeLargeiPhoneThreshold = 400;
|
|||||||
CGFloat const MFSizeStandardiPadPortraitThreshold = 600;
|
CGFloat const MFSizeStandardiPadPortraitThreshold = 600;
|
||||||
CGFloat const MFSizeStandardiPadLandscapeThreshold = 1000;
|
CGFloat const MFSizeStandardiPadLandscapeThreshold = 1000;
|
||||||
CGFloat const MFSizeiPadProLandscapeThreshold = 1300;
|
CGFloat const MFSizeiPadProLandscapeThreshold = 1300;
|
||||||
|
CGFloat const MFSizeMostlySweetSpotThreshold = 750;
|
||||||
|
|
||||||
|
|
||||||
@interface MFSizeObject ()
|
@interface MFSizeObject ()
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user