Merge branch 'feature/notificationView' into 'develop'

Notification View

See merge request BPHV_MIPS/vds_ios!148
This commit is contained in:
Bruce, Matt R 2024-02-14 16:57:28 +00:00
commit fce97ddd96
6 changed files with 139 additions and 34 deletions

View File

@ -12,6 +12,7 @@
44604AD729CE196600E62B51 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD629CE196600E62B51 /* Line.swift */; }; 44604AD729CE196600E62B51 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD629CE196600E62B51 /* Line.swift */; };
5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */; }; 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */; };
5FC35BE328D51405004EBEAC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE228D51405004EBEAC /* Button.swift */; }; 5FC35BE328D51405004EBEAC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE228D51405004EBEAC /* Button.swift */; };
71C02B382B7BD98F00E93E66 /* NotificationChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */; };
EA0B18022A9E236900F2D0CD /* SelectorGroupBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */; }; EA0B18022A9E236900F2D0CD /* SelectorGroupBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */; };
EA0B18052A9E2D2D00F2D0CD /* SelectorBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18032A9E2D2D00F2D0CD /* SelectorBase.swift */; }; EA0B18052A9E2D2D00F2D0CD /* SelectorBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18032A9E2D2D00F2D0CD /* SelectorBase.swift */; };
EA0B18062A9E2D2D00F2D0CD /* SelectorItemBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18042A9E2D2D00F2D0CD /* SelectorItemBase.swift */; }; EA0B18062A9E2D2D00F2D0CD /* SelectorItemBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18042A9E2D2D00F2D0CD /* SelectorItemBase.swift */; };
@ -173,6 +174,7 @@
44604AD629CE196600E62B51 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = "<group>"; }; 44604AD629CE196600E62B51 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = "<group>"; };
5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = "<group>"; }; 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = "<group>"; };
5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; }; 5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = NotificationChangeLog.txt; sourceTree = "<group>"; };
EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorGroupBase.swift; sourceTree = "<group>"; }; EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorGroupBase.swift; sourceTree = "<group>"; };
EA0B18032A9E2D2D00F2D0CD /* SelectorBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorBase.swift; sourceTree = "<group>"; }; EA0B18032A9E2D2D00F2D0CD /* SelectorBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorBase.swift; sourceTree = "<group>"; };
EA0B18042A9E2D2D00F2D0CD /* SelectorItemBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorItemBase.swift; sourceTree = "<group>"; }; EA0B18042A9E2D2D00F2D0CD /* SelectorItemBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorItemBase.swift; sourceTree = "<group>"; };
@ -347,6 +349,7 @@
children = ( children = (
445BA07729C07B3D0036A7C5 /* Notification.swift */, 445BA07729C07B3D0036A7C5 /* Notification.swift */,
44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */, 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */,
71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */,
); );
path = Notification; path = Notification;
sourceTree = "<group>"; sourceTree = "<group>";
@ -928,6 +931,7 @@
files = ( files = (
EAEEECA42B1F934600531FC2 /* IconChangeLog.txt in Resources */, EAEEECA42B1F934600531FC2 /* IconChangeLog.txt in Resources */,
EA3362042891E14D0071C351 /* VerizonNHGeTX-Bold.otf in Resources */, EA3362042891E14D0071C351 /* VerizonNHGeTX-Bold.otf in Resources */,
71C02B382B7BD98F00E93E66 /* NotificationChangeLog.txt in Resources */,
EAEEECA72B1F952000531FC2 /* TabsChangeLog.txt in Resources */, EAEEECA72B1F952000531FC2 /* TabsChangeLog.txt in Resources */,
EAEEEC962B1F893B00531FC2 /* ButtonChangeLog.txt in Resources */, EAEEEC962B1F893B00531FC2 /* ButtonChangeLog.txt in Resources */,
EA5F86CC2A1D28B500BC83E4 /* ReleaseNotes.txt in Resources */, EA5F86CC2A1D28B500BC83E4 /* ReleaseNotes.txt in Resources */,

View File

@ -7,6 +7,7 @@
import Foundation import Foundation
import UIKit import UIKit
import Combine
/// UICollectionView subclassed to deal with Changing the size of itself based on its children and layout and changes of its contentSize. /// UICollectionView subclassed to deal with Changing the size of itself based on its children and layout and changes of its contentSize.
@objc(VDSSelfSizingCollectionView) @objc(VDSSelfSizingCollectionView)
@ -34,10 +35,13 @@ public final class SelfSizingCollectionView: UICollectionView {
// MARK: - Private Properties // MARK: - Private Properties
//-------------------------------------------------- //--------------------------------------------------
private var contentSizeObservation: NSKeyValueObservation? private var contentSizeObservation: NSKeyValueObservation?
private var collectionViewHeight: NSLayoutConstraint?
private var anyCancellable: AnyCancellable?
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Overrides // MARK: - Overrides
//-------------------------------------------------- //--------------------------------------------------
/// The natural size for the receiving view, considering only properties of the view itself. /// The natural size for the receiving view, considering only properties of the view itself.
public override var intrinsicContentSize: CGSize { public override var intrinsicContentSize: CGSize {
let contentSize = self.contentSize let contentSize = self.contentSize
@ -63,12 +67,16 @@ public final class SelfSizingCollectionView: UICollectionView {
//ensure autoLayout uses intrinsic height //ensure autoLayout uses intrinsic height
setContentHuggingPriority(.required, for: .vertical) setContentHuggingPriority(.required, for: .vertical)
setContentCompressionResistancePriority(.required, for: .vertical) setContentCompressionResistancePriority(.required, for: .vertical)
collectionViewHeight = heightAnchor.constraint(equalToConstant: 0).activate()
// Observing the value of contentSize seems to be the only reliable way to get the contentSize after the collection view lays out its subviews. // Observing the value of contentSize seems to be the only reliable way to get the contentSize after the collection view lays out its subviews.
self.contentSizeObservation = self.observe(\.contentSize, options: [.old, .new]) { [weak self] _, change in self.contentSizeObservation = self.observe(\.contentSize, options: [.old, .new]) { [weak self] _, change in
// If we don't specify `options: [.old, .new]`, the change.oldValue and .newValue will always be `nil`. // If we don't specify `options: [.old, .new]`, the change.oldValue and .newValue will always be `nil`.
if change.newValue != change.oldValue { if change.newValue != change.oldValue {
self?.invalidateIntrinsicContentSize() self?.invalidateIntrinsicContentSize()
if let height = change.newValue?.height {
self?.collectionViewHeight?.constant = height
}
} }
} }
} }

View File

@ -9,7 +9,6 @@ import Foundation
import UIKit import UIKit
import VDSColorTokens import VDSColorTokens
import VDSFormControlsTokens import VDSFormControlsTokens
import Combine
/// A button group contains combinations of related CTAs including ``Button``, ``TextLink``, and ``TextLinkCaret``. This group component controls a combination's orientation, spacing, size and allowable size pairings. /// A button group contains combinations of related CTAs including ``Button``, ``TextLink``, and ``TextLinkCaret``. This group component controls a combination's orientation, spacing, size and allowable size pairings.
@objc(VDSButtonGroup) @objc(VDSButtonGroup)
@ -63,7 +62,7 @@ open class ButtonGroup: View {
open var buttons: [ButtonBase] = [] { didSet { setNeedsUpdate() } } open var buttons: [ButtonBase] = [] { didSet { setNeedsUpdate() } }
private var _childWidth: ChildWidth? private var _childWidth: ChildWidth?
/// If provided, width of Button components will be rendered based on this value. If omitted, default button widths are rendered. /// If provided, width of Button components will be rendered based on this value. If omitted, default button widths are rendered.
open var childWidth: ChildWidth? { open var childWidth: ChildWidth? {
get { _childWidth } get { _childWidth }
@ -103,7 +102,6 @@ open class ButtonGroup: View {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Private Properties // MARK: - Private Properties
//-------------------------------------------------- //--------------------------------------------------
fileprivate var collectionViewHeight: NSLayoutConstraint?
fileprivate lazy var positionLayout = ButtonGroupPositionLayout().with { fileprivate lazy var positionLayout = ButtonGroupPositionLayout().with {
$0.position = .center $0.position = .center
@ -132,8 +130,6 @@ open class ButtonGroup: View {
super.setup() super.setup()
addSubview(collectionView) addSubview(collectionView)
collectionView.pinToSuperView() collectionView.pinToSuperView()
collectionViewHeight = heightAnchor.constraint(equalToConstant: VDS.Button.Size.large.height)
collectionViewHeight?.activate()
} }
//-------------------------------------------------- //--------------------------------------------------
@ -184,7 +180,6 @@ open class ButtonGroup: View {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self else { return } guard let self else { return }
self.collectionView.collectionViewLayout.invalidateLayout() self.collectionView.collectionViewLayout.invalidateLayout()
self.collectionViewHeight?.constant = self.collectionView.intrinsicContentSize.height
} }
} }
} }

View File

@ -8,7 +8,6 @@
import Foundation import Foundation
import UIKit import UIKit
class ButtonCollectionViewRow { class ButtonCollectionViewRow {
var attributes = [ButtonLayoutAttributes]() var attributes = [ButtonLayoutAttributes]()
@ -193,6 +192,9 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
// get the rect size of the button // get the rect size of the button
itemSize = delegate.collectionView(collectionView, sizeForItemAtIndexPath: indexPath) itemSize = delegate.collectionView(collectionView, sizeForItemAtIndexPath: indexPath)
// ensure the width is not greater than the collectionViewWidth
itemSize.width = min(itemSize.width, collectionViewWidth)
// determine if the current button will fit in the row // determine if the current button will fit in the row
let rowItemCount = rows.last?.attributes.count ?? 0 let rowItemCount = rows.last?.attributes.count ?? 0
@ -229,7 +231,7 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
// create the custom layout attribute // create the custom layout attribute
let attributes = ButtonLayoutAttributes(spacing: itemSpacing, button: itemButtonBase, forCellWith: indexPath) let attributes = ButtonLayoutAttributes(spacing: itemSpacing, button: itemButtonBase, forCellWith: indexPath)
attributes.frame = CGRect(x: 0, y: 0, width: itemSize.width, height: itemSize.height) attributes.frame = CGRect(x: 0, y: 0, width: min(itemSize.width, collectionViewWidth), height: itemSize.height)
// add it to the array // add it to the array
rows.last?.add(attribute: attributes) rows.last?.add(attribute: attributes)

View File

@ -65,21 +65,29 @@ open class Notification: View {
$0.translatesAutoresizingMaskIntoConstraints = false $0.translatesAutoresizingMaskIntoConstraints = false
$0.alignment = .top $0.alignment = .top
$0.axis = .horizontal $0.axis = .horizontal
$0.spacing = VDSLayout.Spacing.space2X.value $0.spacing = UIDevice.isIPad ? VDSLayout.Spacing.space3X.value : VDSLayout.Spacing.space2X.value
} }
private var labelsView = UIStackView().with { private var labelsView = UIStackView().with {
$0.spacing = VDSLayout.Spacing.space1X.value
$0.translatesAutoresizingMaskIntoConstraints = false $0.translatesAutoresizingMaskIntoConstraints = false
$0.alignment = .top $0.alignment = .fill
$0.distribution = .equalSpacing
$0.axis = .vertical $0.axis = .vertical
} }
private var labelButtonView = UIStackView().with { private var labelButtonView = View().with {
$0.translatesAutoresizingMaskIntoConstraints = false $0.translatesAutoresizingMaskIntoConstraints = false
$0.alignment = .top }
$0.distribution = .fillEqually
$0.axis = .vertical private var labelButtonViewSpacing: CGFloat {
$0.spacing = VDSLayout.Spacing.space2X.value let spacing: CGFloat = UIDevice.isIPad ? 20 : 16
return switch layout {
case .vertical:
0
case .horizontal:
spacing
}
} }
internal var onCloseSubscriber: AnyCancellable? internal var onCloseSubscriber: AnyCancellable?
@ -164,8 +172,8 @@ open class Notification: View {
/// Add this attribute determine your type of Notification. /// Add this attribute determine your type of Notification.
open var style: Style = .info { didSet { setNeedsUpdate()}} open var style: Style = .info { didSet { setNeedsUpdate()}}
var _layout: Layout = .vertical private var _layout: Layout = .vertical
/// Determines the orientation of buttons and text in the Notification. /// Determines the orientation of buttons and text in the Notification.
open var layout: Layout { open var layout: Layout {
@ -215,6 +223,15 @@ open class Notification: View {
return 1232 return 1232
} }
private var labelViewWidthConstraint: NSLayoutConstraint?
private var labelViewBottomConstraint: NSLayoutConstraint?
private var labelViewAndButtonViewConstraint: NSLayoutConstraint?
private var buttonViewTopConstraint: NSLayoutConstraint?
private var typeIconWidthConstraint: NSLayoutConstraint?
private var closeIconWidthConstraint: NSLayoutConstraint?
private var buttonGroupCenterYConstraint: NSLayoutConstraint?
private var buttonGroupBottomConstraint: NSLayoutConstraint?
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Overrides // MARK: - Overrides
//-------------------------------------------------- //--------------------------------------------------
@ -240,15 +257,34 @@ open class Notification: View {
]) ])
maxWidthConstraint = layoutGuide.widthAnchor.constraint(lessThanOrEqualToConstant: maxViewWidth) maxWidthConstraint = layoutGuide.widthAnchor.constraint(lessThanOrEqualToConstant: maxViewWidth)
labelButtonView.addArrangedSubview(labelsView) labelButtonView.addSubview(labelsView)
labelsView
.pinTop()
.pinLeading()
labelViewWidthConstraint = labelsView.widthAnchor.constraint(equalTo: labelButtonView.widthAnchor, multiplier: 1.0)
labelViewWidthConstraint?.activate()
labelViewBottomConstraint = labelButtonView.bottomAnchor.constraint(equalTo: labelsView.bottomAnchor)
labelButtonView.addSubview(buttonGroup)
buttonGroup
.pinTrailing()
buttonGroupBottomConstraint = labelButtonView.bottomAnchor.constraint(equalTo: buttonGroup.bottomAnchor)
buttonGroupCenterYConstraint = buttonGroup.centerYAnchor.constraint(equalTo: labelButtonView.centerYAnchor)
labelViewAndButtonViewConstraint = buttonGroup.topAnchor.constraint(equalTo: labelsView.bottomAnchor, constant: VDSLayout.Spacing.space3X.value)
buttonGroup.widthAnchor.constraint(equalTo: labelsView.widthAnchor).activate()
mainStackView.addArrangedSubview(typeIcon) mainStackView.addArrangedSubview(typeIcon)
mainStackView.addArrangedSubview(labelButtonView) mainStackView.addArrangedSubview(labelButtonView)
mainStackView.addArrangedSubview(closeButton) mainStackView.addArrangedSubview(closeButton)
typeIconWidthConstraint = typeIcon.width(constant: typeIcon.size.dimensions.width)
closeIconWidthConstraint = closeButton.width(constant: closeButton.size.dimensions.width)
//labels //labels
titleLabel.textColorConfiguration = textColorConfiguration.eraseToAnyColorable() titleLabel.textColorConfiguration = textColorConfiguration.eraseToAnyColorable()
subTitleLabel.textColorConfiguration = textColorConfiguration.eraseToAnyColorable() subTitleLabel.textColorConfiguration = textColorConfiguration.eraseToAnyColorable()
//TODO: Need to add setup animation for displaying the Notification view for iOS.
} }
/// Resets to default settings. /// Resets to default settings.
@ -296,6 +332,12 @@ open class Notification: View {
setConstraints() setConstraints()
} }
/// Override to check the screen width to determine cornerRadius
open override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = UIScreen.main.bounds.width == bounds.width ? 0 : 4.0
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Private Methods // MARK: - Private Methods
//-------------------------------------------------- //--------------------------------------------------
@ -324,7 +366,6 @@ open class Notification: View {
} else { } else {
subTitleLabel.removeFromSuperview() subTitleLabel.removeFromSuperview()
} }
} }
private func updateButtons() { private func updateButtons() {
@ -342,28 +383,28 @@ open class Notification: View {
secondaryButton.onClick = secondaryButtonModel.onClick secondaryButton.onClick = secondaryButtonModel.onClick
buttons.append(secondaryButton) buttons.append(secondaryButton)
} }
labelViewWidthConstraint?.deactivate()
if buttons.isEmpty { if buttons.isEmpty {
labelsView.setCustomSpacing(0, after: subTitleLabel) buttonGroup.isHidden = true
buttonGroup.removeFromSuperview() labelViewWidthConstraint = labelsView.widthAnchor.constraint(equalTo: labelButtonView.widthAnchor)
buttonGroup.buttons.removeAll()
} else { } else {
labelsView.setCustomSpacing(VDSLayout.Spacing.space3X.value, after: subTitleLabel) labelsView.setCustomSpacing(VDSLayout.Spacing.space3X.value, after: subTitleLabel)
buttonGroup.buttons = buttons buttonGroup.buttons = buttons
labelButtonView.axis = layout == .vertical ? .vertical : .horizontal buttonGroup.isHidden = false
labelButtonView.addArrangedSubview(buttonGroup) labelViewWidthConstraint = labelsView.widthAnchor.constraint(equalTo: labelButtonView.widthAnchor, multiplier: layout == .vertical ? 1.0 : 0.5, constant: layout == .vertical ? 0 : -labelButtonViewSpacing)
buttonGroup
.pinLeading()
.pinTrailing()
} }
labelViewWidthConstraint?.activate()
} }
private func setConstraints() { private func setConstraints() {
maxWidthConstraint?.constant = maxViewWidth maxWidthConstraint?.constant = maxViewWidth
maxWidthConstraint?.isActive = UIDevice.isIPad maxWidthConstraint?.isActive = UIDevice.isIPad
labelViewAndButtonViewConstraint?.isActive = layout == .vertical && !buttonGroup.buttons.isEmpty
typeIconWidthConstraint?.constant = typeIcon.size.dimensions.width
closeIconWidthConstraint?.constant = closeButton.size.dimensions.width
labelViewBottomConstraint?.isActive = layout == .horizontal || buttonGroup.buttons.isEmpty
buttonGroupCenterYConstraint?.isActive = layout == .horizontal
buttonGroupBottomConstraint?.isActive = layout == .vertical
} }
} }

