完善首页权限申请流程与队列播报体验。
对齐 Android 无权限强制弹窗、角色下拉与申请页交互,补充 UIButton configuration 适配,并增强景区排队 TTS 与相关单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
590
suixinkan/UI/Home/Views/PermissionRoleViews.swift
Normal file
590
suixinkan/UI/Home/Views/PermissionRoleViews.swift
Normal file
@ -0,0 +1,590 @@
|
||||
//
|
||||
// PermissionRoleViews.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
/// “已有角色信息”中的稳定可展开条目。
|
||||
private struct PermissionExistingRoleItem: Hashable {
|
||||
let id: String
|
||||
let roleName: String
|
||||
let scenics: [ScenicInfo]
|
||||
var isExpanded: Bool
|
||||
}
|
||||
|
||||
/// Android 风格“已有角色信息”折叠列表。
|
||||
final class PermissionExistingRoleListView: UIView, UITableViewDelegate {
|
||||
|
||||
private enum Section { case main }
|
||||
|
||||
var onShowAllScenics: ((String, [ScenicInfo]) -> Void)?
|
||||
|
||||
private let titleLabel = UILabel()
|
||||
private let tableView = UITableView(frame: .zero, style: .plain)
|
||||
private var dataSource: UITableViewDiffableDataSource<Section, String>!
|
||||
private var items: [PermissionExistingRoleItem] = []
|
||||
private var tableHeightConstraint: Constraint?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = PermissionApplyUITokens.existingBackground
|
||||
layer.cornerRadius = 12
|
||||
titleLabel.text = "已有角色信息"
|
||||
titleLabel.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
titleLabel.textColor = PermissionApplyUITokens.text
|
||||
|
||||
tableView.backgroundColor = .clear
|
||||
tableView.separatorStyle = .none
|
||||
tableView.isScrollEnabled = false
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.estimatedRowHeight = 52
|
||||
tableView.delegate = self
|
||||
tableView.register(PermissionExistingRoleCell.self, forCellReuseIdentifier: PermissionExistingRoleCell.reuseIdentifier)
|
||||
|
||||
addSubview(titleLabel)
|
||||
addSubview(tableView)
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.top.leading.trailing.equalToSuperview().inset(12)
|
||||
}
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.top.equalTo(titleLabel.snp.bottom).offset(12)
|
||||
make.leading.trailing.bottom.equalToSuperview().inset(12)
|
||||
tableHeightConstraint = make.height.equalTo(1).constraint
|
||||
}
|
||||
|
||||
dataSource = UITableViewDiffableDataSource<Section, String>(tableView: tableView) {
|
||||
[weak self] tableView, indexPath, itemID in
|
||||
guard let self,
|
||||
let item = self.items.first(where: { $0.id == itemID }),
|
||||
let cell = tableView.dequeueReusableCell(
|
||||
withIdentifier: PermissionExistingRoleCell.reuseIdentifier,
|
||||
for: indexPath
|
||||
) as? PermissionExistingRoleCell else { return UITableViewCell() }
|
||||
cell.apply(item: item)
|
||||
cell.onToggle = { [weak self] in self?.toggle(itemID: item.id) }
|
||||
cell.onShowAll = { [weak self] in self?.onShowAllScenics?(item.roleName, item.scenics) }
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
/// 使用本地权限缓存构建已有角色列表。
|
||||
func apply(rolePermissions: [RolePermissionResponse]) {
|
||||
let expandedIDs = Set(items.filter(\.isExpanded).map(\.id))
|
||||
items = rolePermissions.enumerated().map { index, value in
|
||||
let id = "\(index)-\(value.role.id)-\(value.role.name)"
|
||||
return PermissionExistingRoleItem(
|
||||
id: id,
|
||||
roleName: value.role.name,
|
||||
scenics: value.scenic,
|
||||
isExpanded: expandedIDs.contains(id)
|
||||
)
|
||||
}
|
||||
isHidden = items.isEmpty
|
||||
applySnapshot(animatingExpansion: false, reconfigureExisting: true)
|
||||
}
|
||||
|
||||
private func toggle(itemID: String) {
|
||||
guard let index = items.firstIndex(where: { $0.id == itemID }) else { return }
|
||||
items[index].isExpanded.toggle()
|
||||
if let indexPath = dataSource.indexPath(for: itemID),
|
||||
let cell = tableView.cellForRow(at: indexPath) as? PermissionExistingRoleCell {
|
||||
cell.apply(item: items[index], animated: true)
|
||||
}
|
||||
applySnapshot(animatingExpansion: true, reconfigureExisting: false)
|
||||
}
|
||||
|
||||
private func applySnapshot(animatingExpansion: Bool, reconfigureExisting: Bool) {
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
|
||||
snapshot.appendSections([.main])
|
||||
let itemIDs = items.map(\.id)
|
||||
snapshot.appendItems(itemIDs)
|
||||
if reconfigureExisting {
|
||||
let currentIDs = Set(dataSource.snapshot().itemIdentifiers)
|
||||
snapshot.reconfigureItems(itemIDs.filter(currentIDs.contains))
|
||||
}
|
||||
dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in
|
||||
self?.updateHeight(animated: animatingExpansion)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateHeight(animated: Bool) {
|
||||
let layoutRoot = superview ?? self
|
||||
layoutRoot.layoutIfNeeded()
|
||||
let updates = { [self] in
|
||||
tableView.beginUpdates()
|
||||
tableView.endUpdates()
|
||||
tableView.layoutIfNeeded()
|
||||
tableHeightConstraint?.update(offset: max(1, tableView.contentSize.height))
|
||||
invalidateIntrinsicContentSize()
|
||||
layoutRoot.layoutIfNeeded()
|
||||
}
|
||||
guard animated, !UIAccessibility.isReduceMotionEnabled else {
|
||||
UIView.performWithoutAnimation(updates)
|
||||
return
|
||||
}
|
||||
UIView.animate(
|
||||
withDuration: 0.24,
|
||||
delay: 0,
|
||||
options: [.curveEaseOut, .beginFromCurrentState, .allowUserInteraction],
|
||||
animations: updates
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 已有角色折叠列表单元格。
|
||||
private final class PermissionExistingRoleCell: UITableViewCell {
|
||||
|
||||
static let reuseIdentifier = "PermissionExistingRoleCell"
|
||||
|
||||
var onToggle: (() -> Void)?
|
||||
var onShowAll: (() -> Void)?
|
||||
|
||||
private let cardView = UIView()
|
||||
private let headerControl = UIControl()
|
||||
private let titleLabel = UILabel()
|
||||
private let chevron = UIImageView()
|
||||
private let expandedContainer = UIView()
|
||||
private let flowView = PermissionSimpleFlowView()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
backgroundColor = .clear
|
||||
selectionStyle = .none
|
||||
contentView.backgroundColor = .clear
|
||||
cardView.backgroundColor = .white
|
||||
cardView.layer.cornerRadius = 8
|
||||
cardView.layer.shadowColor = UIColor.black.cgColor
|
||||
cardView.layer.shadowOpacity = 0.08
|
||||
cardView.layer.shadowRadius = 1
|
||||
cardView.layer.shadowOffset = CGSize(width: 0, height: 1)
|
||||
|
||||
titleLabel.font = .systemFont(ofSize: 14, weight: .medium)
|
||||
titleLabel.textColor = PermissionApplyUITokens.text
|
||||
chevron.image = UIImage(systemName: "chevron.down")
|
||||
chevron.tintColor = AppColor.textTertiary
|
||||
chevron.contentMode = .scaleAspectFit
|
||||
headerControl.addTarget(self, action: #selector(toggleTapped), for: .touchUpInside)
|
||||
expandedContainer.backgroundColor = PermissionApplyUITokens.existingBackground
|
||||
flowView.onMore = { [weak self] in self?.onShowAll?() }
|
||||
|
||||
contentView.addSubview(cardView)
|
||||
cardView.addSubview(headerControl)
|
||||
headerControl.addSubview(titleLabel)
|
||||
headerControl.addSubview(chevron)
|
||||
cardView.addSubview(expandedContainer)
|
||||
expandedContainer.addSubview(flowView)
|
||||
|
||||
cardView.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview()
|
||||
make.leading.trailing.equalToSuperview()
|
||||
make.bottom.equalToSuperview().offset(-8)
|
||||
}
|
||||
headerControl.snp.makeConstraints { make in
|
||||
make.top.leading.trailing.equalToSuperview()
|
||||
make.height.equalTo(44)
|
||||
}
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.leading.equalToSuperview().offset(16)
|
||||
make.centerY.equalToSuperview()
|
||||
make.trailing.lessThanOrEqualTo(chevron.snp.leading).offset(-8)
|
||||
}
|
||||
chevron.snp.makeConstraints { make in
|
||||
make.trailing.equalToSuperview().offset(-16)
|
||||
make.centerY.equalToSuperview()
|
||||
make.size.equalTo(20)
|
||||
}
|
||||
expandedContainer.snp.makeConstraints { make in
|
||||
make.top.equalTo(headerControl.snp.bottom)
|
||||
make.leading.trailing.bottom.equalToSuperview()
|
||||
}
|
||||
flowView.snp.makeConstraints { make in make.edges.equalToSuperview().inset(12) }
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func apply(item: PermissionExistingRoleItem, animated: Bool = false) {
|
||||
titleLabel.text = item.roleName
|
||||
headerControl.accessibilityLabel = item.roleName
|
||||
headerControl.accessibilityValue = item.isExpanded ? "已展开" : "已收起"
|
||||
flowView.apply(titles: item.scenics.map(\.name), showsMore: item.scenics.count > 3)
|
||||
let showsExpandedContent = item.isExpanded && !item.scenics.isEmpty
|
||||
let isExpanding = expandedContainer.isHidden && showsExpandedContent
|
||||
expandedContainer.isHidden = !showsExpandedContent
|
||||
|
||||
let updates = { [self] in
|
||||
chevron.transform = item.isExpanded
|
||||
? CGAffineTransform(rotationAngle: .pi)
|
||||
: .identity
|
||||
expandedContainer.alpha = 1
|
||||
expandedContainer.transform = .identity
|
||||
}
|
||||
guard animated, !UIAccessibility.isReduceMotionEnabled else {
|
||||
UIView.performWithoutAnimation(updates)
|
||||
return
|
||||
}
|
||||
if isExpanding {
|
||||
expandedContainer.alpha = 0
|
||||
expandedContainer.transform = CGAffineTransform(translationX: 0, y: -8)
|
||||
}
|
||||
UIView.animate(
|
||||
withDuration: 0.24,
|
||||
delay: 0,
|
||||
options: [.curveEaseOut, .beginFromCurrentState, .allowUserInteraction],
|
||||
animations: updates
|
||||
)
|
||||
}
|
||||
|
||||
@objc private func toggleTapped() {
|
||||
onToggle?()
|
||||
}
|
||||
}
|
||||
|
||||
/// 小规模绿色景区标签的手动换行容器。
|
||||
private final class PermissionSimpleFlowView: UIView {
|
||||
|
||||
var onMore: (() -> Void)?
|
||||
|
||||
private var itemViews: [UIView] = []
|
||||
private var lastLayoutWidth: CGFloat = 0
|
||||
|
||||
func apply(titles: [String], showsMore: Bool) {
|
||||
itemViews.forEach { $0.removeFromSuperview() }
|
||||
itemViews = []
|
||||
var values = Array(titles.prefix(3))
|
||||
if showsMore { values.append("···") }
|
||||
for value in values {
|
||||
let button = UIButton(type: .system)
|
||||
button.setTitle(value, for: .normal)
|
||||
button.setTitleColor(PermissionApplyUITokens.existingGreen, for: .normal)
|
||||
button.titleLabel?.font = .systemFont(ofSize: 14)
|
||||
button.setConfigurationContentInsets(
|
||||
NSDirectionalEdgeInsets(top: 8, leading: 12, bottom: 8, trailing: 12)
|
||||
)
|
||||
button.backgroundColor = PermissionApplyUITokens.existingGreenBackground
|
||||
button.layer.cornerRadius = 4
|
||||
button.layer.borderWidth = 1
|
||||
button.layer.borderColor = PermissionApplyUITokens.existingGreen.cgColor
|
||||
button.isUserInteractionEnabled = value == "···"
|
||||
if value == "···" {
|
||||
button.addTarget(self, action: #selector(moreTapped), for: .touchUpInside)
|
||||
button.accessibilityLabel = "查看全部景区"
|
||||
}
|
||||
addSubview(button)
|
||||
itemViews.append(button)
|
||||
}
|
||||
setNeedsLayout()
|
||||
invalidateIntrinsicContentSize()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
guard bounds.width > 0 else { return }
|
||||
let widthChanged = abs(bounds.width - lastLayoutWidth) > 0.5
|
||||
lastLayoutWidth = bounds.width
|
||||
var x: CGFloat = 0
|
||||
var y: CGFloat = 0
|
||||
var rowHeight: CGFloat = 0
|
||||
for view in itemViews {
|
||||
let fitted = view.sizeThatFits(CGSize(width: min(180, bounds.width), height: 34))
|
||||
let width = min(bounds.width, max(42, fitted.width))
|
||||
let height = max(34, fitted.height)
|
||||
if x > 0, x + width > bounds.width {
|
||||
x = 0
|
||||
y += rowHeight + 12
|
||||
rowHeight = 0
|
||||
}
|
||||
view.frame = CGRect(x: x, y: y, width: width, height: height)
|
||||
x += width + 12
|
||||
rowHeight = max(rowHeight, height)
|
||||
}
|
||||
if widthChanged { invalidateIntrinsicContentSize() }
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
let width = bounds.width > 0 ? bounds.width : 280
|
||||
var x: CGFloat = 0
|
||||
var totalHeight: CGFloat = 0
|
||||
var rowHeight: CGFloat = 0
|
||||
for view in itemViews {
|
||||
let fitted = view.sizeThatFits(CGSize(width: min(180, width), height: 34))
|
||||
let itemWidth = min(width, max(42, fitted.width))
|
||||
let itemHeight = max(34, fitted.height)
|
||||
if x > 0, x + itemWidth > width {
|
||||
totalHeight += rowHeight + 12
|
||||
x = 0
|
||||
rowHeight = 0
|
||||
}
|
||||
x += itemWidth + 12
|
||||
rowHeight = max(rowHeight, itemHeight)
|
||||
}
|
||||
return CGSize(width: UIView.noIntrinsicMetric, height: itemViews.isEmpty ? 0 : totalHeight + rowHeight)
|
||||
}
|
||||
|
||||
@objc private func moreTapped() {
|
||||
onMore?()
|
||||
}
|
||||
}
|
||||
|
||||
/// 审核页“新增角色/新增景区”白色卡片。
|
||||
final class PermissionStaticSectionView: UIView {
|
||||
|
||||
private let titleLabel = UILabel()
|
||||
private let chips = PermissionChipCollectionView(style: .selectedStatic)
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .white
|
||||
layer.cornerRadius = 12
|
||||
titleLabel.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
titleLabel.textColor = PermissionApplyUITokens.text
|
||||
addSubview(titleLabel)
|
||||
addSubview(chips)
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.top.leading.trailing.equalToSuperview().inset(16)
|
||||
}
|
||||
chips.snp.makeConstraints { make in
|
||||
make.top.equalTo(titleLabel.snp.bottom).offset(12)
|
||||
make.leading.trailing.bottom.equalToSuperview().inset(16)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
/// 更新标题和不可删除的蓝色标签。
|
||||
func apply(title: String, names: [String]) {
|
||||
titleLabel.text = title
|
||||
chips.apply(titles: names)
|
||||
}
|
||||
}
|
||||
|
||||
/// Android 风格角色或景区删除确认弹窗。
|
||||
final class PermissionDeleteDialogViewController: UIViewController {
|
||||
|
||||
private let target: PermissionDeletionTarget
|
||||
private let onCancel: () -> Void
|
||||
private let onConfirm: () -> Void
|
||||
|
||||
/// 创建删除确认弹窗。
|
||||
init(target: PermissionDeletionTarget, onCancel: @escaping () -> Void, onConfirm: @escaping () -> Void) {
|
||||
self.target = target
|
||||
self.onCancel = onCancel
|
||||
self.onConfirm = onConfirm
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
modalPresentationStyle = .overFullScreen
|
||||
modalTransitionStyle = .crossDissolve
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = UIColor.black.withAlphaComponent(0.32)
|
||||
let card = UIView()
|
||||
card.backgroundColor = .white
|
||||
card.layer.cornerRadius = 16
|
||||
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.text = target.dialogTitle
|
||||
titleLabel.font = .systemFont(ofSize: 18, weight: .bold)
|
||||
titleLabel.textColor = UIColor(hex: 0xFF0000)
|
||||
|
||||
let messageLabel = UILabel()
|
||||
messageLabel.numberOfLines = 0
|
||||
let message = NSMutableAttributedString(
|
||||
string: target.dialogMessage,
|
||||
attributes: [.font: UIFont.systemFont(ofSize: 16), .foregroundColor: UIColor(hex: 0x333333)]
|
||||
)
|
||||
let name: String
|
||||
switch target {
|
||||
case .role(let value), .scenic(_, let value): name = value
|
||||
}
|
||||
let range = (target.dialogMessage as NSString).range(of: name)
|
||||
if range.location != NSNotFound {
|
||||
message.addAttribute(.font, value: UIFont.systemFont(ofSize: 16, weight: .bold), range: range)
|
||||
}
|
||||
messageLabel.attributedText = message
|
||||
|
||||
let cancel = makeButton(title: "取消", background: UIColor(hex: 0xFFE5E5))
|
||||
let confirm = makeButton(title: "确认", background: UIColor(hex: 0xFF0000))
|
||||
cancel.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
|
||||
confirm.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
|
||||
let buttons = UIStackView(arrangedSubviews: [cancel, confirm])
|
||||
buttons.axis = .horizontal
|
||||
buttons.spacing = 12
|
||||
buttons.distribution = .fillEqually
|
||||
buttons.snp.makeConstraints { make in make.height.equalTo(44) }
|
||||
|
||||
let stack = UIStackView(arrangedSubviews: [titleLabel, messageLabel, buttons])
|
||||
stack.axis = .vertical
|
||||
stack.spacing = 16
|
||||
stack.setCustomSpacing(24, after: messageLabel)
|
||||
view.addSubview(card)
|
||||
card.addSubview(stack)
|
||||
card.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
make.leading.trailing.equalToSuperview().inset(32)
|
||||
}
|
||||
stack.snp.makeConstraints { make in make.edges.equalToSuperview().inset(24) }
|
||||
}
|
||||
|
||||
private func makeButton(title: String, background: UIColor) -> UIButton {
|
||||
let button = UIButton(type: .system)
|
||||
button.setTitle(title, for: .normal)
|
||||
button.setTitleColor(.white, for: .normal)
|
||||
button.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||||
button.backgroundColor = background
|
||||
button.layer.cornerRadius = 8
|
||||
button.accessibilityLabel = title
|
||||
return button
|
||||
}
|
||||
|
||||
@objc private func cancelTapped() {
|
||||
dismiss(animated: true) { [onCancel] in onCancel() }
|
||||
}
|
||||
|
||||
@objc private func confirmTapped() {
|
||||
dismiss(animated: true) { [onConfirm] in onConfirm() }
|
||||
}
|
||||
}
|
||||
|
||||
/// 某个已有角色的全部景区弹窗。
|
||||
final class PermissionScenicListDialogViewController: UIViewController {
|
||||
|
||||
private enum Section { case main }
|
||||
|
||||
private let roleName: String
|
||||
private let scenics: [ScenicInfo]
|
||||
private let onDismiss: () -> Void
|
||||
private let tableView = UITableView(frame: .zero, style: .plain)
|
||||
private var dataSource: UITableViewDiffableDataSource<Section, ScenicInfo>!
|
||||
|
||||
/// 创建固定400pt列表高度的景区详情弹窗。
|
||||
init(roleName: String, scenics: [ScenicInfo], onDismiss: @escaping () -> Void) {
|
||||
self.roleName = roleName
|
||||
self.scenics = scenics
|
||||
self.onDismiss = onDismiss
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
modalPresentationStyle = .overFullScreen
|
||||
modalTransitionStyle = .crossDissolve
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = UIColor.black.withAlphaComponent(0.32)
|
||||
let card = UIView()
|
||||
card.backgroundColor = .white
|
||||
card.layer.cornerRadius = 16
|
||||
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.text = roleName
|
||||
titleLabel.font = .systemFont(ofSize: 18, weight: .bold)
|
||||
titleLabel.textColor = UIColor(hex: 0x333333)
|
||||
let closeButton = UIButton(type: .system)
|
||||
closeButton.setImage(UIImage(systemName: "xmark"), for: .normal)
|
||||
closeButton.tintColor = AppColor.textSecondary
|
||||
closeButton.accessibilityLabel = "关闭"
|
||||
closeButton.addTarget(self, action: #selector(closeTapped), for: .touchUpInside)
|
||||
|
||||
tableView.separatorStyle = .none
|
||||
tableView.backgroundColor = .white
|
||||
tableView.rowHeight = 50
|
||||
tableView.register(PermissionScenicDialogCell.self, forCellReuseIdentifier: PermissionScenicDialogCell.reuseIdentifier)
|
||||
dataSource = UITableViewDiffableDataSource<Section, ScenicInfo>(tableView: tableView) {
|
||||
tableView, indexPath, scenic in
|
||||
guard let cell = tableView.dequeueReusableCell(
|
||||
withIdentifier: PermissionScenicDialogCell.reuseIdentifier,
|
||||
for: indexPath
|
||||
) as? PermissionScenicDialogCell else { return UITableViewCell() }
|
||||
cell.apply(name: scenic.name)
|
||||
return cell
|
||||
}
|
||||
|
||||
view.addSubview(card)
|
||||
[titleLabel, closeButton, tableView].forEach(card.addSubview)
|
||||
card.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
make.leading.trailing.equalToSuperview().inset(32)
|
||||
}
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.top.leading.equalToSuperview().inset(24)
|
||||
make.trailing.lessThanOrEqualTo(closeButton.snp.leading).offset(-8)
|
||||
make.centerY.equalTo(closeButton)
|
||||
}
|
||||
closeButton.snp.makeConstraints { make in
|
||||
make.top.trailing.equalToSuperview().inset(16)
|
||||
make.size.equalTo(44)
|
||||
}
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.top.equalTo(closeButton.snp.bottom).offset(8)
|
||||
make.leading.trailing.bottom.equalToSuperview().inset(24)
|
||||
make.height.equalTo(400)
|
||||
}
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, ScenicInfo>()
|
||||
snapshot.appendSections([.main])
|
||||
snapshot.appendItems(scenics)
|
||||
dataSource.apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
|
||||
@objc private func closeTapped() {
|
||||
dismiss(animated: true) { [onDismiss] in onDismiss() }
|
||||
}
|
||||
}
|
||||
|
||||
/// 景区详情弹窗中的绿色标签行。
|
||||
private final class PermissionScenicDialogCell: UITableViewCell {
|
||||
|
||||
static let reuseIdentifier = "PermissionScenicDialogCell"
|
||||
private let chipLabel = UILabel()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
selectionStyle = .none
|
||||
chipLabel.font = .systemFont(ofSize: 14)
|
||||
chipLabel.textColor = PermissionApplyUITokens.existingGreen
|
||||
chipLabel.backgroundColor = PermissionApplyUITokens.existingGreenBackground
|
||||
chipLabel.layer.cornerRadius = 4
|
||||
chipLabel.layer.borderWidth = 1
|
||||
chipLabel.layer.borderColor = PermissionApplyUITokens.existingGreen.cgColor
|
||||
chipLabel.clipsToBounds = true
|
||||
chipLabel.textAlignment = .center
|
||||
contentView.addSubview(chipLabel)
|
||||
chipLabel.snp.makeConstraints { make in
|
||||
make.leading.equalToSuperview()
|
||||
make.centerY.equalToSuperview()
|
||||
make.height.equalTo(34)
|
||||
make.trailing.lessThanOrEqualToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func apply(name: String) {
|
||||
chipLabel.text = " \(name) "
|
||||
chipLabel.accessibilityLabel = name
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user