Integrate SnapKit and Kingfisher, add BaseViewController and NotificationName, and streamline AGENTS.md with Android-aligned conventions. Co-authored-by: Cursor <cursoragent@cursor.com>
163 lines
4.4 KiB
Swift
163 lines
4.4 KiB
Swift
//
|
||
// BaseViewController.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import SnapKit
|
||
import UIKit
|
||
|
||
/// 业务 ViewController 基类,提供统一的 UI 搭建流程与通用 UI 能力。
|
||
class BaseViewController: UIViewController {
|
||
|
||
private var loadingOverlay: UIView?
|
||
private var loadingIndicator: UIActivityIndicatorView?
|
||
|
||
// MARK: - Lifecycle
|
||
|
||
override func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
view.backgroundColor = .systemBackground
|
||
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
|
||
|
||
func showLoading() {
|
||
if loadingOverlay == nil {
|
||
let overlay = UIView()
|
||
overlay.backgroundColor = UIColor.black.withAlphaComponent(0.1)
|
||
|
||
let indicator = UIActivityIndicatorView(style: .large)
|
||
indicator.hidesWhenStopped = true
|
||
|
||
view.addSubview(overlay)
|
||
overlay.addSubview(indicator)
|
||
|
||
overlay.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview()
|
||
}
|
||
indicator.snp.makeConstraints { make in
|
||
make.center.equalToSuperview()
|
||
}
|
||
|
||
loadingOverlay = overlay
|
||
loadingIndicator = indicator
|
||
}
|
||
|
||
loadingOverlay?.isHidden = false
|
||
loadingIndicator?.startAnimating()
|
||
view.bringSubviewToFront(loadingOverlay!)
|
||
}
|
||
|
||
func hideLoading() {
|
||
loadingIndicator?.stopAnimating()
|
||
loadingOverlay?.isHidden = true
|
||
}
|
||
|
||
// 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 = 8
|
||
container.clipsToBounds = true
|
||
container.alpha = 0
|
||
|
||
let label = UILabel()
|
||
label.text = message
|
||
label.textColor = .white
|
||
label.font = .systemFont(ofSize: 14)
|
||
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)
|
||
}
|
||
|
||
// MARK: - Notifications
|
||
|
||
private func registerBaseNotifications() {
|
||
NotificationCenter.default.addObserver(
|
||
self,
|
||
selector: #selector(handleSessionDidExpireNotification),
|
||
name: NotificationName.sessionDidExpire,
|
||
object: nil
|
||
)
|
||
}
|
||
|
||
@objc private func handleSessionDidExpireNotification() {
|
||
hideLoading()
|
||
onSessionDidExpire()
|
||
}
|
||
}
|