// // BaseViewController.swift // suixinkan // import SnapKit import UIKit /// 业务 ViewController 基类,提供统一的 UI 搭建流程与通用 UI 能力。 class BaseViewController: UIViewController { // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = AppColor.pageBackground setupNavigationBar() setupUI() setupConstraints() bindActions() registerBaseNotifications() } deinit { NotificationCenter.default.removeObserver(self) } // MARK: - Template Methods /// 配置导航栏,子类按需重写。 func setupNavigationBar() {} /// 创建并添加子视图。 func setupUI() {} /// 使用 SnapKit 设置约束。 func setupConstraints() {} /// 绑定 target-action、delegate 等交互。 func bindActions() {} /// Token 失效时回调,子类可重写处理跳转登录等逻辑。 func onSessionDidExpire() {} // MARK: - Loading @MainActor func showLoading() { GlobalLoadingManager.shared.show() } @MainActor func hideLoading() { GlobalLoadingManager.shared.hide() } // MARK: - Toast / Alert func showToast(_ message: String, duration: TimeInterval = 2.0) { guard !message.isEmpty else { return } let container = UIView() container.backgroundColor = UIColor.black.withAlphaComponent(0.8) container.layer.cornerRadius = AppRadius.sm container.clipsToBounds = true container.alpha = 0 let label = UILabel() label.text = message label.textColor = .white label.font = .app(.body) label.textAlignment = .center label.numberOfLines = 0 container.addSubview(label) view.addSubview(container) label.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets(top: 10, left: 16, bottom: 10, right: 16)) } container.snp.makeConstraints { make in make.centerX.equalToSuperview() make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-32) make.leading.greaterThanOrEqualToSuperview().offset(32) make.trailing.lessThanOrEqualToSuperview().offset(-32) } UIView.animate(withDuration: 0.25) { container.alpha = 1 } completion: { _ in UIView.animate(withDuration: 0.25, delay: duration) { container.alpha = 0 } completion: { _ in container.removeFromSuperview() } } } func showError(_ message: String) { showToast(message) } func showAlert( title: String?, message: String?, confirmTitle: String = "确定", confirmHandler: (() -> Void)? = nil ) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: confirmTitle, style: .default) { _ in confirmHandler?() }) present(alert, animated: true) } // MARK: - Navigation func popViewController() { navigationController?.popViewController(animated: true) } /// 沿 presentation 链找到当前最顶层控制器,避免 Sheet 已展示时 present 冲突。 func topMostPresentedViewController() -> UIViewController { var top: UIViewController = self while let presented = top.presentedViewController { top = presented } return top } /// 在当前可见 presentation 链顶端弹出页面。 func presentOnTop( _ viewController: UIViewController, animated: Bool = true, completion: (() -> Void)? = nil ) { topMostPresentedViewController().present(viewController, animated: animated, completion: completion) } /// 全屏预览图片与视频媒体。 func presentMediaPreview(items: [MediaPreviewItem], startIndex: Int) { guard !items.isEmpty else { return } presentOnTop(MediaPreviewViewController(items: items, startIndex: startIndex)) } /// 全屏预览网络图片。 func presentImagePreview(imageURLs: [String], startIndex: Int) { guard !imageURLs.isEmpty else { return } let items = imageURLs.compactMap { MediaPreviewItem.remoteImage($0) } presentMediaPreview(items: items, startIndex: startIndex) } // MARK: - Notifications private func registerBaseNotifications() { NotificationCenter.default.addObserver( self, selector: #selector(handleSessionDidExpireNotification), name: NotificationName.sessionDidExpire, object: nil ) } @objc private func handleSessionDidExpireNotification() { GlobalLoadingManager.shared.hideAll() onSessionDidExpire() } }