Files
suixinkan_uikit/suixinkan/UI/Setting/AccountDeletionViewControllers.swift

762 lines
27 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// AccountDeletionViewControllers.swift
// suixinkan
//
import SnapKit
import UIKit
/// 注销账号资产核验页,中性展示账号资产与注销后果。
final class AccountDeletionViewController: BaseViewController {
private let viewModel: AccountDeletionViewModel
private let scrollView = UIScrollView()
private let contentView = UIView()
private let contentStack = UIStackView()
private let warningView = AccountDeletionWarningView()
private let assetCardView = AccountDeletionAssetCardView()
private let detailButton = UIButton(type: .system)
private let consequenceCardView = AccountDeletionConsequenceCardView()
private let bottomPanel = UIView()
private let acknowledgementButton = UIButton(type: .system)
private let continueButton = AppButton(title: "继续注销", style: .primary)
private var lastErrorMessage: String?
/// 创建注销资产核验页。
init(viewModel: AccountDeletionViewModel = AccountDeletionViewModel()) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "注销账号"
}
override func setupUI() {
view.backgroundColor = AppColor.pageBackground
scrollView.alwaysBounceVertical = true
scrollView.showsVerticalScrollIndicator = false
contentStack.axis = .vertical
contentStack.spacing = 16
contentStack.isLayoutMarginsRelativeArrangement = true
contentStack.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
detailButton.setTitle("查看资产明细", for: .normal)
detailButton.setTitleColor(AppColor.primary, for: .normal)
detailButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
bottomPanel.backgroundColor = AppColor.pageBackground
acknowledgementButton.contentHorizontalAlignment = .leading
acknowledgementButton.tintColor = AppColor.primary
acknowledgementButton.setTitleColor(AppColor.textPrimary, for: .normal)
acknowledgementButton.titleLabel?.font = .systemFont(ofSize: 14)
acknowledgementButton.titleLabel?.numberOfLines = 0
acknowledgementButton.setTitle(" 我已知晓并自愿放弃以上资产和权益", for: .normal)
acknowledgementButton.accessibilityLabel = "我已知晓并自愿放弃以上资产和权益"
view.addSubview(scrollView)
scrollView.addSubview(contentView)
contentView.addSubview(contentStack)
view.addSubview(bottomPanel)
bottomPanel.addSubview(acknowledgementButton)
bottomPanel.addSubview(continueButton)
[warningView, assetCardView, detailButton, consequenceCardView].forEach(contentStack.addArrangedSubview)
applyViewModel()
}
override func setupConstraints() {
bottomPanel.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
}
acknowledgementButton.snp.makeConstraints { make in
make.top.equalToSuperview().offset(8)
make.leading.trailing.equalToSuperview().inset(16)
make.height.greaterThanOrEqualTo(44)
}
continueButton.snp.makeConstraints { make in
make.top.equalTo(acknowledgementButton.snp.bottom).offset(8)
make.leading.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
}
scrollView.snp.makeConstraints { make in
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
make.bottom.equalTo(bottomPanel.snp.top)
}
contentView.snp.makeConstraints { make in
make.edges.equalToSuperview()
make.width.equalTo(scrollView.snp.width)
}
contentStack.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
warningView.snp.makeConstraints { make in
make.height.greaterThanOrEqualTo(64)
}
detailButton.snp.makeConstraints { make in
make.height.equalTo(36)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
acknowledgementButton.addTarget(self, action: #selector(acknowledgementTapped), for: .touchUpInside)
continueButton.addTarget(self, action: #selector(continueTapped), for: .touchUpInside)
detailButton.addTarget(self, action: #selector(detailTapped), for: .touchUpInside)
}
override func viewDidLoad() {
super.viewDidLoad()
Task {
showLoading()
await viewModel.load()
hideLoading()
}
}
private func applyViewModel() {
assetCardView.configure(assets: viewModel.assets)
consequenceCardView.configure(items: viewModel.consequences)
acknowledgementButton.setImage(
UIImage(systemName: viewModel.isAcknowledged ? "checkmark.square.fill" : "square"),
for: .normal
)
acknowledgementButton.accessibilityValue = viewModel.isAcknowledged ? "已勾选" : "未勾选"
continueButton.isEnabled = viewModel.isContinueButtonEnabled
if let errorMessage = viewModel.errorMessage,
!errorMessage.isEmpty,
errorMessage != lastErrorMessage {
lastErrorMessage = errorMessage
showToast(errorMessage)
}
}
@objc private func acknowledgementTapped() {
viewModel.toggleAcknowledgement()
}
@objc private func continueTapped() {
do {
let verificationViewModel = try viewModel.makeVerificationViewModel()
navigationController?.pushViewController(
AccountDeletionVerificationViewController(viewModel: verificationViewModel),
animated: true
)
} catch {
showToast(error.localizedDescription)
}
}
@objc private func detailTapped() {
let message = viewModel.assets
.map { "\($0.title):\($0.valueText)" }
.joined(separator: "\n")
showAlert(title: "当前账号资产", message: message)
}
}
/// 注销手机号验证页,使用短信验证码确认是账号本人操作。
final class AccountDeletionVerificationViewController: BaseViewController {
private let viewModel: AccountDeletionVerificationViewModel
private let contentStack = UIStackView()
private let iconView = UIImageView()
private let titleLabel = UILabel()
private let descriptionLabel = UILabel()
private let codeContainer = UIView()
private let codeField = UITextField()
private let resendButton = UIButton(type: .system)
private let demoHintLabel = UILabel()
private let noticeView = AccountDeletionNoticeView()
private let submitButton = AppButton(title: "提交注销申请", style: .primary)
private var lastErrorMessage: String?
/// 创建注销手机号验证页。
init(viewModel: AccountDeletionVerificationViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "验证手机号"
}
override func setupUI() {
view.backgroundColor = AppColor.pageBackground
contentStack.axis = .vertical
contentStack.alignment = .fill
contentStack.spacing = 16
iconView.image = UIImage(systemName: "message.badge.filled.fill")
iconView.tintColor = AppColor.primary
iconView.contentMode = .scaleAspectFit
titleLabel.text = "确认是你本人操作"
titleLabel.font = .systemFont(ofSize: 22, weight: .semibold)
titleLabel.textColor = AppColor.textPrimary
titleLabel.textAlignment = .center
descriptionLabel.text = "验证码将发送至 \(viewModel.context.maskedPhone)"
descriptionLabel.font = .systemFont(ofSize: 15)
descriptionLabel.textColor = AppColor.textSecondary
descriptionLabel.textAlignment = .center
codeContainer.backgroundColor = .white
codeContainer.layer.cornerRadius = 12
codeContainer.layer.borderWidth = 1
codeContainer.layer.borderColor = AppColor.border.cgColor
codeField.placeholder = "请输入6位验证码"
codeField.font = .systemFont(ofSize: 18, weight: .medium)
codeField.textColor = AppColor.textPrimary
codeField.keyboardType = .numberPad
codeField.textContentType = .oneTimeCode
codeField.clearButtonMode = .whileEditing
resendButton.setTitle("重新发送", for: .normal)
resendButton.setTitleColor(AppColor.primary, for: .normal)
resendButton.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
demoHintLabel.text = "演示验证码:123456"
demoHintLabel.font = .systemFont(ofSize: 13)
demoHintLabel.textColor = AppColor.warning
demoHintLabel.textAlignment = .center
noticeView.configure(
iconName: "calendar.badge.exclamationmark",
text: "提交成功后账号将退出,7天内再次登录可取消注销。"
)
view.addSubview(contentStack)
view.addSubview(submitButton)
codeContainer.addSubview(codeField)
codeContainer.addSubview(resendButton)
[iconView, titleLabel, descriptionLabel, codeContainer, demoHintLabel, noticeView]
.forEach(contentStack.addArrangedSubview)
applyViewModel()
}
override func setupConstraints() {
contentStack.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(32)
make.leading.trailing.equalToSuperview().inset(24)
}
iconView.snp.makeConstraints { make in
make.height.equalTo(64)
}
codeContainer.snp.makeConstraints { make in
make.height.equalTo(56)
}
codeField.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.top.bottom.equalToSuperview()
make.trailing.equalTo(resendButton.snp.leading).offset(-8)
}
resendButton.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-12)
make.centerY.equalToSuperview()
make.width.equalTo(76)
make.height.equalTo(44)
}
noticeView.snp.makeConstraints { make in
make.height.greaterThanOrEqualTo(64)
}
submitButton.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
codeField.addTarget(self, action: #selector(codeChanged), for: .editingChanged)
resendButton.addTarget(self, action: #selector(resendTapped), for: .touchUpInside)
submitButton.addTarget(self, action: #selector(submitTapped), for: .touchUpInside)
}
override func viewDidLoad() {
super.viewDidLoad()
Task {
await viewModel.sendVerificationCode()
if viewModel.hasSentCode {
showToast("验证码已发送")
}
}
}
private func applyViewModel() {
if codeField.text != viewModel.verificationCode {
codeField.text = viewModel.verificationCode
}
resendButton.isEnabled = !viewModel.isSendingCode && !viewModel.isSubmitting
resendButton.setTitle(viewModel.isSendingCode ? "发送中" : "重新发送", for: .normal)
submitButton.isEnabled = viewModel.isSubmitEnabled
if let errorMessage = viewModel.errorMessage,
!errorMessage.isEmpty,
errorMessage != lastErrorMessage {
lastErrorMessage = errorMessage
showToast(errorMessage)
}
}
@objc private func codeChanged() {
viewModel.updateVerificationCode(codeField.text ?? "")
}
@objc private func resendTapped() {
Task {
await viewModel.sendVerificationCode()
if viewModel.hasSentCode {
showToast("验证码已重新发送")
}
}
}
@objc private func submitTapped() {
let alert = UIAlertController(
title: "确认提交注销申请?",
message: "提交后账号将退出,7天内再次登录可以取消注销。",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "再想想", style: .cancel))
alert.addAction(UIAlertAction(title: "确认提交", style: .destructive) { [weak self] _ in
self?.submitDeletion()
})
present(alert, animated: true)
}
private func submitDeletion() {
Task {
showLoading()
defer { hideLoading() }
do {
let request = try await viewModel.submit()
navigationController?.pushViewController(
AccountDeletionSuccessViewController(request: request),
animated: true
)
} catch is CancellationError {
return
} catch {
showToast(error.localizedDescription)
}
}
}
}
/// 注销申请提交成功页,明确展示计划删除时间和恢复方式。
final class AccountDeletionSuccessViewController: BaseViewController {
private let request: AccountDeletionRequest
private let iconView = UIImageView()
private let titleLabel = UILabel()
private let descriptionLabel = UILabel()
private let dateCardView = UIView()
private let dateTitleLabel = UILabel()
private let dateValueLabel = UILabel()
private let recoveryLabel = UILabel()
private let exitButton = AppButton(title: "退出并返回登录页", style: .primary)
/// 创建注销申请提交成功页。
init(request: AccountDeletionRequest) {
self.request = request
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "注销账号"
navigationItem.hidesBackButton = true
}
override func setupUI() {
view.backgroundColor = AppColor.pageBackground
iconView.image = UIImage(systemName: "checkmark.circle.fill")
iconView.tintColor = AppColor.success
iconView.contentMode = .scaleAspectFit
titleLabel.text = "注销申请已提交"
titleLabel.font = .systemFont(ofSize: 24, weight: .semibold)
titleLabel.textColor = AppColor.textPrimary
titleLabel.textAlignment = .center
descriptionLabel.text = "账号已进入7天注销冷静期"
descriptionLabel.font = .systemFont(ofSize: 15)
descriptionLabel.textColor = AppColor.textSecondary
descriptionLabel.textAlignment = .center
dateCardView.backgroundColor = .white
dateCardView.layer.cornerRadius = 12
dateTitleLabel.text = "计划永久注销时间"
dateTitleLabel.font = .systemFont(ofSize: 14)
dateTitleLabel.textColor = AppColor.textSecondary
dateValueLabel.text = AccountDeletionDateFormatter.displayText(request.scheduledDeletionAt)
dateValueLabel.font = .systemFont(ofSize: 20, weight: .semibold)
dateValueLabel.textColor = AppColor.danger
dateValueLabel.textAlignment = .right
recoveryLabel.text = "在上述时间前再次使用当前手机号和密码登录,确认恢复账号后即可取消注销。"
recoveryLabel.font = .systemFont(ofSize: 15)
recoveryLabel.textColor = AppColor.textSecondary
recoveryLabel.numberOfLines = 0
recoveryLabel.textAlignment = .center
[iconView, titleLabel, descriptionLabel, dateCardView, recoveryLabel, exitButton].forEach(view.addSubview)
dateCardView.addSubview(dateTitleLabel)
dateCardView.addSubview(dateValueLabel)
}
override func setupConstraints() {
iconView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(56)
make.centerX.equalToSuperview()
make.width.height.equalTo(72)
}
titleLabel.snp.makeConstraints { make in
make.top.equalTo(iconView.snp.bottom).offset(24)
make.leading.trailing.equalToSuperview().inset(24)
}
descriptionLabel.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(10)
make.leading.trailing.equalToSuperview().inset(24)
}
dateCardView.snp.makeConstraints { make in
make.top.equalTo(descriptionLabel.snp.bottom).offset(32)
make.leading.trailing.equalToSuperview().inset(16)
make.height.equalTo(72)
}
dateTitleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.centerY.equalToSuperview()
}
dateValueLabel.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-16)
make.centerY.equalToSuperview()
make.leading.greaterThanOrEqualTo(dateTitleLabel.snp.trailing).offset(12)
}
recoveryLabel.snp.makeConstraints { make in
make.top.equalTo(dateCardView.snp.bottom).offset(24)
make.leading.trailing.equalToSuperview().inset(32)
}
exitButton.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
}
}
override func bindActions() {
exitButton.addTarget(self, action: #selector(exitTapped), for: .touchUpInside)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
@objc private func exitTapped() {
NotificationCenter.default.post(name: NotificationName.userDidLogout, object: nil)
}
}
/// 注销页顶部风险提示条。
private final class AccountDeletionWarningView: UIView {
private let iconView = UIImageView()
private let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = AppColor.dangerBackground
layer.cornerRadius = 12
layer.borderWidth = 1
layer.borderColor = AppColor.danger.withAlphaComponent(0.25).cgColor
iconView.image = UIImage(systemName: "exclamationmark.shield.fill")
iconView.tintColor = AppColor.danger
iconView.contentMode = .scaleAspectFit
label.text = "注销后,账号及相关数据将永久删除"
label.font = .systemFont(ofSize: 15, weight: .medium)
label.textColor = AppColor.danger
label.numberOfLines = 0
addSubview(iconView)
addSubview(label)
iconView.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.centerY.equalToSuperview()
make.width.height.equalTo(28)
}
label.snp.makeConstraints { make in
make.leading.equalTo(iconView.snp.trailing).offset(12)
make.trailing.equalToSuperview().offset(-16)
make.top.bottom.equalToSuperview().inset(14)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
/// 注销资产汇总卡片,以列表形式中性展示名称和数量。
private final class AccountDeletionAssetCardView: UIView {
private let stackView = UIStackView()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
layer.cornerRadius = 12
clipsToBounds = true
stackView.axis = .vertical
addSubview(stackView)
stackView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16))
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// 使用最新资产快照刷新卡片。
func configure(assets: [AccountDeletionAssetSummary]) {
stackView.arrangedSubviews.forEach {
stackView.removeArrangedSubview($0)
$0.removeFromSuperview()
}
assets.enumerated().forEach { index, asset in
stackView.addArrangedSubview(
AccountDeletionAssetRowView(asset: asset, showsDivider: index < assets.count - 1)
)
}
}
}
/// 注销资产列表行,仅显示客观资产名称和当前值。
private final class AccountDeletionAssetRowView: UIView {
private let titleLabel = UILabel()
private let valueLabel = UILabel()
private let divider = UIView()
init(asset: AccountDeletionAssetSummary, showsDivider: Bool) {
super.init(frame: .zero)
titleLabel.text = asset.title
titleLabel.font = .systemFont(ofSize: 17, weight: .medium)
titleLabel.textColor = AppColor.textPrimary
valueLabel.text = asset.valueText
valueLabel.font = .systemFont(ofSize: 18, weight: .semibold)
valueLabel.textColor = AppColor.textPrimary
valueLabel.textAlignment = .right
divider.backgroundColor = AppColor.border
divider.isHidden = !showsDivider
[titleLabel, valueLabel, divider].forEach(addSubview)
snp.makeConstraints { make in
make.height.equalTo(76)
}
titleLabel.snp.makeConstraints { make in
make.leading.centerY.equalToSuperview()
}
valueLabel.snp.makeConstraints { make in
make.trailing.centerY.equalToSuperview()
make.leading.greaterThanOrEqualTo(titleLabel.snp.trailing).offset(12)
}
divider.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
make.height.equalTo(0.5)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
/// 注销影响说明卡片,展示账号解除、内容删除和7天恢复规则。
private final class AccountDeletionConsequenceCardView: UIView {
private let titleLabel = UILabel()
private let stackView = UIStackView()
private let iconNames = [
"person.crop.circle.badge.xmark",
"folder",
"calendar.badge.exclamationmark",
]
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
layer.cornerRadius = 12
clipsToBounds = true
titleLabel.text = "注销后将发生"
titleLabel.font = .systemFont(ofSize: 18, weight: .semibold)
titleLabel.textColor = AppColor.textPrimary
stackView.axis = .vertical
addSubview(titleLabel)
addSubview(stackView)
titleLabel.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview().inset(16)
}
stackView.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(8)
make.leading.trailing.bottom.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 8, right: 16))
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// 使用最新注销影响文案刷新卡片。
func configure(items: [String]) {
stackView.arrangedSubviews.forEach {
stackView.removeArrangedSubview($0)
$0.removeFromSuperview()
}
items.enumerated().forEach { index, text in
stackView.addArrangedSubview(
AccountDeletionConsequenceRowView(
iconName: iconNames.indices.contains(index) ? iconNames[index] : "info.circle",
text: text,
showsDivider: index < items.count - 1
)
)
}
}
}
/// 注销影响说明列表行。
private final class AccountDeletionConsequenceRowView: UIView {
private let iconBackgroundView = UIView()
private let iconView = UIImageView()
private let label = UILabel()
private let divider = UIView()
init(iconName: String, text: String, showsDivider: Bool) {
super.init(frame: .zero)
iconBackgroundView.backgroundColor = AppColor.primaryLight
iconBackgroundView.layer.cornerRadius = 18
iconView.image = UIImage(systemName: iconName)
iconView.tintColor = AppColor.primary
iconView.contentMode = .scaleAspectFit
label.text = text
label.font = .systemFont(ofSize: 14)
label.textColor = AppColor.textPrimary
label.numberOfLines = 0
divider.backgroundColor = AppColor.border
divider.isHidden = !showsDivider
addSubview(iconBackgroundView)
iconBackgroundView.addSubview(iconView)
addSubview(label)
addSubview(divider)
snp.makeConstraints { make in
make.height.greaterThanOrEqualTo(58)
}
iconBackgroundView.snp.makeConstraints { make in
make.leading.centerY.equalToSuperview()
make.width.height.equalTo(36)
}
iconView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(8)
}
label.snp.makeConstraints { make in
make.leading.equalTo(iconBackgroundView.snp.trailing).offset(12)
make.trailing.equalToSuperview()
make.centerY.equalToSuperview()
}
divider.snp.makeConstraints { make in
make.leading.equalTo(label)
make.trailing.bottom.equalToSuperview()
make.height.equalTo(0.5)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
/// 注销流程通用说明条。
private final class AccountDeletionNoticeView: UIView {
private let iconView = UIImageView()
private let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = AppColor.infoBackground
layer.cornerRadius = 12
addSubview(iconView)
addSubview(label)
iconView.tintColor = AppColor.primary
iconView.contentMode = .scaleAspectFit
label.font = .systemFont(ofSize: 14)
label.textColor = AppColor.textSecondary
label.numberOfLines = 0
iconView.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.centerY.equalToSuperview()
make.width.height.equalTo(24)
}
label.snp.makeConstraints { make in
make.leading.equalTo(iconView.snp.trailing).offset(12)
make.trailing.equalToSuperview().offset(-16)
make.top.bottom.equalToSuperview().inset(14)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// 配置说明条图标与文案。
func configure(iconName: String, text: String) {
iconView.image = UIImage(systemName: iconName)
label.text = text
}
}