Integrate高德 SDK with simulator-safe build flags, add map views for operating area and punch points, refactor main tabs and key feature screens to UIKit with Diffable lists, and document Swift concurrency defaults in AGENTS.md. Co-authored-by: Cursor <cursoragent@cursor.com>
558 lines
22 KiB
Swift
558 lines
22 KiB
Swift
//
|
||
// LoginViewController.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import SnapKit
|
||
import UIKit
|
||
|
||
@MainActor
|
||
/// 登录页,展示手机号/密码表单、协议勾选和多账号选择入口。
|
||
final class LoginViewController: UIViewController {
|
||
|
||
private let services: AppServices
|
||
private let viewModel = LoginViewModel()
|
||
|
||
private let scrollView = UIScrollView()
|
||
private let contentView = UIView()
|
||
private let backgroundImageView = UIImageView(image: UIImage(named: "LoginBackground"))
|
||
private let titleLabel = UILabel()
|
||
private let cardView = UIView()
|
||
private let usernameField = LoginInputField(iconName: "person", placeholder: "请输入手机号", isSecure: false)
|
||
private let passwordField = LoginInputField(iconName: "lock", placeholder: "请输入密码", isSecure: true)
|
||
private let privacyButton = UIButton(type: .custom)
|
||
private let privacyLabel = UILabel()
|
||
private let userAgreementButton = UIButton(type: .system)
|
||
private let privacyPolicyButton = UIButton(type: .system)
|
||
private let loginButton = UIButton(type: .system)
|
||
|
||
/// 初始化实例。
|
||
init(services: AppServices) {
|
||
self.services = services
|
||
super.init(nibName: nil, bundle: nil)
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
/// 视图加载完成后的 UI 初始化与数据绑定。
|
||
override func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
view.backgroundColor = UIColor(hex: 0x0B1220)
|
||
configureViews()
|
||
bindViewModel()
|
||
viewModel.applyPreferences(services.authSessionCoordinator.loginPreferences())
|
||
}
|
||
|
||
/// 配置Views展示内容。
|
||
private func configureViews() {
|
||
backgroundImageView.contentMode = .scaleAspectFill
|
||
backgroundImageView.clipsToBounds = true
|
||
|
||
titleLabel.text = "欢迎使用\n随心瞰商家版"
|
||
titleLabel.font = .systemFont(ofSize: AppMetrics.FontSize.largeTitle, weight: .semibold)
|
||
titleLabel.textColor = .white
|
||
titleLabel.numberOfLines = 0
|
||
|
||
cardView.backgroundColor = .white
|
||
cardView.layer.cornerRadius = AppMetrics.CornerRadius.card
|
||
cardView.layer.shadowColor = UIColor.black.cgColor
|
||
cardView.layer.shadowOpacity = 0.12
|
||
cardView.layer.shadowRadius = 18
|
||
cardView.layer.shadowOffset = CGSize(width: 0, height: 8)
|
||
|
||
usernameField.textField.keyboardType = .phonePad
|
||
usernameField.textField.textContentType = .telephoneNumber
|
||
usernameField.textField.autocorrectionType = .no
|
||
usernameField.textField.autocapitalizationType = .none
|
||
usernameField.textField.returnKeyType = .next
|
||
usernameField.textField.accessibilityIdentifier = "login.username"
|
||
usernameField.textField.delegate = self
|
||
usernameField.textField.addTarget(self, action: #selector(usernameChanged), for: .editingChanged)
|
||
|
||
passwordField.textField.autocorrectionType = .no
|
||
passwordField.textField.autocapitalizationType = .none
|
||
passwordField.textField.returnKeyType = .go
|
||
passwordField.textField.accessibilityIdentifier = "login.password"
|
||
passwordField.textField.delegate = self
|
||
passwordField.textField.addTarget(self, action: #selector(passwordChanged), for: .editingChanged)
|
||
passwordField.onToggleVisibility = { [weak self] in
|
||
self?.viewModel.showsPassword.toggle()
|
||
}
|
||
|
||
privacyButton.accessibilityIdentifier = "login.privacy"
|
||
privacyButton.addTarget(self, action: #selector(togglePrivacy), for: .touchUpInside)
|
||
|
||
configureAgreementText()
|
||
|
||
loginButton.setTitle("登录", for: .normal)
|
||
loginButton.titleLabel?.font = .systemFont(ofSize: AppMetrics.FontSize.body, weight: .medium)
|
||
loginButton.layer.cornerRadius = AppMetrics.CornerRadius.button
|
||
loginButton.accessibilityIdentifier = "login.submit"
|
||
loginButton.addTarget(self, action: #selector(loginTapped), for: .touchUpInside)
|
||
|
||
view.addSubview(backgroundImageView)
|
||
view.addSubview(scrollView)
|
||
scrollView.addSubview(contentView)
|
||
contentView.addSubview(titleLabel)
|
||
contentView.addSubview(cardView)
|
||
|
||
cardView.addSubview(usernameField)
|
||
cardView.addSubview(passwordField)
|
||
cardView.addSubview(privacyButton)
|
||
cardView.addSubview(privacyLabel)
|
||
cardView.addSubview(loginButton)
|
||
|
||
backgroundImageView.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview()
|
||
}
|
||
|
||
scrollView.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview()
|
||
}
|
||
|
||
contentView.snp.makeConstraints { make in
|
||
make.edges.equalTo(scrollView.contentLayoutGuide)
|
||
make.width.equalTo(scrollView.frameLayoutGuide)
|
||
}
|
||
|
||
titleLabel.snp.makeConstraints { make in
|
||
make.top.equalTo(contentView.safeAreaLayoutGuide).offset(72)
|
||
make.leading.trailing.equalToSuperview().inset(24)
|
||
}
|
||
titleLabel.accessibilityIdentifier = "login.title"
|
||
|
||
cardView.snp.makeConstraints { make in
|
||
make.top.equalTo(titleLabel.snp.bottom).offset(AppMetrics.Spacing.xxLarge)
|
||
make.leading.trailing.equalToSuperview().inset(20)
|
||
make.bottom.equalToSuperview().inset(AppMetrics.Spacing.xxLarge)
|
||
}
|
||
|
||
usernameField.snp.makeConstraints { make in
|
||
make.top.leading.trailing.equalToSuperview().inset(AppMetrics.Spacing.medium)
|
||
make.height.equalTo(AppMetrics.ControlSize.inputHeight)
|
||
}
|
||
|
||
passwordField.snp.makeConstraints { make in
|
||
make.top.equalTo(usernameField.snp.bottom).offset(AppMetrics.Spacing.xSmall)
|
||
make.leading.trailing.equalTo(usernameField)
|
||
make.height.equalTo(AppMetrics.ControlSize.inputHeight)
|
||
}
|
||
|
||
privacyButton.snp.makeConstraints { make in
|
||
make.top.equalTo(passwordField.snp.bottom).offset(AppMetrics.ControlSize.checkboxTapArea)
|
||
make.leading.equalToSuperview().inset(AppMetrics.Spacing.medium)
|
||
make.width.height.equalTo(AppMetrics.ControlSize.checkboxTapArea)
|
||
}
|
||
|
||
privacyLabel.snp.makeConstraints { make in
|
||
make.leading.equalTo(privacyButton.snp.trailing)
|
||
make.trailing.equalToSuperview().inset(AppMetrics.Spacing.medium)
|
||
make.top.equalTo(privacyButton).offset(AppMetrics.Spacing.xxSmall)
|
||
}
|
||
|
||
loginButton.snp.makeConstraints { make in
|
||
make.top.equalTo(privacyLabel.snp.bottom).offset(AppMetrics.Spacing.mediumLarge)
|
||
make.leading.trailing.bottom.equalToSuperview().inset(AppMetrics.Spacing.medium)
|
||
make.height.equalTo(AppMetrics.ControlSize.primaryButtonHeight)
|
||
}
|
||
|
||
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
|
||
tap.cancelsTouchesInView = false
|
||
view.addGestureRecognizer(tap)
|
||
}
|
||
|
||
/// 配置AgreementText展示内容。
|
||
private func configureAgreementText() {
|
||
privacyLabel.numberOfLines = 0
|
||
privacyLabel.font = .systemFont(ofSize: AppMetrics.FontSize.subheadline)
|
||
|
||
userAgreementButton.setTitle("《用户协议》", for: .normal)
|
||
userAgreementButton.titleLabel?.font = .systemFont(ofSize: AppMetrics.FontSize.subheadline)
|
||
userAgreementButton.setTitleColor(UIColor(hex: 0x208BFF), for: .normal)
|
||
userAgreementButton.accessibilityIdentifier = "login.userAgreement"
|
||
userAgreementButton.addTarget(self, action: #selector(openUserAgreement), for: .touchUpInside)
|
||
|
||
privacyPolicyButton.setTitle("《隐私政策》", for: .normal)
|
||
privacyPolicyButton.titleLabel?.font = .systemFont(ofSize: AppMetrics.FontSize.subheadline)
|
||
privacyPolicyButton.setTitleColor(UIColor(hex: 0x208BFF), for: .normal)
|
||
privacyPolicyButton.accessibilityIdentifier = "login.privacyPolicy"
|
||
privacyPolicyButton.addTarget(self, action: #selector(openPrivacyPolicy), for: .touchUpInside)
|
||
|
||
let prefix = UILabel()
|
||
prefix.text = "已阅读并同意"
|
||
prefix.font = .systemFont(ofSize: AppMetrics.FontSize.subheadline)
|
||
prefix.textColor = UIColor(hex: 0x666666)
|
||
|
||
let separator = UILabel()
|
||
separator.text = "与"
|
||
separator.font = .systemFont(ofSize: AppMetrics.FontSize.subheadline)
|
||
separator.textColor = UIColor(hex: 0x666666)
|
||
|
||
let row = UIStackView(arrangedSubviews: [prefix, userAgreementButton, separator, privacyPolicyButton])
|
||
row.axis = .horizontal
|
||
row.spacing = 0
|
||
row.alignment = .center
|
||
privacyLabel.addSubview(row)
|
||
row.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview()
|
||
}
|
||
}
|
||
|
||
/// 绑定ViewModel回调或数据。
|
||
private func bindViewModel() {
|
||
viewModel.onChange = { [weak self] in
|
||
self?.renderViewModel()
|
||
}
|
||
renderViewModel()
|
||
}
|
||
|
||
/// render视图模型相关逻辑。
|
||
private func renderViewModel() {
|
||
if usernameField.textField.text != viewModel.username {
|
||
usernameField.textField.text = viewModel.username
|
||
}
|
||
if passwordField.textField.text != viewModel.password {
|
||
passwordField.textField.text = viewModel.password
|
||
}
|
||
|
||
passwordField.setSecureEntry(!viewModel.showsPassword)
|
||
|
||
let checkboxImage = viewModel.privacyChecked ? "LoginCheckboxChecked" : "LoginCheckboxUnchecked"
|
||
privacyButton.setImage(UIImage(named: checkboxImage), for: .normal)
|
||
|
||
loginButton.isEnabled = viewModel.canSubmit && !viewModel.isLoading
|
||
loginButton.backgroundColor = viewModel.canSubmit ? AppDesign.primary : .gray
|
||
loginButton.setTitleColor(.white, for: .normal)
|
||
loginButton.alpha = viewModel.isLoading ? 0.7 : 1
|
||
|
||
if viewModel.showsAgreementSheet {
|
||
viewModel.showsAgreementSheet = false
|
||
presentAgreementSheet()
|
||
}
|
||
|
||
if let payload = viewModel.pendingAccountSelection {
|
||
if let controller = accountSelectionController {
|
||
controller.updateLoading(viewModel.isSelectingAccount)
|
||
} else if presentedViewController == nil {
|
||
presentAccountSelection(payload)
|
||
}
|
||
} else if accountSelectionController != nil {
|
||
dismiss(animated: true)
|
||
accountSelectionController = nil
|
||
}
|
||
}
|
||
|
||
private weak var accountSelectionController: AccountSelectionViewController?
|
||
|
||
/// username变更相关逻辑。
|
||
@objc private func usernameChanged() {
|
||
viewModel.username = usernameField.textField.text ?? ""
|
||
viewModel.normalizeUsernameCountryCodeIfNeeded()
|
||
services.toastCenter.dismiss()
|
||
}
|
||
|
||
/// password变更相关逻辑。
|
||
@objc private func passwordChanged() {
|
||
viewModel.password = passwordField.textField.text ?? ""
|
||
services.toastCenter.dismiss()
|
||
}
|
||
|
||
/// togglePrivacy相关逻辑。
|
||
@objc private func togglePrivacy() {
|
||
viewModel.privacyChecked.toggle()
|
||
}
|
||
|
||
/// openUserAgreement相关逻辑。
|
||
@objc private func openUserAgreement() {
|
||
showToast("用户协议页面待接入")
|
||
}
|
||
|
||
/// openPrivacyPolicy相关逻辑。
|
||
@objc private func openPrivacyPolicy() {
|
||
showToast("隐私政策页面待接入")
|
||
}
|
||
|
||
/// 点击login的处理逻辑。
|
||
@objc private func loginTapped() {
|
||
view.endEditing(true)
|
||
performLogin()
|
||
}
|
||
|
||
/// dismiss键盘相关逻辑。
|
||
@objc private func dismissKeyboard() {
|
||
view.endEditing(true)
|
||
}
|
||
|
||
/// perform登录相关逻辑。
|
||
private func performLogin() {
|
||
if let validationError = viewModel.validateForLogin() {
|
||
if validationError == .privacyUnchecked {
|
||
presentAgreementSheet()
|
||
} else {
|
||
showToast(validationError.message)
|
||
if validationError.focusField == .username {
|
||
usernameField.textField.becomeFirstResponder()
|
||
} else if validationError.focusField == .password {
|
||
passwordField.textField.becomeFirstResponder()
|
||
}
|
||
}
|
||
return
|
||
}
|
||
|
||
Task {
|
||
do {
|
||
try await services.globalLoading.withLoading(message: "登录中...") {
|
||
let resolution = try await viewModel.login(authAPI: services.authAPI)
|
||
switch resolution {
|
||
case let .completed(response):
|
||
await completeLogin(with: response)
|
||
case .needsAccountSelection:
|
||
break
|
||
}
|
||
}
|
||
} catch is CancellationError {
|
||
return
|
||
} catch {
|
||
showToast(error.localizedDescription)
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 弹出AgreementSheet页面。
|
||
private func presentAgreementSheet() {
|
||
let controller = LoginAgreementConsentViewController(
|
||
onOpenAgreement: { [weak self] title in
|
||
self?.showToast("\(title)页面待接入")
|
||
},
|
||
onAgreeAndContinue: { [weak self] in
|
||
self?.viewModel.acceptAgreement()
|
||
self?.performLogin()
|
||
}
|
||
)
|
||
if let sheet = controller.sheetPresentationController {
|
||
sheet.detents = [.custom(resolver: { _ in 240 })]
|
||
sheet.prefersGrabberVisible = true
|
||
}
|
||
present(controller, animated: true)
|
||
}
|
||
|
||
/// 弹出AccountSelection页面。
|
||
private func presentAccountSelection(_ payload: AccountSelectionPayload) {
|
||
let controller = AccountSelectionViewController(
|
||
payload: payload,
|
||
isLoading: viewModel.isSelectingAccount,
|
||
onCancel: { [weak self] in
|
||
self?.viewModel.clearPendingAccountSelection()
|
||
self?.accountSelectionController = nil
|
||
},
|
||
onConfirm: { [weak self] account in
|
||
self?.selectAccount(account)
|
||
}
|
||
)
|
||
accountSelectionController = controller
|
||
let navigation = UINavigationController(rootViewController: controller)
|
||
navigation.modalPresentationStyle = .formSheet
|
||
present(navigation, animated: true)
|
||
}
|
||
|
||
/// select账号相关逻辑。
|
||
private func selectAccount(_ account: AccountSwitchAccount) {
|
||
Task {
|
||
do {
|
||
try await services.globalLoading.withLoading(message: "账号切换中...") {
|
||
let response = try await viewModel.selectAccount(account, authAPI: services.authAPI)
|
||
await completeLogin(with: response)
|
||
accountSelectionController = nil
|
||
dismiss(animated: true)
|
||
}
|
||
} catch is CancellationError {
|
||
return
|
||
} catch {
|
||
showToast(error.localizedDescription)
|
||
}
|
||
}
|
||
}
|
||
|
||
/// complete登录相关逻辑。
|
||
private func completeLogin(with response: V9AuthResponse) async {
|
||
do {
|
||
try await services.authSessionCoordinator.completeLogin(
|
||
with: response,
|
||
username: viewModel.normalizedUsername,
|
||
privacyAgreementAccepted: viewModel.privacyChecked,
|
||
appSession: services.appSession,
|
||
accountContext: services.accountContext,
|
||
permissionContext: services.permissionContext,
|
||
profileAPI: services.profileAPI,
|
||
accountContextAPI: services.accountContextAPI
|
||
)
|
||
} catch {
|
||
showToast(error.localizedDescription)
|
||
}
|
||
}
|
||
}
|
||
|
||
extension LoginViewController: UITextFieldDelegate {
|
||
/// text字段ShouldReturn相关逻辑。
|
||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||
if textField === usernameField.textField {
|
||
passwordField.textField.becomeFirstResponder()
|
||
} else if viewModel.canSubmit, !viewModel.isLoading {
|
||
performLogin()
|
||
}
|
||
return true
|
||
}
|
||
}
|
||
|
||
/// 登录输入框,包含图标、文本框和密码可见切换。
|
||
private final class LoginInputField: UIView {
|
||
let textField = UITextField()
|
||
var onToggleVisibility: (() -> Void)?
|
||
|
||
private let toggleButton = UIButton(type: .custom)
|
||
private var isSecure = false
|
||
|
||
/// 初始化实例。
|
||
init(iconName: String, placeholder: String, isSecure: Bool) {
|
||
self.isSecure = isSecure
|
||
super.init(frame: .zero)
|
||
backgroundColor = UIColor(hex: 0xF1F5F9)
|
||
layer.cornerRadius = AppMetrics.CornerRadius.input
|
||
layer.borderWidth = 1
|
||
layer.borderColor = UIColor(hex: 0xDDE3EA).cgColor
|
||
|
||
let icon = UIImageView(image: UIImage(systemName: iconName))
|
||
icon.tintColor = AppDesign.textSecondary
|
||
icon.contentMode = .scaleAspectFit
|
||
|
||
textField.placeholder = placeholder
|
||
textField.font = .systemFont(ofSize: AppMetrics.FontSize.body)
|
||
textField.textColor = AppDesign.textPrimary
|
||
textField.tintColor = AppDesign.primary
|
||
textField.borderStyle = .none
|
||
textField.isSecureTextEntry = isSecure
|
||
|
||
addSubview(icon)
|
||
addSubview(textField)
|
||
|
||
icon.snp.makeConstraints { make in
|
||
make.leading.equalToSuperview().inset(AppMetrics.Spacing.medium)
|
||
make.centerY.equalToSuperview()
|
||
make.width.height.equalTo(AppMetrics.ControlSize.smallIcon)
|
||
}
|
||
|
||
if isSecure {
|
||
toggleButton.addTarget(self, action: #selector(toggleVisibility), for: .touchUpInside)
|
||
addSubview(toggleButton)
|
||
toggleButton.snp.makeConstraints { make in
|
||
make.trailing.equalToSuperview().inset(AppMetrics.Spacing.small)
|
||
make.centerY.equalToSuperview()
|
||
make.width.height.equalTo(AppMetrics.ControlSize.iconTapArea)
|
||
}
|
||
textField.snp.makeConstraints { make in
|
||
make.leading.equalTo(icon.snp.trailing).offset(AppMetrics.Spacing.xSmall)
|
||
make.trailing.equalTo(toggleButton.snp.leading)
|
||
make.centerY.equalToSuperview()
|
||
}
|
||
} else {
|
||
textField.snp.makeConstraints { make in
|
||
make.leading.equalTo(icon.snp.trailing).offset(AppMetrics.Spacing.xSmall)
|
||
make.trailing.equalToSuperview().inset(AppMetrics.Spacing.medium)
|
||
make.centerY.equalToSuperview()
|
||
}
|
||
}
|
||
}
|
||
|
||
required init?(coder: NSCoder) {
|
||
nil
|
||
}
|
||
|
||
/// setSecureEntry相关逻辑。
|
||
func setSecureEntry(_ secure: Bool) {
|
||
textField.isSecureTextEntry = secure
|
||
let imageName = secure ? "LoginPwdInvisible" : "LoginPwdVisible"
|
||
toggleButton.setImage(UIImage(named: imageName), for: .normal)
|
||
}
|
||
|
||
/// toggleVisibility相关逻辑。
|
||
@objc private func toggleVisibility() {
|
||
onToggleVisibility?()
|
||
}
|
||
}
|
||
|
||
/// 协议确认弹窗,引导用户勾选协议后继续登录。
|
||
private final class LoginAgreementConsentViewController: UIViewController {
|
||
|
||
private let onOpenAgreement: (String) -> Void
|
||
private let onAgreeAndContinue: () -> Void
|
||
|
||
/// 初始化实例。
|
||
init(onOpenAgreement: @escaping (String) -> Void, onAgreeAndContinue: @escaping () -> Void) {
|
||
self.onOpenAgreement = onOpenAgreement
|
||
self.onAgreeAndContinue = onAgreeAndContinue
|
||
super.init(nibName: nil, bundle: nil)
|
||
}
|
||
|
||
required init?(coder: NSCoder) {
|
||
nil
|
||
}
|
||
|
||
/// 视图加载完成后的 UI 初始化与数据绑定。
|
||
override func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
view.backgroundColor = .systemBackground
|
||
|
||
let titleLabel = UILabel()
|
||
titleLabel.text = "请先阅读并同意相关协议"
|
||
titleLabel.font = .systemFont(ofSize: AppMetrics.FontSize.title3, weight: .semibold)
|
||
titleLabel.textColor = AppDesign.textPrimary
|
||
titleLabel.textAlignment = .center
|
||
|
||
let descriptionLabel = UILabel()
|
||
descriptionLabel.text = "为了保障你的账号安全和服务体验,请阅读并同意用户协议和隐私政策。"
|
||
descriptionLabel.font = .systemFont(ofSize: AppMetrics.FontSize.subheadline)
|
||
descriptionLabel.textColor = AppDesign.textSecondary
|
||
descriptionLabel.numberOfLines = 0
|
||
descriptionLabel.textAlignment = .center
|
||
|
||
let continueButton = UIButton(type: .system)
|
||
continueButton.setTitle("同意并继续", for: .normal)
|
||
continueButton.titleLabel?.font = .systemFont(ofSize: AppMetrics.FontSize.body, weight: .medium)
|
||
continueButton.backgroundColor = AppDesign.primary
|
||
continueButton.setTitleColor(.white, for: .normal)
|
||
continueButton.layer.cornerRadius = AppMetrics.CornerRadius.button
|
||
continueButton.accessibilityIdentifier = "login.agreement.continue"
|
||
continueButton.addTarget(self, action: #selector(continueTapped), for: .touchUpInside)
|
||
|
||
view.addSubview(titleLabel)
|
||
view.addSubview(descriptionLabel)
|
||
view.addSubview(continueButton)
|
||
|
||
titleLabel.snp.makeConstraints { make in
|
||
make.top.equalTo(view.safeAreaLayoutGuide).offset(AppMetrics.Spacing.sheet)
|
||
make.leading.trailing.equalToSuperview().inset(AppMetrics.Spacing.large)
|
||
}
|
||
|
||
descriptionLabel.snp.makeConstraints { make in
|
||
make.top.equalTo(titleLabel.snp.bottom).offset(AppMetrics.Spacing.small)
|
||
make.leading.trailing.equalTo(titleLabel)
|
||
}
|
||
|
||
continueButton.snp.makeConstraints { make in
|
||
make.leading.trailing.equalTo(titleLabel)
|
||
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(AppMetrics.Spacing.mediumLarge)
|
||
make.height.equalTo(AppMetrics.ControlSize.sheetButtonHeight)
|
||
}
|
||
}
|
||
|
||
/// 点击continue的处理逻辑。
|
||
@objc private func continueTapped() {
|
||
dismiss(animated: true) { [onAgreeAndContinue] in
|
||
onAgreeAndContinue()
|
||
}
|
||
}
|
||
}
|