添加 iOS 基础架构与 SPM 依赖
This commit is contained in:
162
suixinkan/Base/BaseViewController.swift
Normal file
162
suixinkan/Base/BaseViewController.swift
Normal file
@ -0,0 +1,162 @@
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
74
suixinkan/Common/NotificationName.swift
Normal file
74
suixinkan/Common/NotificationName.swift
Normal file
@ -0,0 +1,74 @@
|
||||
//
|
||||
// NotificationName.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 工程中所有 `NotificationCenter` 通知名称的统一入口。
|
||||
/// 新增通知时只能在此文件声明,禁止在业务代码中硬编码字符串。
|
||||
enum NotificationName {
|
||||
|
||||
private static let prefix = "com.yuanzhixiang.suixinkan."
|
||||
|
||||
private static func name(_ suffix: String) -> Notification.Name {
|
||||
Notification.Name(prefix + suffix)
|
||||
}
|
||||
|
||||
// MARK: - Session
|
||||
|
||||
/// 用户登录成功
|
||||
static let userDidLogin = name("userDidLogin")
|
||||
|
||||
/// 用户主动退出登录
|
||||
static let userDidLogout = name("userDidLogout")
|
||||
|
||||
/// Token 失效或鉴权失败,需重新登录
|
||||
static let sessionDidExpire = name("sessionDidExpire")
|
||||
|
||||
// MARK: - Scenic
|
||||
|
||||
/// 当前景区切换
|
||||
static let scenicDidChange = name("scenicDidChange")
|
||||
|
||||
// MARK: - User Profile
|
||||
|
||||
/// 用户资料更新(头像、昵称、角色等)
|
||||
static let userProfileDidUpdate = name("userProfileDidUpdate")
|
||||
|
||||
// MARK: - Message
|
||||
|
||||
/// 未读消息数量变化
|
||||
static let unreadMessageCountDidChange = name("unreadMessageCountDidChange")
|
||||
|
||||
// MARK: - Order
|
||||
|
||||
/// 订单状态或列表发生变化
|
||||
static let orderDidUpdate = name("orderDidUpdate")
|
||||
|
||||
// MARK: - Task
|
||||
|
||||
/// 任务状态或列表发生变化
|
||||
static let taskDidUpdate = name("taskDidUpdate")
|
||||
|
||||
// MARK: - Scenic Queue
|
||||
|
||||
/// 排队叫号数据变化
|
||||
static let scenicQueueDidUpdate = name("scenicQueueDidUpdate")
|
||||
|
||||
// MARK: - Voice Call
|
||||
|
||||
/// 语音通话状态变化
|
||||
static let voiceCallStateDidChange = name("voiceCallStateDidChange")
|
||||
}
|
||||
|
||||
/// `Notification.userInfo` 字典键的统一入口。
|
||||
enum NotificationUserInfoKey {
|
||||
|
||||
static let scenicId = "scenicId"
|
||||
static let scenicName = "scenicName"
|
||||
static let orderId = "orderId"
|
||||
static let taskId = "taskId"
|
||||
static let unreadCount = "unreadCount"
|
||||
static let voiceCallState = "voiceCallState"
|
||||
}
|
||||
@ -7,13 +7,11 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class ViewController: UIViewController {
|
||||
class ViewController: BaseViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
// Do any additional setup after loading the view.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user