View File

@ -0,0 +1,55 @@
MM/DD/YYYY
----------------
12/30/2021
----------------
- Updated Hover and Active state trigger specs. If triggered by mouse, Active same as Hover. If not, Active same as Default.
03/03/2022
----------------
- Initial Brand 3.0 handoff
03/07/2022
----------------
- Added Native positioning examples
05/03/2022
----------------
- Finalized Native Positioning and Triggers sections.
07/28/2022
----------------
- Added note to Anatomy that Border radius is only for Inline Notifications.
11/30/2022
----------------
- Added "(web only)" to any instance of "keyboard focus"
12/13/2022
----------------
- Replaced focus border pixel and style & spacing values with tokens.
01/12/2023
----------------
- Change VDS Button to Button in AnatomyMoved Button combinations from Anatomy to Elements.
04/12/2023
----------------
- Updated hex colors to reflect updated color tokens values.
- Updated visuals on the Native frame to reflect new guideline that the notification surface color should be based on whatever it is placed upon, or based on the color of the top navigation bar when displaying above the global nav. Added spec notation about this also.
05/04/2023
----------------
- Button Icon Threading - CloseButton
- Anatomy section updated with info on Button Icon
- CloseButton updated with VDS Button Icon details
- Updated Viewport section with Button Icon Config
- Removed Elements section. Button combination is now part of Configurations section
- Updated visuals in all sections to cater to changes caused by Button Icon threading
11/21/2023
----------------
- Added hideCloseButton property to align with React component build
- Added dev note to Error variant
- Removed close button from Error variant examples
- Removed Web/App Discrepancies list