Files
youle_app_ios_v2/ylgamehall/Source/WebView/SplashOverlay.swift
T
joywayerandClaude Opus 4.7 57d4d93781 SplashOverlay 抽公共组件 + 进度条配色统一
大厅 / 子游戏之前各有一份 private SplashOverlay:lobby 版含错误态 UI(拉远端
配置失败 splash 上提示重试 / 前往设置);sub-game 版只有 image + label +
progressView,配色规则不同(width 0.45 vs 0.7,progressTintColor 不设)。

抽到 Source/WebView/SplashOverlay.swift 共用组件:
- 含完整加载 UI + 错误态 UI(sub-game 不调 showError,但保留兜底)
- 进度条 progressTintColor = .black,trackTintColor = .systemGray5(实色浅灰)
  之前 lobby 用 .systemBlue + label.withAlphaComponent(0.15) 半透明轨道,
  黑色填充推过半透明轨道时视觉上像"颜色逐渐加深";改实色轨道后是干脆的
  "黑滑过灰",无加深错觉
- progressView.setProgress(animated: true) 保留补间,让快下载也能看到平滑增长

WebContainer / SubGame 删除各自的 private SplashOverlay 类定义,引用同一份。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 21:34:25 +08:00

169 lines
7.8 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)
label.textColor = .white
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)
])
}
}