Compare commits

..

No commits in common. "5b4358e9e93908ab7aef4110773956567c130ec5" and "d99a7ee546935756c0439724eea6e6194a8c26e2" have entirely different histories.

9 changed files with 88 additions and 61 deletions

View File

@ -43,7 +43,6 @@
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)
@ -64,9 +63,8 @@
bottomLabelConstraint.isActive = true bottomLabelConstraint.isActive = true
alignCheckbox(.center) alignCheckbox(.center)
isAccessibilityElement = true isAccessibilityElement = false
accessibilityHint = checkbox.accessibilityHint accessibilityElements = [checkbox, label]
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()
} }
@ -139,6 +137,9 @@
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)")
}
} }
} }

View File

@ -182,7 +182,29 @@ 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() {
@ -199,6 +221,7 @@ 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
@ -206,8 +229,9 @@ public typealias ActionBlock = () -> ()
if let fontSize { if let fontSize {
standardFontSize = fontSize standardFontSize = fontSize
} }
if let newFont = UIFont(name: fontName, size: fontSize ?? standardFontSize) { if let customStyle = style(for: fontName, pointSize: fontSize ?? standardFontSize), customStyle != textStyle {
font = newFont font = customStyle.font
textStyle = customStyle
} }
} }
@ -224,6 +248,26 @@ 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()
@ -418,8 +462,6 @@ 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
@ -439,8 +481,6 @@ 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 }

View File

@ -54,3 +54,12 @@ 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
}
}

View File

@ -41,6 +41,16 @@ 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

View File

@ -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,7 +34,6 @@ 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)
} }
//-------------------------------------------------- //--------------------------------------------------
@ -71,8 +70,6 @@ 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
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -38,7 +38,6 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol {
case backgroundColor case backgroundColor
case primaryButton case primaryButton
case secondaryButton case secondaryButton
case fillContainer
} }
//-------------------------------------------------- //--------------------------------------------------
@ -70,7 +69,6 @@ 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 {
@ -80,6 +78,5 @@ 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)
} }
} }

View File

@ -55,9 +55,6 @@ 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
@ -92,10 +89,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 { [self] in DispatchQueue.main.async {
collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: itemAlignment, animated: false) self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false)
collectionView.layoutIfNeeded() self.collectionView.layoutIfNeeded()
showPeaking(true) self.showPeaking(true)
} }
} }
@ -140,16 +137,6 @@ 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)
}
}
} }
//-------------------------------------------------- //--------------------------------------------------
@ -289,8 +276,7 @@ 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
pagingBottomPin = bottomAnchor.constraint(greaterThanOrEqualTo: pagingView.bottomAnchor) bottomAnchor.constraint(greaterThanOrEqualTo: pagingView.bottomAnchor).isActive = true
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

View File

@ -18,8 +18,6 @@
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 {
@ -62,8 +60,6 @@
case line case line
case scrollToRowIndex case scrollToRowIndex
case singleCellSelection case singleCellSelection
case footerlessSpacerHeight
case footerlessSpacerColor
} }
//-------------------------------------------------- //--------------------------------------------------
@ -78,8 +74,6 @@
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()
} }
@ -91,7 +85,5 @@
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)
} }
} }

View File

@ -69,21 +69,16 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
return molecule return molecule
} }
open override func viewForBottom() -> UIView { override open func viewForBottom() -> UIView {
// If there is a footer molecule return the molecule. guard let footerModel = templateModel?.footer,
if let footerModel = templateModel?.footer, let molecule = generateMoleculeView(from: footerModel)
let molecule = generateMoleculeView(from: footerModel) { else {
return molecule let view = super.viewForBottom()
view.backgroundColor = templateModel?.backgroundColor?.uiColor ?? .clear
return view
} }
// Otherwise setup a bottom spacer view.
let view: UIView return molecule
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() {