Files
suixinkan_uikit/suixinkan/UI/Live/LiveAlbumViewControllers.swift
汉秋 6336feff27 完善景区排队设置页与全局按钮配置迁移。
重构排队设置与变更日志交互,补充 Overlay 与资源,并将多页面主操作按钮统一到 UIButton Configuration,同步更新相关单元测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 16:21:53 +08:00

701 lines
28 KiB
Swift
Raw Permalink 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.

//
// LiveAlbumViewControllers.swift
// suixinkan
//
import Kingfisher
import PhotosUI
import SnapKit
import UIKit
/// Android `AliveAlbumScreen`
final class LiveAlbumListViewController: BaseViewController, UITableViewDelegate {
private enum Section {
case main
}
private let viewModel = LiveAlbumListViewModel()
private let liveAPI: any LiveServing
private let filterCard = UIView()
private let startButton = UIButton(type: .system)
private let endButton = UIButton(type: .system)
private let tableView = UITableView(frame: .zero, style: .plain)
private let refreshControl = UIRefreshControl()
private let emptyLabel = UILabel()
private let loadingIndicator = UIActivityIndicatorView(style: .medium)
private let bottomBar = UIView()
private let uploadButton = AppButton(title: "上传素材")
private var dataSource: UITableViewDiffableDataSource<Section, LiveAlbumFolder>!
///
@MainActor
init(liveAPI: (any LiveServing)? = nil) {
self.liveAPI = liveAPI ?? NetworkServices.shared.liveAPI
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "直播相册"
}
override func setupUI() {
view.backgroundColor = AppColor.pageBackground
filterCard.backgroundColor = .white
filterCard.layer.cornerRadius = 12
configureDateButton(startButton, title: "开始时间")
configureDateButton(endButton, title: "结束时间")
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.delegate = self
tableView.refreshControl = refreshControl
tableView.register(LiveAlbumFolderCell.self, forCellReuseIdentifier: LiveAlbumFolderCell.reuseIdentifier)
tableView.contentInset = UIEdgeInsets(top: 12, left: 0, bottom: 12, right: 0)
dataSource = UITableViewDiffableDataSource<Section, LiveAlbumFolder>(tableView: tableView) { [weak self] tableView, indexPath, folder in
guard let cell = tableView.dequeueReusableCell(withIdentifier: LiveAlbumFolderCell.reuseIdentifier, for: indexPath) as? LiveAlbumFolderCell else {
return UITableViewCell()
}
cell.apply(folder)
cell.onDelete = { [weak self] in self?.confirmDelete(folder) }
cell.onImageSelected = { [weak self] index in
self?.navigationController?.pushViewController(
LiveAlbumPreviewViewController(folderId: folder.id, startIndex: index),
animated: true
)
}
return cell
}
emptyLabel.text = "暂无素材"
emptyLabel.font = .systemFont(ofSize: 14)
emptyLabel.textColor = UIColor(hex: 0x4B5563)
emptyLabel.isHidden = true
loadingIndicator.hidesWhenStopped = true
loadingIndicator.color = AppColor.primary
bottomBar.backgroundColor = .white
view.addSubview(filterCard)
filterCard.addSubview(startButton)
filterCard.addSubview(endButton)
view.addSubview(tableView)
view.addSubview(emptyLabel)
view.addSubview(loadingIndicator)
view.addSubview(bottomBar)
bottomBar.addSubview(uploadButton)
}
override func setupConstraints() {
filterCard.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide)
make.leading.trailing.equalToSuperview()
make.height.equalTo(59)
}
startButton.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(12)
make.centerY.equalToSuperview()
make.height.equalTo(35)
}
endButton.snp.makeConstraints { make in
make.leading.equalTo(startButton.snp.trailing).offset(8)
make.trailing.equalToSuperview().inset(12)
make.centerY.width.height.equalTo(startButton)
}
bottomBar.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
}
uploadButton.snp.makeConstraints { make in
make.top.equalToSuperview().offset(16)
make.leading.trailing.equalToSuperview().inset(16)
make.height.equalTo(48)
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(16)
}
tableView.snp.makeConstraints { make in
make.top.equalTo(filterCard.snp.bottom)
make.leading.trailing.equalToSuperview()
make.bottom.equalTo(bottomBar.snp.top)
}
emptyLabel.snp.makeConstraints { make in
make.center.equalTo(tableView)
}
loadingIndicator.snp.makeConstraints { make in
make.center.equalTo(tableView)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
viewModel.onShowMessage = { [weak self] message in
Task { @MainActor in self?.showToast(message) }
}
refreshControl.addTarget(self, action: #selector(refreshTriggered), for: .valueChanged)
startButton.addTarget(self, action: #selector(selectStartDate), for: .touchUpInside)
endButton.addTarget(self, action: #selector(selectEndDate), for: .touchUpInside)
uploadButton.addTarget(self, action: #selector(openUpload), for: .touchUpInside)
}
override func viewDidLoad() {
super.viewDidLoad()
Task { await viewModel.loadInitial(api: liveAPI) }
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
Task { await viewModel.refresh(api: liveAPI) }
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let last = tableView.indexPathsForVisibleRows?.map(\.row).max() else { return }
Task { await viewModel.loadMoreIfNeeded(lastVisibleIndex: last, api: liveAPI) }
}
@objc private func refreshTriggered() {
Task { await viewModel.refresh(api: liveAPI) }
}
@objc private func selectStartDate() {
presentDatePicker(initialDate: viewModel.startDate ?? Date()) { [weak self] date in
guard let self else { return }
Task { await self.viewModel.setStartDate(date, api: self.liveAPI) }
}
}
@objc private func selectEndDate() {
presentDatePicker(initialDate: viewModel.endDate ?? Date()) { [weak self] date in
guard let self else { return }
Task { await self.viewModel.setEndDate(date, api: self.liveAPI) }
}
}
@objc private func openUpload() {
navigationController?.pushViewController(LiveAlbumAddViewController(), animated: true)
}
private func applyViewModel() {
let startText = LiveUIFormatter.date(viewModel.startDate)
let endText = LiveUIFormatter.date(viewModel.endDate)
setDateButtonTitle(startButton, text: startText.isEmpty ? "开始时间" : startText)
setDateButtonTitle(endButton, text: endText.isEmpty ? "结束时间" : endText)
if viewModel.isRefreshing {
if !refreshControl.isRefreshing { refreshControl.beginRefreshing() }
} else {
refreshControl.endRefreshing()
}
viewModel.isLoading && viewModel.folders.isEmpty ? loadingIndicator.startAnimating() : loadingIndicator.stopAnimating()
emptyLabel.isHidden = viewModel.isLoading || !viewModel.folders.isEmpty
var snapshot = NSDiffableDataSourceSnapshot<Section, LiveAlbumFolder>()
snapshot.appendSections([.main])
snapshot.appendItems(viewModel.folders)
dataSource.apply(snapshot, animatingDifferences: true)
}
private func confirmDelete(_ folder: LiveAlbumFolder) {
showAlert(title: "确认删除", message: "是否确认删除该相册?删除后将无法恢复。") { [weak self] in
guard let self else { return }
Task { await self.viewModel.deleteFolder(folder, api: self.liveAPI) }
}
}
private func configureDateButton(_ button: UIButton, title: String) {
button.backgroundColor = UIColor(hex: 0xF9FAFB)
button.layer.cornerRadius = 4
button.setTitleColor(AppColor.textPrimary, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 13)
button.setImage(UIImage(systemName: "calendar"), for: .normal)
button.tintColor = AppColor.textPrimary
button.setConfigurationImagePlacement(.leading, padding: 4)
setDateButtonTitle(button, text: title)
}
private func setDateButtonTitle(_ button: UIButton, text: String) {
button.setTitle(text, for: .normal)
}
private func presentDatePicker(initialDate: Date, onConfirm: @escaping (Date) -> Void) {
let alert = UIAlertController(title: nil, message: "\n\n\n\n\n\n\n\n", preferredStyle: .actionSheet)
let picker = UIDatePicker()
picker.datePickerMode = .date
picker.preferredDatePickerStyle = .wheels
picker.locale = Locale(identifier: "zh_CN")
picker.date = initialDate
alert.view.addSubview(picker)
picker.snp.makeConstraints { make in
make.top.equalToSuperview().offset(8)
make.leading.trailing.equalToSuperview().inset(8)
make.height.equalTo(216)
}
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
alert.addAction(UIAlertAction(title: "确定", style: .default) { _ in onConfirm(picker.date) })
present(alert, animated: true)
}
}
/// Cell Android `AliveAlbumFolderView`
private final class LiveAlbumFolderCell: UITableViewCell {
static let reuseIdentifier = "LiveAlbumFolderCell"
var onDelete: (() -> Void)?
var onImageSelected: ((Int) -> Void)?
private let container = UIView()
private let titleLabel = UILabel()
private let deleteButton = UIButton(type: .system)
private let gridStack = UIStackView()
private var imageViews: [LiveCoverImageView] = []
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
onDelete = nil
onImageSelected = nil
imageViews.forEach { $0.removeFromSuperview() }
imageViews.removeAll()
gridStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
}
///
func apply(_ folder: LiveAlbumFolder) {
titleLabel.text = folder.displayName
buildGrid(items: folder.items)
}
private func setup() {
selectionStyle = .none
backgroundColor = .clear
container.backgroundColor = .white
titleLabel.font = .systemFont(ofSize: 16, weight: .medium)
titleLabel.textColor = AppColor.textPrimary
deleteButton.setImage(UIImage(systemName: "trash"), for: .normal)
deleteButton.tintColor = AppColor.danger
gridStack.axis = .vertical
gridStack.spacing = 12
contentView.addSubview(container)
container.addSubview(titleLabel)
container.addSubview(deleteButton)
container.addSubview(gridStack)
container.snp.makeConstraints { make in
make.top.bottom.equalToSuperview()
make.leading.trailing.equalToSuperview()
}
titleLabel.snp.makeConstraints { make in
make.top.leading.equalToSuperview().offset(16)
make.trailing.lessThanOrEqualTo(deleteButton.snp.leading).offset(-8)
}
deleteButton.snp.makeConstraints { make in
make.top.trailing.equalToSuperview().inset(8)
make.size.equalTo(44)
}
gridStack.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(12)
make.leading.trailing.equalToSuperview().inset(12)
make.bottom.equalToSuperview().inset(12)
}
deleteButton.addTarget(self, action: #selector(deleteTapped), for: .touchUpInside)
}
private func buildGrid(items: [LiveAlbumFile]) {
let rows = stride(from: 0, to: items.count, by: 2).map { start in
Array(items[start ..< min(start + 2, items.count)])
}
for (rowIndex, rowItems) in rows.enumerated() {
let rowStack = UIStackView()
rowStack.axis = .horizontal
rowStack.spacing = 12
rowStack.distribution = .fillEqually
gridStack.addArrangedSubview(rowStack)
for (columnIndex, item) in rowItems.enumerated() {
let imageView = LiveCoverImageView(cornerRadius: 12)
imageView.setURL(item.url)
imageView.isUserInteractionEnabled = true
imageView.tag = rowIndex * 2 + columnIndex
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(imageTapped(_:))))
rowStack.addArrangedSubview(imageView)
imageViews.append(imageView)
imageView.snp.makeConstraints { make in
make.height.equalTo(imageView.snp.width).multipliedBy(9.0 / 16.0)
}
}
if rowItems.count == 1 {
rowStack.addArrangedSubview(UIView())
}
}
}
@objc private func deleteTapped() {
onDelete?()
}
@objc private func imageTapped(_ recognizer: UITapGestureRecognizer) {
guard let view = recognizer.view else { return }
onImageSelected?(view.tag)
}
}
/// Android `AliveAlbumAddScreen`
final class LiveAlbumAddViewController: BaseViewController, PHPickerViewControllerDelegate {
private let viewModel = LiveAlbumAddViewModel()
private let liveAPI: any LiveServing
private let uploader: any LiveOSSUploading
private let scrollView = UIScrollView()
private let cardView = UIView()
private let stackView = UIStackView()
private let titleField = UITextField()
private let uploadView = LiveDashedUploadView(title: "点击上传图片", subtitle: "支持 jpg、png、mp4 格式")
private let filesStack = UIStackView()
private let bottomBar = UIView()
private let confirmButton = AppButton(title: "确认")
///
@MainActor
init(
liveAPI: (any LiveServing)? = nil,
uploader: (any LiveOSSUploading)? = nil
) {
self.liveAPI = liveAPI ?? NetworkServices.shared.liveAPI
self.uploader = uploader ?? NetworkServices.shared.ossUploadService
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "上传素材"
}
override func setupUI() {
view.backgroundColor = AppColor.pageBackground
cardView.backgroundColor = .white
cardView.layer.cornerRadius = 12
stackView.axis = .vertical
stackView.spacing = 8
filesStack.axis = .vertical
filesStack.spacing = 12
titleField.placeholder = "请输入作品名称"
titleField.font = .systemFont(ofSize: 14)
titleField.layer.borderWidth = 1
titleField.layer.borderColor = AppColor.border.cgColor
titleField.layer.cornerRadius = 8
titleField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 12, height: 1))
titleField.leftViewMode = .always
bottomBar.backgroundColor = .white
view.addSubview(scrollView)
scrollView.addSubview(cardView)
cardView.addSubview(stackView)
addRequiredTitle("直播标题", to: stackView)
stackView.addArrangedSubview(titleField)
addRequiredTitle("封面图片", suffix: "(单次最多可上传 20 张图片)", to: stackView)
stackView.addArrangedSubview(uploadView)
stackView.addArrangedSubview(filesStack)
view.addSubview(bottomBar)
bottomBar.addSubview(confirmButton)
}
override func setupConstraints() {
bottomBar.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
}
confirmButton.snp.makeConstraints { make in
make.top.equalToSuperview().offset(16)
make.leading.trailing.equalToSuperview().inset(16)
make.height.equalTo(48)
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(16)
}
scrollView.snp.makeConstraints { make in
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
make.bottom.equalTo(bottomBar.snp.top)
}
cardView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
make.width.equalTo(scrollView).offset(-32)
}
stackView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
}
titleField.snp.makeConstraints { make in
make.height.equalTo(44)
}
uploadView.snp.makeConstraints { make in
make.height.equalTo(120)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
viewModel.onShowMessage = { [weak self] message in
Task { @MainActor in self?.showToast(message) }
}
viewModel.onCreateSuccess = { [weak self] in
Task { @MainActor in self?.navigationController?.popViewController(animated: true) }
}
titleField.addTarget(self, action: #selector(titleChanged), for: .editingChanged)
uploadView.addTarget(self, action: #selector(pickImage), for: .touchUpInside)
confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard let result = results.first else { return }
Task {
do {
let media = try await LivePhotoPickerLoader.loadImage(from: result)
await viewModel.addLocalMedia(media, uploader: uploader)
} catch {
showToast(error.localizedDescription)
}
}
}
@objc private func titleChanged() {
viewModel.updateTitle(titleField.text ?? "")
if titleField.text != viewModel.title {
titleField.text = viewModel.title
}
}
@objc private func pickImage() {
var configuration = PHPickerConfiguration(photoLibrary: .shared())
configuration.filter = .images
configuration.selectionLimit = 1
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
@objc private func confirmTapped() {
Task { await viewModel.create(api: liveAPI) }
}
private func applyViewModel() {
viewModel.isLoading ? showLoading() : hideLoading()
filesStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
for (index, file) in viewModel.files.enumerated() {
let view = LiveAlbumUploadedFileView(file: file)
view.onDelete = { [weak self] in self?.confirmDeleteLocalFile(at: index) }
filesStack.addArrangedSubview(view)
view.snp.makeConstraints { make in
make.height.equalTo(view.snp.width).multipliedBy(9.0 / 16.0)
}
}
}
private func confirmDeleteLocalFile(at index: Int) {
showAlert(title: "确认删除", message: "是否确认删除该相册?删除后将无法恢复。") { [weak self] in
self?.viewModel.deleteLocalFile(at: index)
}
}
private func addRequiredTitle(_ title: String, suffix: String? = nil, to stack: UIStackView) {
let label = UILabel()
let text = NSMutableAttributedString(
string: "\(title) *",
attributes: [.font: UIFont.systemFont(ofSize: 14, weight: .medium), .foregroundColor: AppColor.textPrimary]
)
text.addAttribute(.foregroundColor, value: UIColor.red, range: NSRange(location: title.count + 1, length: 1))
if let suffix {
text.append(NSAttributedString(string: suffix, attributes: [.font: UIFont.systemFont(ofSize: 14), .foregroundColor: AppColor.textPrimary]))
}
label.attributedText = text
stack.addArrangedSubview(label)
}
}
///
private final class LiveAlbumUploadedFileView: UIView {
var onDelete: (() -> Void)?
private let imageView = LiveCoverImageView()
private let deleteButton = UIButton(type: .system)
///
init(file: LiveAlbumFile) {
super.init(frame: .zero)
imageView.setURL(file.url)
deleteButton.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal)
deleteButton.tintColor = .gray
addSubview(imageView)
addSubview(deleteButton)
imageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
deleteButton.snp.makeConstraints { make in
make.top.trailing.equalToSuperview().inset(4)
make.size.equalTo(28)
}
deleteButton.addTarget(self, action: #selector(deleteTapped), for: .touchUpInside)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func deleteTapped() {
onDelete?()
}
}
/// Android `AliveAlbumPreviewScreen`
final class LiveAlbumPreviewViewController: BaseViewController, UIScrollViewDelegate {
private let viewModel: LiveAlbumPreviewViewModel
private let liveAPI: any LiveServing
private let pagingScrollView = UIScrollView()
private let pageControl = UIPageControl()
private let emptyLabel = UILabel()
private let bottomBar = UIView()
private let deleteButton = LiveActionButton(title: "删除素材")
///
@MainActor
init(folderId: Int, startIndex: Int, liveAPI: (any LiveServing)? = nil) {
viewModel = LiveAlbumPreviewViewModel(folderId: folderId, startIndex: startIndex)
self.liveAPI = liveAPI ?? NetworkServices.shared.liveAPI
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "预览"
navigationController?.navigationBar.barTintColor = .black
navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white]
navigationController?.navigationBar.tintColor = .white
}
override func setupUI() {
view.backgroundColor = .black
pagingScrollView.isPagingEnabled = true
pagingScrollView.showsHorizontalScrollIndicator = false
pagingScrollView.delegate = self
pageControl.currentPageIndicatorTintColor = .white
pageControl.pageIndicatorTintColor = UIColor.white.withAlphaComponent(0.4)
emptyLabel.text = "没有可预览的图片"
emptyLabel.textColor = .white
emptyLabel.font = .systemFont(ofSize: 16)
emptyLabel.textAlignment = .center
bottomBar.backgroundColor = .black
deleteButton.buttonStyle = .danger
view.addSubview(pagingScrollView)
view.addSubview(pageControl)
view.addSubview(emptyLabel)
view.addSubview(bottomBar)
bottomBar.addSubview(deleteButton)
}
override func setupConstraints() {
bottomBar.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
}
deleteButton.snp.makeConstraints { make in
make.top.equalToSuperview().offset(16)
make.leading.trailing.equalToSuperview().inset(16)
make.height.equalTo(48)
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(16)
}
pagingScrollView.snp.makeConstraints { make in
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
make.bottom.equalTo(bottomBar.snp.top)
}
pageControl.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalTo(pagingScrollView).inset(8)
}
emptyLabel.snp.makeConstraints { make in
make.center.equalTo(pagingScrollView)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
viewModel.onShowMessage = { [weak self] message in
Task { @MainActor in self?.showToast(message) }
}
deleteButton.addTarget(self, action: #selector(confirmDelete), for: .touchUpInside)
}
override func viewDidLoad() {
super.viewDidLoad()
Task { await viewModel.load(api: liveAPI) }
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.barTintColor = nil
navigationController?.navigationBar.titleTextAttributes = nil
navigationController?.navigationBar.tintColor = nil
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
layoutPages()
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let index = Int(round(scrollView.contentOffset.x / max(scrollView.bounds.width, 1)))
viewModel.updateCurrentIndex(index)
}
private func applyViewModel() {
viewModel.isLoading && viewModel.files.isEmpty ? showLoading() : hideLoading()
pageControl.numberOfPages = viewModel.files.count
pageControl.currentPage = viewModel.currentIndex
emptyLabel.isHidden = !viewModel.files.isEmpty
deleteButton.isEnabled = !viewModel.files.isEmpty
buildPages()
}
private func buildPages() {
pagingScrollView.subviews.forEach { $0.removeFromSuperview() }
for (index, file) in viewModel.files.enumerated() {
let imageView = LiveCoverImageView(cornerRadius: 0)
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = .black
imageView.setURL(file.url)
imageView.tag = index
pagingScrollView.addSubview(imageView)
}
layoutPages()
}
private func layoutPages() {
let size = pagingScrollView.bounds.size
guard size.width > 0 else { return }
for (index, view) in pagingScrollView.subviews.enumerated() {
view.frame = CGRect(x: CGFloat(index) * size.width, y: 0, width: size.width, height: size.height)
}
pagingScrollView.contentSize = CGSize(width: size.width * CGFloat(viewModel.files.count), height: size.height)
pagingScrollView.setContentOffset(CGPoint(x: CGFloat(viewModel.currentIndex) * size.width, y: 0), animated: false)
}
@objc private func confirmDelete() {
showAlert(title: "确认删除", message: "是否确认删除该素材?删除后将无法恢复。") { [weak self] in
guard let self else { return }
Task { await self.viewModel.deleteCurrentFile(api: self.liveAPI) }
}
}
}