Files
VoidLink/VoidLink/AlertControllerUtil.swift
T
True Zhuanjia 9a634da3ed - feat: global controller navigation
- shorten initial host detection
2026-07-26 16:07:03 +08:00

200 lines
7.1 KiB
Swift

//
// AlertControllerUtil.swift
// VoidLink
//
// Created by Weimin on 2025/10/6.
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
//
import UIKit
@objc class AlertControllerUtil: NSObject {
private enum AlertTextRole {
case title
case message
}
private static func makeCountdownMessage(baseMessage: String?, remainingSeconds: Int) -> String {
let countdownText = "\(remainingSeconds)"
guard let baseMessage, !baseMessage.isEmpty else {
return countdownText
}
return "\(baseMessage)\n\n\(countdownText)"
}
@available(iOS 13.0, *)
private static func makeLeftAlignedAttributedText(_ text: String, role: AlertTextRole) -> NSAttributedString {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .left
paragraphStyle.lineBreakMode = .byWordWrapping
let font: UIFont
let color: UIColor
switch role {
case .title:
font = .preferredFont(forTextStyle: .headline)
color = .label
case .message:
font = .preferredFont(forTextStyle: .footnote)
color = .label
}
return NSAttributedString(
string: text,
attributes: [
.paragraphStyle: paragraphStyle,
.font: font,
.foregroundColor: color
]
)
}
private static func applyLeftAlignment(title: String?, message: String?) {
if #available(iOS 26.0, *) {
return
}
if #available(iOS 13.0, *) {
if let title, !title.isEmpty {
alertController.setValue(makeLeftAlignedAttributedText(title, role: .title), forKey: "attributedTitle")
}
if let message, !message.isEmpty {
alertController.setValue(makeLeftAlignedAttributedText(message, role: .message), forKey: "attributedMessage")
}
}
}
/// 类方法,直接在任何 UIViewController 上显示倒计时弹窗
/// - Parameters:
/// - viewController: 要显示弹窗的控制器
/// - title: 弹窗标题
/// - message: 弹窗内容
/// - buttonTitle: 按钮初始文字(倒计时结束后显示)
/// - countdown: 倒计时秒数
/// - completion: 点击确认或倒计时结束的回调
@objc public static var actionCancelled:Bool = false
@objc public static var alertController:UIAlertController = UIAlertController()
@objc public static var cancelButtonString:String = "Cancel"
@objc static var completion: (() -> Void)? = nil
@objc public static var autoCompletion:Bool = false
@objc public static var isCountingDown:Bool = false
@objc class func showAlert(
in viewController: UIViewController? = nil,
title: String?,
message: String?,
withCancel: Bool,
buttonTitle: String,
countdown: Int,
action: (() -> Void)? = nil,
completion: (() -> Void)? = nil
) {
var remainingSeconds = countdown
let originalMessage = message
var isAlertDismissed = false
AlertControllerUtil.completion = completion
guard let viewController = viewController else {return}
alertController = UIAlertController(
title: title,
message: (countdown > 0 && !autoCompletion) ? makeCountdownMessage(baseMessage: originalMessage, remainingSeconds: remainingSeconds) : message,
preferredStyle: .alert
)
applyLeftAlignment(
title: title,
message: (countdown > 0 && !autoCompletion) ? makeCountdownMessage(baseMessage: originalMessage, remainingSeconds: remainingSeconds) : message
)
let confirmAction = UIAlertAction(title: buttonTitle, style: .default) { _ in
isAlertDismissed = true
actionCancelled = false
completion?()
cancelButtonString = "Cancel"
}
let cancelAction = UIAlertAction(title: LocalizationHelper.localizedString(forKey: cancelButtonString), style: .cancel) { _ in
isAlertDismissed = true
actionCancelled = true
completion?()
cancelButtonString = "Cancel"
}
confirmAction.isEnabled = false
if withCancel {alertController.addAction(cancelAction)}
if buttonTitle != "" && !autoCompletion {alertController.addAction(confirmAction)}
if(countdown == 0) {
confirmAction.isEnabled = buttonTitle != ""
}
viewController.present(alertController, animated: true, completion: action)
isCountingDown = !autoCompletion && countdown > 0
if(countdown == 0) {return}
// 倒计时
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
timer.schedule(deadline: .now() + (autoCompletion ? 0.5 : 1.0), repeating: autoCompletion ? 0.1: 1.0, leeway: .milliseconds(50))
timer.setEventHandler {
guard !isAlertDismissed else {
timer.cancel()
return
}
remainingSeconds -= 1
if remainingSeconds <= 0 {
isCountingDown = false
isAlertDismissed = true
timer.cancel()
if autoCompletion {
alertController.message = originalMessage
applyLeftAlignment(title: title, message: originalMessage)
autoCompletion = false
completion?()
AlertControllerUtil.completion = nil
alertController.dismiss(animated: false)
return
}
if PublicUtils.isRunningOnMacAsiPadApp && buttonTitle != "" {
AlertControllerUtil.completion = nil
alertController.dismiss(animated: false) {
showAlert(
in: viewController,
title: title,
message: originalMessage,
withCancel: withCancel,
buttonTitle: buttonTitle,
countdown: 0,
action: nil,
completion: completion
)
}
return
}
confirmAction.isEnabled = true
alertController.message = originalMessage
applyLeftAlignment(title: title, message: originalMessage)
if #available(iOS 13.0, *) {
ControllerNavigator.setUINavigationDelegate(alertController)
}
} else {
if !autoCompletion {
let countdownMessage = makeCountdownMessage(baseMessage: originalMessage, remainingSeconds: remainingSeconds)
alertController.message = countdownMessage
applyLeftAlignment(title: title, message: countdownMessage)
}
}
}
timer.resume()
}
}