// // EGNewBaseAlert.swift // Crush // // Created by Leon on 2025/8/3. // import UIKit import SnapKit // MARK: - EGNewAlertAction enum EGNewAlertActionStyle: Int { case `default` case cancel case confirm case disabled case inputSave case destructive } class EGNewAlertAction : Equatable { var title: String var attributedTitle: NSAttributedString? var actionStyle: EGNewAlertActionStyle var autoDismiss: Bool var actionBlock: (() -> Void)? init(title: String, actionStyle: EGNewAlertActionStyle = .default, autoDismiss: Bool = true, block: (() -> Void)? = nil) { self.title = title self.attributedTitle = nil self.actionStyle = actionStyle self.autoDismiss = autoDismiss self.actionBlock = block } // 新增:使用 NSAttributedString 的初始化方法 init(attributedTitle: NSAttributedString, actionStyle: EGNewAlertActionStyle = .default, autoDismiss: Bool = true, block: (() -> Void)? = nil) { self.title = "" // 当使用富文本时,title 为空 self.attributedTitle = attributedTitle self.actionStyle = actionStyle self.autoDismiss = autoDismiss self.actionBlock = block } static func action(title: String, block: (() -> Void)? = nil) -> EGNewAlertAction { return EGNewAlertAction(title: title, block: block) } static func action(title: String, actionStyle: EGNewAlertActionStyle, block: (() -> Void)? = nil) -> EGNewAlertAction { return EGNewAlertAction(title: title, actionStyle: actionStyle, block: block) } static func action(title: String, actionStyle: EGNewAlertActionStyle, autoDismiss: Bool, block: (() -> Void)? = nil) -> EGNewAlertAction { return EGNewAlertAction(title: title, actionStyle: actionStyle, autoDismiss: autoDismiss, block: block) } // 新增:使用 NSAttributedString 的静态方法 static func action(attributedTitle: NSAttributedString, block: (() -> Void)? = nil) -> EGNewAlertAction { return EGNewAlertAction(attributedTitle: attributedTitle, block: block) } static func action(attributedTitle: NSAttributedString, actionStyle: EGNewAlertActionStyle, block: (() -> Void)? = nil) -> EGNewAlertAction { return EGNewAlertAction(attributedTitle: attributedTitle, actionStyle: actionStyle, block: block) } static func action(attributedTitle: NSAttributedString, actionStyle: EGNewAlertActionStyle, autoDismiss: Bool, block: (() -> Void)? = nil) -> EGNewAlertAction { return EGNewAlertAction(attributedTitle: attributedTitle, actionStyle: actionStyle, autoDismiss: autoDismiss, block: block) } // MARK: - Equatable static func == (lhs: EGNewAlertAction, rhs: EGNewAlertAction) -> Bool { return lhs === rhs // 🔥 Using identity comparison; adjust if unique identifier exists } } // MARK: - EGNewBaseAlert enum EGNewAlertPriority: Int { case `default` = 0 case update = 10 case forceUpdate = 100 } class EGNewBaseAlert: UIView { // MARK: - Properties private(set) var backgroundView: UIView! private(set) var containerView: UIView! private(set) var textContentView: UIView! private(set) var buttonContainer: UIView! private(set) var buttons: [StyleButton] = [] private(set) var actions: [EGNewAlertAction] = [] var priority: EGNewAlertPriority = .default private var maxActionCount: Int = 2 var containerWidth: CGFloat { return UIScreen.main.bounds.width - containerMarginLR() * 2 } // MARK: - Initialization override init(frame: CGRect) { super.init(frame: frame) baseDataInit() baseUIInit() } required init?(coder: NSCoder) { super.init(coder: coder) baseDataInit() baseUIInit() } private func baseDataInit() { maxActionCount = 2 buttons = [] actions = [] } private func baseUIInit() { backgroundView = UIView().then { addSubview($0) $0.backgroundColor = .c.cbs $0.alpha = 0 $0.snp.makeConstraints { make in make.edges.equalToSuperview() } } containerView = UIView().then { addSubview($0) $0.backgroundColor = .c.csbn // 🔥 Assuming EPS_Color_Surface_base_normal is a light color $0.layer.cornerRadius = containerCornerRadius() $0.clipsToBounds = true $0.alpha = 0 let width = UIScreen.main.bounds.width * 0.8 $0.snp.makeConstraints { make in make.center.equalToSuperview() make.width.equalTo(width) } } textContentView = UIView().then { containerView.addSubview($0) $0.backgroundColor = .clear $0.setContentHuggingPriority(.defaultLow, for: .vertical) $0.snp.makeConstraints { make in make.leading.trailing.top.equalToSuperview() make.height.greaterThanOrEqualTo(textContentMinHeight()) } } buttonContainer = UIView().then { containerView.addSubview($0) $0.backgroundColor = .clear $0.snp.makeConstraints { make in make.top.equalTo(textContentView.snp.bottom) make.leading.trailing.bottom.equalToSuperview() } } } // MARK: - Layout Methods (Overridable) func containerMarginLR() -> CGFloat { return 40 } func textContentMaxHeight() -> CGFloat { return UIScreen.main.bounds.height * 0.65 } func textContentMinHeight() -> CGFloat { return 100 } func containerCornerRadius() -> CGFloat { return 16 } func containerAlphaDuration() -> CGFloat { return 0.35 } func backgroundAlphaValue() -> CGFloat { return 0.65 } func alertInitRatio() -> CGFloat { return 0.6 } // MARK: - Actions func addAction(_ action: EGNewAlertAction) { addAction(action, propertySetup: nil) } func addAction(_ action: EGNewAlertAction, propertySetup: ((UIButton) -> Void)?) { guard actions.count < maxActionCount else { return } let button = StyleButton(type: .custom) button.addTarget(self, action: #selector(baseButtonPressed(_:)), for: .touchUpInside) // 支持富文本和普通文本 if let attributedTitle = action.attributedTitle { button.setAttributedTitle(attributedTitle, for: .normal) } else { button.setTitle(action.title, for: .normal) } button.tag = actions.count button.titleLabel?.font = .t.tbsl// EPS_Txt_BodySemibold_l is a bold font reloadButton(button, style: action.actionStyle) if let setup = propertySetup { setup(button) } actions.append(action) buttons.append(button) } func reloadAction(_ action: EGNewAlertAction, title: String) { guard let index = actions.firstIndex(of: action) else { assertionFailure("Action not found") return } let button = buttons[index] button.setTitle(title, for: .normal) reloadButton(button, style: action.actionStyle) } // 新增:支持富文本的 reloadAction 方法 func reloadAction(_ action: EGNewAlertAction, attributedTitle: NSAttributedString) { guard let index = actions.firstIndex(of: action) else { assertionFailure("Action not found") return } let button = buttons[index] button.setAttributedTitle(attributedTitle, for: .normal) reloadButton(button, style: action.actionStyle) } private func reloadButton(_ button: StyleButton, style: EGNewAlertActionStyle) { button.isEnabled = true switch style { case .default, .cancel: button.tertiary(size: .large) case .confirm,.inputSave: button.primary(size: .large) case .disabled: button.tertiary(size: .large) button.isEnabled = false case .destructive: button.defaultDestructive(size: .large) } } // MARK: - Events @objc private func baseButtonPressed(_ button: UIButton) { endEditing(true) let action = actions[button.tag] if action.autoDismiss { dismiss() } action.actionBlock?() } func alertDidShow() { // Overridable by subclasses } // MARK: - Animation func showWithAnimation() { containerView.transform = CGAffineTransform(scaleX: alertInitRatio(), y: alertInitRatio()) UIView.animate(withDuration: containerAlphaDuration(), delay: 0, options: .curveEaseOut, animations: { self.backgroundView.alpha = self.backgroundAlphaValue() self.containerView.alpha = 1 self.containerView.transform = .identity }) { _ in self.alertDidShow() } } func dismissWithAnimation() { UIView.animate(withDuration: containerAlphaDuration(), delay: 0, options: .curveEaseIn, animations: { self.containerView.transform = CGAffineTransform(scaleX: 1.05, y: 1.05) self.backgroundView.alpha = 0 self.containerView.alpha = 0 }) { _ in self.removeFromSuperview() } } // MARK: - Display func show() { // ⚠️ Before is windows's first //guard let window = UIApplication.shared.windows.first else { return } // 第二种方式: // var window: UIWindow! // for per in UIApplication.shared.windows.reversed(){ // if per.isHidden == false{ // window = per // break // } // } // 第3种方式: 会过滤一些 var window: UIWindow! window = UIWindow.getTopDisplayWindow()! if window == nil{ assert(false, "alert window not found") } window.endEditing(true) // 隐藏已有的Alert(覆盖显示) for subview in window.subviews { if let oldAlert = subview as? EGNewBaseAlert { if oldAlert.priority != .default && oldAlert.priority.rawValue >= priority.rawValue { return } else { oldAlert.dismiss() } } } window.addSubview(self) self.frame = window.bounds showWithAnimation() Hud.hideIndicator() } func dismiss() { dismissWithAnimation() } static func existingAlert() -> Bool { return existingAlertObj() != nil } static func existingAlertObj() -> EGNewBaseAlert? { //guard let window = UIApplication.shared.windows.first else { return nil } guard let window = UIWindow.getTopDisplayWindow() else { return nil } return window.subviews.first(where: { $0 is EGNewBaseAlert }) as? EGNewBaseAlert } static func hideAllAlert() { guard let window = UIApplication.shared.windows.first else { return } for subview in window.subviews { if let alert = subview as? EGNewBaseAlert { alert.isHidden = true alert.removeFromSuperview() } } } func setupMaxActionCount(_ count: Int) { maxActionCount = count } } // MARK: - Then Protocol for Chaining protocol Then {} extension Then where Self: AnyObject { func then(_ block: (Self) -> Void) -> Self { block(self) return self } } extension UIView: Then {}