Compare commits
39 Commits
d99a7ee546
...
5b4358e9e9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b4358e9e9 | ||
|
|
b021e14030 | ||
|
|
e1f248875e | ||
|
|
51315abf49 | ||
|
|
59ea9ca2bd | ||
|
|
3219d9f351 | ||
|
|
6ab685d906 | ||
|
|
869804cc39 | ||
|
|
09081b8bc8 | ||
|
|
2505559444 | ||
|
|
37fadb9a29 | ||
|
|
054a70d41b | ||
|
|
8f166223d0 | ||
|
|
a46f52e87b | ||
|
|
91423f87f4 | ||
|
|
47178a33d3 | ||
|
|
25c8c139a0 | ||
|
|
5c414a0e40 | ||
|
|
9b831bfa35 | ||
|
|
582cd33a1c | ||
|
|
b9675ce5f1 | ||
|
|
80f3524d43 | ||
|
|
8d57b0ae0e | ||
|
|
5ba1260383 | ||
|
|
bfe209305d | ||
|
|
1f2753e9d7 | ||
|
|
19b4fb308c | ||
|
|
6e2d1d7132 | ||
|
|
483454d094 | ||
|
|
959bdce13f | ||
|
|
f53847372a | ||
|
|
73d13ba37b | ||
|
|
2877534c70 | ||
|
|
b638637af7 | ||
|
|
dcb5b91e32 | ||
|
|
f0d9664d16 | ||
|
|
deac7b37f0 | ||
|
|
d31b9f7880 | ||
|
|
5834b8fcd2 |
@ -43,6 +43,7 @@
|
|||||||
addSubview(label)
|
addSubview(label)
|
||||||
|
|
||||||
label.text = ""
|
label.text = ""
|
||||||
|
|
||||||
checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true
|
checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true
|
||||||
|
|
||||||
checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: checkbox.bottomAnchor)
|
checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: checkbox.bottomAnchor)
|
||||||
@ -63,8 +64,9 @@
|
|||||||
bottomLabelConstraint.isActive = true
|
bottomLabelConstraint.isActive = true
|
||||||
|
|
||||||
alignCheckbox(.center)
|
alignCheckbox(.center)
|
||||||
isAccessibilityElement = false
|
isAccessibilityElement = true
|
||||||
accessibilityElements = [checkbox, label]
|
accessibilityHint = checkbox.accessibilityHint
|
||||||
|
accessibilityTraits = checkbox.accessibilityTraits
|
||||||
observation = observe(\.checkbox.isSelected, options: [.new]) { [weak self] _, _ in
|
observation = observe(\.checkbox.isSelected, options: [.new]) { [weak self] _, _ in
|
||||||
self?.updateAccessibilityLabel()
|
self?.updateAccessibilityLabel()
|
||||||
}
|
}
|
||||||
@ -137,9 +139,6 @@
|
|||||||
|
|
||||||
open func updateAccessibilityLabel() {
|
open func updateAccessibilityLabel() {
|
||||||
checkbox.updateAccessibilityLabel()
|
checkbox.updateAccessibilityLabel()
|
||||||
|
accessibilityLabel = [checkbox.accessibilityLabel, label.text].compactMap { $0 }.joined(separator: ",")
|
||||||
if let text = label.text {
|
|
||||||
checkbox.accessibilityLabel?.append(", \(text)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -182,29 +182,7 @@ public typealias ActionBlock = () -> ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc public func resetAttributeStyle() {
|
@objc public func resetAttributeStyle() {
|
||||||
/*
|
setNeedsUpdate()
|
||||||
* This is to address a reuse issue with iOS 13 and up.
|
|
||||||
* Even if you set text & attributedText to nil, the moment you set text with a value,
|
|
||||||
* attributedText will hold a dirty value from a previously reused cell even if reset() is
|
|
||||||
* appropriately called.
|
|
||||||
* Only other reference found of issue: https://www.thetopsites.net/article/58142205.shtml
|
|
||||||
*/
|
|
||||||
if let text = text, !text.isEmpty {
|
|
||||||
|
|
||||||
//create the primary string
|
|
||||||
let mutableText = NSMutableAttributedString.mutableText(for: text,
|
|
||||||
textStyle: textStyle,
|
|
||||||
useScaledFont: useScaledFont,
|
|
||||||
textColor: textColorConfiguration.getColor(self),
|
|
||||||
alignment: textAlignment,
|
|
||||||
lineBreakMode: lineBreakMode)
|
|
||||||
|
|
||||||
if let attributes = attributes {
|
|
||||||
mutableText.apply(attributes: attributes)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.attributedText = mutableText
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func viewModelDidUpdate() {
|
public func viewModelDidUpdate() {
|
||||||
@ -221,7 +199,6 @@ public typealias ActionBlock = () -> ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let style = viewModel.fontStyle?.vdsTextStyle() {
|
if let style = viewModel.fontStyle?.vdsTextStyle() {
|
||||||
font = style.font
|
|
||||||
textStyle = style
|
textStyle = style
|
||||||
} else if let fontName = viewModel.fontName {
|
} else if let fontName = viewModel.fontName {
|
||||||
// there is a TextStyle.defaultStyle
|
// there is a TextStyle.defaultStyle
|
||||||
@ -229,9 +206,8 @@ public typealias ActionBlock = () -> ()
|
|||||||
if let fontSize {
|
if let fontSize {
|
||||||
standardFontSize = fontSize
|
standardFontSize = fontSize
|
||||||
}
|
}
|
||||||
if let customStyle = style(for: fontName, pointSize: fontSize ?? standardFontSize), customStyle != textStyle {
|
if let newFont = UIFont(name: fontName, size: fontSize ?? standardFontSize) {
|
||||||
font = customStyle.font
|
font = newFont
|
||||||
textStyle = customStyle
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,26 +224,6 @@ public typealias ActionBlock = () -> ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See if the font that is currently set matches a VDS Font and if so grab the matching TextStyle or create custom TextStyle that
|
|
||||||
/// that the Label will use moving forward.
|
|
||||||
private func checkforFontChange() {
|
|
||||||
guard let customStyle = style(for: font.fontName, pointSize: font.pointSize), customStyle != textStyle
|
|
||||||
else { return }
|
|
||||||
textStyle = customStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
private func style(for fontName: String, pointSize: CGFloat) -> TextStyle? {
|
|
||||||
guard let vdsFont = Font.from(fontName: fontName),
|
|
||||||
let customStyle = TextStyle.style(from: vdsFont, pointSize: pointSize)
|
|
||||||
else { return nil }
|
|
||||||
return customStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
open override func updateView() {
|
|
||||||
checkforFontChange()
|
|
||||||
super.updateView()
|
|
||||||
}
|
|
||||||
|
|
||||||
open override func updateAccessibility() {
|
open override func updateAccessibility() {
|
||||||
super.updateAccessibility()
|
super.updateAccessibility()
|
||||||
@ -462,6 +418,8 @@ extension Label {
|
|||||||
|
|
||||||
/// Underlines the tappable region and stores the tap logic for interation.
|
/// Underlines the tappable region and stores the tap logic for interation.
|
||||||
private func setTextLinkState(range: NSRange, actionBlock: @escaping ActionBlock) {
|
private func setTextLinkState(range: NSRange, actionBlock: @escaping ActionBlock) {
|
||||||
|
guard let text, text.isValid(range: range) else { return }
|
||||||
|
|
||||||
var textLink = ActionLabelAttribute(location: range.location, length: range.length)
|
var textLink = ActionLabelAttribute(location: range.location, length: range.length)
|
||||||
textLink.subscriber = textLink
|
textLink.subscriber = textLink
|
||||||
.action
|
.action
|
||||||
@ -481,6 +439,8 @@ extension Label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func createActionBlockFor(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> ActionBlock? {
|
public func createActionBlockFor(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> ActionBlock? {
|
||||||
|
guard let actionMap else { return nil }
|
||||||
|
|
||||||
return { [weak self] in
|
return { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
|||||||
@ -54,12 +54,3 @@ extension VDS.Font {
|
|||||||
Self.allCases.filter({$0.fontName == fontName }).first
|
Self.allCases.filter({$0.fontName == fontName }).first
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension VDS.TextStyle {
|
|
||||||
internal static func style(from font: VDS.Font, pointSize: CGFloat) -> TextStyle? {
|
|
||||||
guard let first = allCases.filter({$0.fontFace == font && $0.pointSize == pointSize}).first else {
|
|
||||||
return TextStyle(rawValue: "Custom-TextStyle", fontFace: font, pointSize: pointSize)
|
|
||||||
}
|
|
||||||
return first
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -41,16 +41,6 @@ import VDS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
|
||||||
// MARK: - Public Properties Overrides
|
|
||||||
//--------------------------------------------------
|
|
||||||
open override var selectedIndex: Int {
|
|
||||||
didSet {
|
|
||||||
guard let viewModel else { return }
|
|
||||||
viewModel.selectedIndex = selectedIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------------------------------------
|
//-------------------------------------------------
|
||||||
// MARK: - Layout Views
|
// MARK: - Layout Views
|
||||||
|
|||||||
@ -17,11 +17,11 @@ import VDS
|
|||||||
open var viewModel: TwoButtonViewModel!
|
open var viewModel: TwoButtonViewModel!
|
||||||
open var delegateObject: MVMCoreUIDelegateObject?
|
open var delegateObject: MVMCoreUIDelegateObject?
|
||||||
open var additionalData: [AnyHashable : Any]?
|
open var additionalData: [AnyHashable : Any]?
|
||||||
|
|
||||||
open var primaryButton = PillButton()
|
open var primaryButton = PillButton()
|
||||||
open var secondaryButton = PillButton()
|
open var secondaryButton = PillButton()
|
||||||
private var buttonGroup = VDS.ButtonGroup()
|
private var buttonGroup = VDS.ButtonGroup()
|
||||||
|
private var heightConstraint: NSLayoutConstraint?
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Lifecycle
|
// MARK: - Lifecycle
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -34,6 +34,7 @@ import VDS
|
|||||||
buttonGroup.alignment = .center
|
buttonGroup.alignment = .center
|
||||||
buttonGroup.rowQuantityPhone = 2
|
buttonGroup.rowQuantityPhone = 2
|
||||||
buttonGroup.rowQuantityTablet = 2
|
buttonGroup.rowQuantityTablet = 2
|
||||||
|
heightConstraint = height(constant: VDS.Button.Size.large.height, priority: .required)
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -70,6 +71,8 @@ import VDS
|
|||||||
if buttons.count != buttonGroup.buttons.count {
|
if buttons.count != buttonGroup.buttons.count {
|
||||||
buttonGroup.buttons = buttons
|
buttonGroup.buttons = buttons
|
||||||
}
|
}
|
||||||
|
|
||||||
|
heightConstraint?.constant = primaryButton.size == .small || secondaryButton.size == .small ? VDS.Button.Size.small.height : VDS.Button.Size.large.height
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|||||||
@ -38,6 +38,7 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol {
|
|||||||
case backgroundColor
|
case backgroundColor
|
||||||
case primaryButton
|
case primaryButton
|
||||||
case secondaryButton
|
case secondaryButton
|
||||||
|
case fillContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -69,6 +70,7 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol {
|
|||||||
try decoder.setContext(value: Use.secondary, for: "style") {
|
try decoder.setContext(value: Use.secondary, for: "style") {
|
||||||
self.secondaryButton = try typeContainer.decodeMoleculeIfPresent(codingKey: .secondaryButton)
|
self.secondaryButton = try typeContainer.decodeMoleculeIfPresent(codingKey: .secondaryButton)
|
||||||
}
|
}
|
||||||
|
fillContainer = try typeContainer.decodeIfPresent(Bool.self, forKey: .fillContainer) ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -78,5 +80,6 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol {
|
|||||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||||
try container.encodeIfPresent(primaryButton, forKey: .primaryButton)
|
try container.encodeIfPresent(primaryButton, forKey: .primaryButton)
|
||||||
try container.encodeIfPresent(secondaryButton, forKey: .secondaryButton)
|
try container.encodeIfPresent(secondaryButton, forKey: .secondaryButton)
|
||||||
|
try container.encodeIfPresent(fillContainer, forKey: .fillContainer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,6 +55,9 @@ open class Carousel: View {
|
|||||||
/// The view that we use for paging
|
/// The view that we use for paging
|
||||||
public var pagingView: (MoleculeViewProtocol & CarouselPageControlProtocol)?
|
public var pagingView: (MoleculeViewProtocol & CarouselPageControlProtocol)?
|
||||||
|
|
||||||
|
/// The pagingView anchor to the bottom of the carousel. Disabled when the pagingView is hidden.
|
||||||
|
public var pagingBottomPin: NSLayoutConstraint?
|
||||||
|
|
||||||
/// 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
|
||||||
|
|
||||||
@ -89,10 +92,10 @@ 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, !model.molecules.isEmpty,
|
guard let model = model as? CarouselModel, !model.molecules.isEmpty,
|
||||||
(model.paging == true || loop == true) else { return }
|
(model.paging == true || loop == true) else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async { [self] in
|
||||||
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false)
|
collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: itemAlignment, animated: false)
|
||||||
self.collectionView.layoutIfNeeded()
|
collectionView.layoutIfNeeded()
|
||||||
self.showPeaking(true)
|
showPeaking(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +140,16 @@ open class Carousel: View {
|
|||||||
(cell as? MVMCoreViewProtocol)?.updateView(size)
|
(cell as? MVMCoreViewProtocol)?.updateView(size)
|
||||||
}
|
}
|
||||||
layoutCollection()
|
layoutCollection()
|
||||||
|
|
||||||
|
// Check must be dispatched to main for the layout to complete in layoutCollection.
|
||||||
|
DispatchQueue.main.async { [self] in
|
||||||
|
let shouldHidePager = molecules?.count ?? 0 < 2 || collectionView.contentSize.width < bounds.width
|
||||||
|
if let pagingView = pagingView, shouldHidePager != pagingView.isHidden {
|
||||||
|
pagingView.isHidden = shouldHidePager
|
||||||
|
pagingBottomPin?.isActive = !shouldHidePager
|
||||||
|
delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -276,7 +289,8 @@ open class Carousel: View {
|
|||||||
addSubview(pagingView)
|
addSubview(pagingView)
|
||||||
pagingView.centerXAnchor.constraint(equalTo: collectionView.centerXAnchor).isActive = true
|
pagingView.centerXAnchor.constraint(equalTo: collectionView.centerXAnchor).isActive = true
|
||||||
collectionView.bottomAnchor.constraint(equalTo: pagingView.centerYAnchor, constant: position).isActive = true
|
collectionView.bottomAnchor.constraint(equalTo: pagingView.centerYAnchor, constant: position).isActive = true
|
||||||
bottomAnchor.constraint(greaterThanOrEqualTo: pagingView.bottomAnchor).isActive = true
|
pagingBottomPin = bottomAnchor.constraint(greaterThanOrEqualTo: pagingView.bottomAnchor)
|
||||||
|
pagingBottomPin?.isActive = true
|
||||||
bottomPin = bottomAnchor.constraint(equalTo: collectionView.bottomAnchor)
|
bottomPin = bottomAnchor.constraint(equalTo: collectionView.bottomAnchor)
|
||||||
bottomPin?.priority = .defaultLow
|
bottomPin?.priority = .defaultLow
|
||||||
bottomPin?.isActive = true
|
bottomPin?.isActive = true
|
||||||
|
|||||||
@ -18,6 +18,8 @@
|
|||||||
public var line: LineModel?
|
public var line: LineModel?
|
||||||
public var scrollToRowIndex: Int?
|
public var scrollToRowIndex: Int?
|
||||||
public var singleCellSelection: Bool = false
|
public var singleCellSelection: Bool = false
|
||||||
|
public var footerlessSpacerHeight: CGFloat?
|
||||||
|
public var footerlessSpacerColor: Color?
|
||||||
|
|
||||||
public override var rootMolecules: [MoleculeModelProtocol] {
|
public override var rootMolecules: [MoleculeModelProtocol] {
|
||||||
if let molecules = molecules {
|
if let molecules = molecules {
|
||||||
@ -60,6 +62,8 @@
|
|||||||
case line
|
case line
|
||||||
case scrollToRowIndex
|
case scrollToRowIndex
|
||||||
case singleCellSelection
|
case singleCellSelection
|
||||||
|
case footerlessSpacerHeight
|
||||||
|
case footerlessSpacerColor
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -74,6 +78,8 @@
|
|||||||
if let singleCellSelection = try typeContainer.decodeIfPresent(Bool.self, forKey: .singleCellSelection) {
|
if let singleCellSelection = try typeContainer.decodeIfPresent(Bool.self, forKey: .singleCellSelection) {
|
||||||
self.singleCellSelection = singleCellSelection
|
self.singleCellSelection = singleCellSelection
|
||||||
}
|
}
|
||||||
|
footerlessSpacerColor = try typeContainer.decodeIfPresent(Color.self, forKey: .footerlessSpacerColor)
|
||||||
|
footerlessSpacerHeight = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .footerlessSpacerHeight)
|
||||||
try super.init(from: decoder)
|
try super.init(from: decoder)
|
||||||
try validateModelHasContent()
|
try validateModelHasContent()
|
||||||
}
|
}
|
||||||
@ -85,5 +91,7 @@
|
|||||||
try container.encode(line, forKey: .line)
|
try container.encode(line, forKey: .line)
|
||||||
try container.encode(singleCellSelection, forKey: .singleCellSelection)
|
try container.encode(singleCellSelection, forKey: .singleCellSelection)
|
||||||
try container.encodeIfPresent(scrollToRowIndex, forKey: .scrollToRowIndex)
|
try container.encodeIfPresent(scrollToRowIndex, forKey: .scrollToRowIndex)
|
||||||
|
try container.encodeIfPresent(footerlessSpacerColor, forKey: .footerlessSpacerColor)
|
||||||
|
try container.encodeIfPresent(footerlessSpacerHeight, forKey: .footerlessSpacerHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,16 +69,21 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
|
|||||||
return molecule
|
return molecule
|
||||||
}
|
}
|
||||||
|
|
||||||
override open func viewForBottom() -> UIView {
|
open override func viewForBottom() -> UIView {
|
||||||
guard let footerModel = templateModel?.footer,
|
// If there is a footer molecule return the molecule.
|
||||||
let molecule = generateMoleculeView(from: footerModel)
|
if let footerModel = templateModel?.footer,
|
||||||
else {
|
let molecule = generateMoleculeView(from: footerModel) {
|
||||||
let view = super.viewForBottom()
|
return molecule
|
||||||
view.backgroundColor = templateModel?.backgroundColor?.uiColor ?? .clear
|
|
||||||
return view
|
|
||||||
}
|
}
|
||||||
|
// Otherwise setup a bottom spacer view.
|
||||||
return molecule
|
let view: UIView
|
||||||
|
if let footerlessSpacerHeight = templateModel?.footerlessSpacerHeight {
|
||||||
|
view = MVMCoreUICommonViewsUtility.getView(with: footerlessSpacerHeight <= 0.5 ? 0.5 : footerlessSpacerHeight)
|
||||||
|
} else {
|
||||||
|
view = super.viewForBottom()
|
||||||
|
}
|
||||||
|
view.backgroundColor = templateModel?.footerlessSpacerColor?.uiColor ?? templateModel?.backgroundColor?.uiColor ?? .clear
|
||||||
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func handleNewData() {
|
open override func handleNewData() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user