Files
youle_app_ios_v2/ylgamehall/Source/WebView/SplashOverlay.swift
T

170 lines
7.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// SplashOverlay.swift
// ylgamehall
//
// 与 LaunchScreen 同款启动图 + UIProgressView + 状态文字标签的复合 view,
// 大厅 / 子游戏 boot pipeline 期间覆盖在 WebView 上方,加载完成 fade out 后移除。
//
// 内置错误态 UItitle + message + 单按钮):仅大厅启动期使用——拉远端配置 /
// 解压 / IPA 升级失败时调 showError(...),按错误 kind 切换"重试" / "前往设置"
// 按钮语义;子游戏目前不调用,沿用 fatal alert 路径。
//
import UIKit
final class SplashOverlay: UIView {
private let imageView = UIImageView()
private let progressView = UIProgressView(progressViewStyle: .default)
private let label = UILabel()
// 错误态 UI(默认隐藏;showError 时显示,覆盖在 label/progress 区域)
private let errorContainer = UIStackView()
private let errorTitleLabel = UILabel()
private let errorMessageLabel = UILabel()
private let retryButton = UIButton(type: .system)
/// 用户点重试按钮时调(启动期 splash 期间)
var onRetry: (() -> Void)?
init() {
super.init(frame: .zero)
backgroundColor = .black
// 与 LaunchScreen.storyboard 同款 contentMode = scaleAspectFill
// 素材是 1136×64016:9)横屏画面,现代 iPhone 横屏约 19.5:9 比 16:9 更宽,
// scaleAspectFill 高度撑满 + 宽度按比例放大→左右轻微裁切;启动图左右是空白底色,
// logo + 文字主体居中可见,裁切无影响。
// 等价 daoqi msext LaunchImage Asset Catalog 时代「启动图覆盖整屏」的体感,
// 避免 scaleAspectFit 在现代屏左右补大黑边。
imageView.contentMode = .scaleAspectFill
imageView.image = UIImage(named: "SplashImage")
imageView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(imageView)
// SplashImage 底部是白色留白,白字会完全融进去 → 用黑色。
label.textColor = .black
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textAlignment = .center
label.numberOfLines = 1
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
progressView.translatesAutoresizingMaskIntoConstraints = false
progressView.isHidden = true
// 进度条填充黑色 + 轨道用**不透明**浅灰(systemGray5)。
// 之前用 label.withAlphaComponent(0.15) 半透明轨道,会透出 splash 启动图的颜色,
// 黑色填充推进时视觉上像"把彩色压成黑色 → 颜色逐渐加深"。改实色轨道后两侧都是
// 实色,过渡是干脆的"黑色滑过灰色",无加深错觉。
progressView.progressTintColor = .black
progressView.trackTintColor = .systemGray5
addSubview(progressView)
setupErrorContainer()
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: topAnchor),
imageView.bottomAnchor.constraint(equalTo: bottomAnchor),
imageView.leadingAnchor.constraint(equalTo: leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: trailingAnchor),
label.centerXAnchor.constraint(equalTo: centerXAnchor),
label.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -32),
label.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 24),
label.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -24),
progressView.centerXAnchor.constraint(equalTo: centerXAnchor),
progressView.bottomAnchor.constraint(equalTo: label.topAnchor, constant: -12),
progressView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.7),
errorContainer.centerXAnchor.constraint(equalTo: centerXAnchor),
errorContainer.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 60),
errorContainer.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 24),
errorContainer.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -24),
errorContainer.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor, multiplier: 0.7)
])
}
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError("SplashOverlay does not support init(coder:)") }
func update(text: String, progress: Double?) {
label.text = text
if let p = progress {
progressView.isHidden = false
// 用 animated:true 让填充宽度在两个 progress 值之间平滑过渡。
// 下载回调来得快(甚至同一 RunLoop 多次),不动画会出现"看不到中间帧、
// 一瞬间跳到 100%"的视觉。animated:true 是 UIKit 的标准进度条用法。
progressView.setProgress(Float(p), animated: true)
} else {
progressView.isHidden = true
progressView.setProgress(0, animated: false)
}
}
/// 显示错误态。隐藏 imageView / label / progress,背景改为浅色系统背景,
/// 展示 title + message + 单个按钮(点击触发 onRetry)。
/// 按钮文字由调用方按错误 kind 决定("重试" / "前往设置"),同一个按钮承担双语义。
/// 启动图是浅色 + 复杂图案(不适合在上面叠文字),错误态下改纯净浅背景对比清晰。
func showError(title: String, message: String, actionTitle: String = "重试") {
imageView.isHidden = true
label.isHidden = true
progressView.isHidden = true
backgroundColor = .systemBackground
errorTitleLabel.text = title
errorMessageLabel.text = message
var cfg = retryButton.configuration
cfg?.title = actionTitle
retryButton.configuration = cfg
errorContainer.isHidden = false
}
/// 关闭错误态,恢复 splash 默认状态(启动图 + loading 文案;runBootPipeline 重新进入时调)。
func hideError() {
errorContainer.isHidden = true
imageView.isHidden = false
label.isHidden = false
backgroundColor = .black
}
private func setupErrorContainer() {
// 错误态在浅色 systemBackground 上展示,文字用 .label / .secondaryLabel
// 系统语义色(浅模式深、深模式浅),保证两种 iOS appearance 下都看得清。
errorTitleLabel.textColor = .label
errorTitleLabel.font = .systemFont(ofSize: 18, weight: .semibold)
errorTitleLabel.textAlignment = .center
errorTitleLabel.numberOfLines = 1
errorMessageLabel.textColor = .secondaryLabel
errorMessageLabel.font = .systemFont(ofSize: 14)
errorMessageLabel.textAlignment = .center
errorMessageLabel.numberOfLines = 0
var cfg = UIButton.Configuration.filled()
cfg.title = "重试"
cfg.baseBackgroundColor = .black
cfg.baseForegroundColor = .white
cfg.cornerStyle = .medium
cfg.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 24, bottom: 8, trailing: 24)
retryButton.configuration = cfg
retryButton.addAction(UIAction { [weak self] _ in self?.onRetry?() }, for: .touchUpInside)
errorContainer.axis = .vertical
errorContainer.alignment = .center
errorContainer.spacing = 12
errorContainer.isHidden = true
errorContainer.translatesAutoresizingMaskIntoConstraints = false
errorContainer.addArrangedSubview(errorTitleLabel)
errorContainer.addArrangedSubview(errorMessageLabel)
errorContainer.addArrangedSubview(retryButton)
addSubview(errorContainer)
NSLayoutConstraint.activate([
retryButton.heightAnchor.constraint(equalToConstant: 40),
retryButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 120)
])
}
}