Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tooltip to KeyValueGridView #1399

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ extension KeyValueGridDemoView: TweakableDemo {

private extension Array where Element == KeyValuePair {
static var demoData: [KeyValuePair] = [
.init(title: "Driving range", value: "409 km", infoTooltip: "WLTP is a metric from when the car was new and the actual range must be seen in context of age, km, driving pattern and weather conditions"),
.init(title: "Omregistrering", value: "1 618 kr"),
.init(title: "Pris eks omreg", value: "178 381 kr"),
.init(title: "Årsavgift", value: "Nye regler."),
Expand All @@ -98,6 +99,7 @@ private extension Array where Element == KeyValuePair {
.init(title: "Sylindervolum", value: "2,5 l"),
.init(title: "Vekt", value: "2 005 kg"),
.init(title: "CO2-utslipp", value: "254 g/km"),
.init(title: "Driving range", value: "409 km", infoTooltip: "WLTP is a metric from when the car was new and the actual range must be seen in context of age, km, driving pattern and weather conditions"),
.init(title: "Antall seter", value: "7"),
.init(title: "Karosseri", value: "Kasse"),
.init(title: "Antall dører", value: "4"),
Expand All @@ -108,6 +110,7 @@ private extension Array where Element == KeyValuePair {
.init(title: "Reg.nr", value: "DX11111"),
.init(title: "Chassis nr. (VIN)", value: "XX1234XX1X099999"),
.init(title: "Maksimal tilhengervekt", value: "2 500 kg"),
.init(title: "Driving range", value: "409 km", infoTooltip: "WLTP is a metric from when the car was new and the actual range must be seen in context of age, km, driving pattern and weather conditions"),
]

static var energyLabels: [KeyValuePair] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class KeyValueGridView: UIView {
private var titleStyle: Warp.Typography = .body
private var valueStyle: Warp.Typography = .bodyStrong
private lazy var verticalStackView = UIStackView(axis: .vertical, spacing: Warp.Spacing.spacing200, alignment: .leading, distribution: .equalSpacing, withAutoLayout: true)
private weak var activeTooltipView: UIView?
private weak var activeInfoButton: UIView?

// MARK: - Initializers

Expand Down Expand Up @@ -80,11 +82,45 @@ public class KeyValueGridView: UIView {
}

private func createCellView(for pair: KeyValuePair) -> UIView {
let stackView = UIStackView(axis: .vertical, spacing: Warp.Spacing.spacing25, alignment: .leading, distribution: .equalSpacing, withAutoLayout: true)
let stackView = UIStackView(
axis: .vertical,
spacing: Warp.Spacing.spacing25,
alignment: .leading,
distribution: .equalSpacing,
withAutoLayout: true
)

let titleLabel = Label(style: titleStyle, numberOfLines: 2, withAutoLayout: true)
titleLabel.lineBreakMode = .byWordWrapping

let titleContainer = UIStackView(
axis: .horizontal,
spacing: Warp.Spacing.spacing100,
alignment: .center,
distribution: .fill,
withAutoLayout: true
)

titleLabel.text = pair.title
titleContainer.addArrangedSubview(titleLabel)

if let infoText = pair.infoTooltip, !infoText.isEmpty {
let infoButton = UIButton(type: .custom)
infoButton.setImage(Warp.Icon.info.uiImage, for: .normal)
infoButton.translatesAutoresizingMaskIntoConstraints = false
Comment on lines +108 to +110
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no label for VoiceOver users for this button. How should the button and the presentation work for them?

infoButton.accessibilityLabel = pair.infoTooltipAccessibilityLabel
NSLayoutConstraint.activate([
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bstien Like this?

infoButton.widthAnchor.constraint(equalToConstant: Warp.Spacing.spacing200),
infoButton.heightAnchor.constraint(equalToConstant: Warp.Spacing.spacing200)
])
infoButton.addAction(UIAction(handler: { [weak self] action in
guard let view = action.sender as? UIView else { return }
self?.toggleTooltip(infoText, from: infoButton)
}), for: .touchUpInside)

titleContainer.addArrangedSubview(infoButton)
}

let valueLabel = PaddableLabel(style: valueStyle, numberOfLines: 2, withAutoLayout: true)
valueLabel.lineBreakMode = .byWordWrapping
valueLabel.setTextCopyable(true)
Expand All @@ -95,11 +131,12 @@ public class KeyValueGridView: UIView {
valueLabel.textPadding = .init(vertical: 0, horizontal: valueStyle.horizontalPadding)
}

titleLabel.text = pair.title
valueLabel.text = pair.value

stackView.addArrangedSubviews([titleLabel, valueLabel])
stackView.arrangedSubviews.forEach { $0.setContentCompressionResistancePriority(.required, for: .vertical) }
stackView.addArrangedSubviews([titleContainer, valueLabel])
stackView.arrangedSubviews.forEach {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
}

stackView.isAccessibilityElement = true
if let accessibilityLabel = pair.accessibilityLabel {
Expand All @@ -119,6 +156,49 @@ public class KeyValueGridView: UIView {
stackView.spacing = Warp.Spacing.spacing200
return stackView
}

private func toggleTooltip(_ text: String, from infoButton: UIView) {
if let activeTooltip = activeTooltipView, activeInfoButton === infoButton {
dismissTooltip()
} else {
dismissTooltip()
showTooltip(text, from: infoButton)
}
}

private func showTooltip(_ text: String, from sourceView: UIView) {
let tooltipView = Warp.Tooltip(title: text, arrowEdge: .bottom).uiView
tooltipView.translatesAutoresizingMaskIntoConstraints = false
tooltipView.isUserInteractionEnabled = true

addSubview(tooltipView)

// Convert the button’s frame to the KeyValueGridView’s coordinate system
let buttonFrameInSelf = sourceView.convert(sourceView.bounds, to: self)

NSLayoutConstraint.activate([
tooltipView.topAnchor.constraint(equalTo: self.topAnchor, constant: buttonFrameInSelf.maxY - Warp.Spacing.spacing50),
tooltipView.centerXAnchor.constraint(equalTo: self.leftAnchor, constant: buttonFrameInSelf.midX),
tooltipView.leadingAnchor.constraint(greaterThanOrEqualTo: self.leadingAnchor, constant: Warp.Spacing.spacing100),
tooltipView.trailingAnchor.constraint(lessThanOrEqualTo: self.trailingAnchor, constant: -Warp.Spacing.spacing100)
])

let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTooltipTap(_:)))
tooltipView.addGestureRecognizer(tapGesture)

activeTooltipView = tooltipView
activeInfoButton = sourceView
}

@objc private func handleTooltipTap(_ gesture: UITapGestureRecognizer) {
dismissTooltip()
}

private func dismissTooltip() {
activeTooltipView?.removeFromSuperview()
activeTooltipView = nil
activeInfoButton = nil
}
}

// MARK: - Private types
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@ public struct KeyValuePair: Hashable {
public let value: String
public let accessibilityLabel: String?
public let valueStyle: Style?
public let infoTooltip: String?
public let infoTooltipAccessibilityLabel: String?

public init(
title: String,
value: String,
accessibilityLabel: String? = nil,
valueStyle: Style? = nil
valueStyle: Style? = nil,
infoTooltip: String? = nil,
infoTooltipAccessibilityLabel: String? = nil
) {
self.title = title
self.value = value
self.accessibilityLabel = accessibilityLabel
self.valueStyle = valueStyle
self.infoTooltip = infoTooltip
self.infoTooltipAccessibilityLabel = infoTooltipAccessibilityLabel
}
}

Expand Down