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.
|
||||||
|
|||||||
@ -53,7 +53,7 @@ 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
|
||||||
@ -88,7 +88,7 @@ open class Carousel: View {
|
|||||||
|
|
||||||
// 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()
|
||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,8 +394,8 @@ extension Carousel: UICollectionViewDataSource {
|
|||||||
|
|
||||||
open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
guard let molecule = molecules?[indexPath.row],
|
guard let molecule = molecules?[indexPath.row],
|
||||||
let moleculeInfo = getMoleculeInfo(with: molecule, delegateObject: nil)
|
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,15 +458,15 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
@ -553,7 +566,7 @@ class CarouselAccessibilityElement: UIAccessibilityElement {
|
|||||||
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 {
|
||||||
@ -565,8 +578,8 @@ class CarouselAccessibilityElement: UIAccessibilityElement {
|
|||||||
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)
|
||||||
@ -589,9 +602,9 @@ class CarouselAccessibilityElement: UIAccessibilityElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
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 }
|
||||||
|
|
||||||
@ -604,9 +617,9 @@ class CarouselAccessibilityElement: UIAccessibilityElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
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 }
|
||||||
|
|
||||||
@ -619,9 +632,9 @@ class CarouselAccessibilityElement: UIAccessibilityElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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.
|
||||||
@ -634,10 +647,10 @@ class CarouselAccessibilityElement: UIAccessibilityElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,7 +146,7 @@ public extension FormValidator {
|
|||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,3 +154,4 @@ public extension FormValidator {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fieldValidity {
|
||||||
|
return (true, previousValidity)
|
||||||
|
}
|
||||||
|
previousValidity[formKey] = false
|
||||||
}
|
}
|
||||||
return 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,6 +43,13 @@ 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)
|
||||||
@ -49,9 +57,9 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol {
|
|||||||
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