完善样片管理列表、详情与上传页 UI 交互。
对齐 Android 样片模块布局与加载状态,补充共享 UI 组件与图片资源,并增强列表解析容错与单元测试覆盖。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -10,7 +10,7 @@ import UniformTypeIdentifiers
|
||||
import UIKit
|
||||
|
||||
/// 样片相册上传页,对齐 Android `UploadSampleScreen`。
|
||||
final class UploadSampleViewController: BaseViewController {
|
||||
final class UploadSampleViewController: SampleBaseViewController {
|
||||
private enum PickTarget {
|
||||
case material
|
||||
case cover
|
||||
@ -42,6 +42,7 @@ final class UploadSampleViewController: BaseViewController {
|
||||
private let cancelButton = UIButton(type: .system)
|
||||
private let confirmButton = UIButton(type: .system)
|
||||
private let progressOverlay = SampleUploadProgressOverlay()
|
||||
private let dropdownOverlay = SampleAnchoredDropdownView()
|
||||
|
||||
private var pickTarget: PickTarget = .material
|
||||
|
||||
@ -65,16 +66,18 @@ final class UploadSampleViewController: BaseViewController {
|
||||
}
|
||||
|
||||
override func setupNavigationBar() {
|
||||
title = "样片相册"
|
||||
super.setupNavigationBar()
|
||||
setSampleTitle("样片相册")
|
||||
}
|
||||
|
||||
override func setupUI() {
|
||||
view.backgroundColor = UIColor(hex: 0xF4F4F4)
|
||||
super.setupUI()
|
||||
view.backgroundColor = SampleManagementUITokens.pageBackground
|
||||
formCard.backgroundColor = .white
|
||||
formCard.layer.cornerRadius = 12
|
||||
formCard.clipsToBounds = true
|
||||
formStack.axis = .vertical
|
||||
formStack.spacing = 16
|
||||
formStack.spacing = 0
|
||||
|
||||
nameField.placeholder = "请输入相册名称"
|
||||
nameField.font = .systemFont(ofSize: 14)
|
||||
@ -96,6 +99,12 @@ final class UploadSampleViewController: BaseViewController {
|
||||
|
||||
[nameSection, mediaTypeSection, materialSection, coverSection, scenicSection, spotSection, projectSection]
|
||||
.forEach(formStack.addArrangedSubview)
|
||||
formStack.setCustomSpacing(8, after: nameSection)
|
||||
formStack.setCustomSpacing(8, after: mediaTypeSection)
|
||||
formStack.setCustomSpacing(16, after: materialSection)
|
||||
formStack.setCustomSpacing(16, after: coverSection)
|
||||
formStack.setCustomSpacing(8, after: scenicSection)
|
||||
formStack.setCustomSpacing(8, after: spotSection)
|
||||
|
||||
bottomBar.backgroundColor = .white
|
||||
configureBottomButton(cancelButton, title: "取消", backgroundColor: UIColor(hex: 0xEFF6FF), titleColor: AppColor.primary)
|
||||
@ -107,6 +116,7 @@ final class UploadSampleViewController: BaseViewController {
|
||||
view.addSubview(bottomBar)
|
||||
bottomBar.addSubview(cancelButton)
|
||||
bottomBar.addSubview(confirmButton)
|
||||
view.addSubview(dropdownOverlay)
|
||||
view.addSubview(progressOverlay)
|
||||
progressOverlay.isHidden = true
|
||||
}
|
||||
@ -128,7 +138,8 @@ final class UploadSampleViewController: BaseViewController {
|
||||
make.trailing.equalToSuperview().inset(15)
|
||||
}
|
||||
scrollView.snp.makeConstraints { make in
|
||||
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
|
||||
make.top.equalToSuperview()
|
||||
make.leading.trailing.equalTo(view.safeAreaLayoutGuide)
|
||||
make.bottom.equalTo(bottomBar.snp.top)
|
||||
}
|
||||
formCard.snp.makeConstraints { make in
|
||||
@ -149,6 +160,9 @@ final class UploadSampleViewController: BaseViewController {
|
||||
progressOverlay.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
dropdownOverlay.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
override func bindActions() {
|
||||
@ -169,6 +183,7 @@ final class UploadSampleViewController: BaseViewController {
|
||||
mediaTypeButton.addTarget(self, action: #selector(mediaTypeTapped), for: .touchUpInside)
|
||||
mediaGridView.onAdd = { [weak self] in self?.presentPickerSource(target: .material) }
|
||||
mediaGridView.onDelete = { [weak self] index in self?.viewModel.deleteMaterial(at: index) }
|
||||
mediaGridView.onPreview = { [weak self] index in self?.presentMediaPreview(startingAt: index) }
|
||||
coverView.onAdd = { [weak self] in self?.presentPickerSource(target: .cover) }
|
||||
coverView.onDelete = { [weak self] in self?.viewModel.deleteCoverImage() }
|
||||
scenicButton.addTarget(self, action: #selector(scenicTapped), for: .touchUpInside)
|
||||
@ -229,26 +244,25 @@ final class UploadSampleViewController: BaseViewController {
|
||||
}
|
||||
|
||||
@objc private func mediaTypeTapped() {
|
||||
let sheet = UIAlertController(title: "素材类型", message: nil, preferredStyle: .actionSheet)
|
||||
SampleMediaType.allCases.forEach { type in
|
||||
sheet.addAction(UIAlertAction(title: type.title, style: .default) { [weak self] _ in
|
||||
self?.viewModel.selectMediaType(type)
|
||||
})
|
||||
let types = SampleMediaType.allCases
|
||||
dropdownOverlay.show(
|
||||
anchor: mediaTypeButton,
|
||||
options: types.map { .init(title: $0.title, isSelected: $0 == viewModel.mediaType) }
|
||||
) { [weak self] index in
|
||||
guard types.indices.contains(index) else { return }
|
||||
self?.viewModel.selectMediaType(types[index])
|
||||
}
|
||||
sheet.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||||
present(sheet, animated: true)
|
||||
}
|
||||
|
||||
@objc private func scenicTapped() {
|
||||
let sheet = UIAlertController(title: "关联景区", message: nil, preferredStyle: .actionSheet)
|
||||
viewModel.scenicList.forEach { scenic in
|
||||
sheet.addAction(UIAlertAction(title: scenic.name, style: .default) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
Task { await self.viewModel.selectScenic(scenic, api: self.api) }
|
||||
})
|
||||
let scenics = viewModel.scenicList
|
||||
dropdownOverlay.show(
|
||||
anchor: scenicButton,
|
||||
options: scenics.map { .init(title: $0.name, isSelected: $0.id == viewModel.selectedScenic?.id) }
|
||||
) { [weak self] index in
|
||||
guard let self, scenics.indices.contains(index) else { return }
|
||||
Task { await self.viewModel.selectScenic(scenics[index], api: self.api) }
|
||||
}
|
||||
sheet.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||||
present(sheet, animated: true)
|
||||
}
|
||||
|
||||
@objc private func spotTapped() {
|
||||
@ -256,14 +270,14 @@ final class UploadSampleViewController: BaseViewController {
|
||||
showToast("请先选择关联景区")
|
||||
return
|
||||
}
|
||||
let sheet = UIAlertController(title: "关联打卡点", message: nil, preferredStyle: .actionSheet)
|
||||
viewModel.scenicSpotList.forEach { spot in
|
||||
sheet.addAction(UIAlertAction(title: spot.name, style: .default) { [weak self] _ in
|
||||
self?.viewModel.selectScenicSpot(spot)
|
||||
})
|
||||
let spots = viewModel.scenicSpotList
|
||||
dropdownOverlay.show(
|
||||
anchor: spotButton,
|
||||
options: spots.map { .init(title: $0.name, isSelected: $0.id == viewModel.selectedScenicSpot?.id) }
|
||||
) { [weak self] index in
|
||||
guard spots.indices.contains(index) else { return }
|
||||
self?.viewModel.selectScenicSpot(spots[index])
|
||||
}
|
||||
sheet.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||||
present(sheet, animated: true)
|
||||
}
|
||||
|
||||
@objc private func projectTapped() {
|
||||
@ -277,28 +291,36 @@ final class UploadSampleViewController: BaseViewController {
|
||||
await viewModel.loadProjects(refresh: true, api: api)
|
||||
hideLoading()
|
||||
}
|
||||
presentProjectSheet()
|
||||
await MainActor.run { self.presentProjectDropdown() }
|
||||
}
|
||||
}
|
||||
|
||||
private func presentProjectSheet() {
|
||||
let sheet = UIAlertController(title: "关联项目", message: nil, preferredStyle: .actionSheet)
|
||||
viewModel.projectList.forEach { project in
|
||||
sheet.addAction(UIAlertAction(title: project.name, style: .default) { [weak self] _ in
|
||||
self?.viewModel.selectProject(project)
|
||||
})
|
||||
}
|
||||
@MainActor
|
||||
private func presentProjectDropdown() {
|
||||
let projects = viewModel.projectList
|
||||
var options = projects.map { SampleAnchoredDropdownView.Option(title: $0.name, isSelected: $0.id == viewModel.selectedProject?.id) }
|
||||
if viewModel.projectCanLoadMore {
|
||||
sheet.addAction(UIAlertAction(title: "加载更多...", style: .default) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
Task {
|
||||
await self.viewModel.loadProjects(refresh: false, api: self.api)
|
||||
self.presentProjectSheet()
|
||||
}
|
||||
})
|
||||
options.append(.init(title: "加载更多...", isLoadMore: true))
|
||||
}
|
||||
dropdownOverlay.show(
|
||||
anchor: projectButton,
|
||||
options: options,
|
||||
onReachedBottom: { [weak self] in self?.loadMoreProjectsAndRefreshDropdown() }
|
||||
) { [weak self] index in
|
||||
guard let self else { return }
|
||||
if projects.indices.contains(index) {
|
||||
self.viewModel.selectProject(projects[index])
|
||||
} else if self.viewModel.projectCanLoadMore {
|
||||
self.loadMoreProjectsAndRefreshDropdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadMoreProjectsAndRefreshDropdown() {
|
||||
Task {
|
||||
await viewModel.loadProjects(refresh: false, api: api)
|
||||
await MainActor.run { self.presentProjectDropdown() }
|
||||
}
|
||||
sheet.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||||
present(sheet, animated: true)
|
||||
}
|
||||
|
||||
@objc private func cancelTapped() {
|
||||
@ -329,6 +351,19 @@ final class UploadSampleViewController: BaseViewController {
|
||||
present(sheet, animated: true)
|
||||
}
|
||||
|
||||
private func presentMediaPreview(startingAt sourceIndex: Int) {
|
||||
let completed = viewModel.uploadedMediaList.enumerated().filter { !$0.element.isUploading && !$0.element.ossUrl.isEmpty }
|
||||
guard let startIndex = completed.firstIndex(where: { $0.offset == sourceIndex }) else { return }
|
||||
let previewItems = completed.compactMap { entry -> MediaPreviewItem? in
|
||||
switch viewModel.mediaType {
|
||||
case .image: return .remoteImage(entry.element.ossUrl)
|
||||
case .video: return .remoteVideo(entry.element.ossUrl)
|
||||
}
|
||||
}
|
||||
guard !previewItems.isEmpty else { return }
|
||||
presentMediaPreview(items: previewItems, startIndex: min(startIndex, previewItems.count - 1))
|
||||
}
|
||||
|
||||
private func presentPhotoPicker() {
|
||||
var configuration = PHPickerConfiguration(photoLibrary: .shared())
|
||||
if pickTarget == .cover {
|
||||
@ -424,16 +459,28 @@ extension UploadSampleViewController: PHPickerViewControllerDelegate {
|
||||
guard let self, let url else { return }
|
||||
let data = (try? Data(contentsOf: url)) ?? Data()
|
||||
guard !data.isEmpty else { return }
|
||||
let fileName = provider.suggestedName.map { "\($0).mp4" } ?? "sample_video_\(Int(Date().timeIntervalSince1970)).mp4"
|
||||
let fileName = self.resolvedFileName(provider: provider, url: url, fallbackExtension: "mp4")
|
||||
loadedItems[index] = self.makeVideoItem(data: data, fileName: fileName, sourceURL: url)
|
||||
}
|
||||
} else {
|
||||
group.enter()
|
||||
provider.loadObject(ofClass: UIImage.self) { [weak self] object, _ in
|
||||
let imageType = provider.registeredTypeIdentifiers.first {
|
||||
UTType($0)?.conforms(to: .image) == true
|
||||
} ?? UTType.image.identifier
|
||||
provider.loadFileRepresentation(forTypeIdentifier: imageType) { [weak self] url, _ in
|
||||
defer { group.leave() }
|
||||
guard let self, let image = object as? UIImage else { return }
|
||||
let fileName = "sample_image_\(Int(Date().timeIntervalSince1970))_\(index).jpg"
|
||||
loadedItems[index] = self.makeImageItem(image: image, fileName: fileName)
|
||||
guard let self, let url, let data = try? Data(contentsOf: url), let image = UIImage(data: data) else { return }
|
||||
let pathExtension = url.pathExtension
|
||||
let ext = UTType(imageType)?.preferredFilenameExtension ?? (pathExtension.isEmpty ? "jpg" : pathExtension)
|
||||
let fileName = self.resolvedFileName(provider: provider, url: url, fallbackExtension: ext)
|
||||
loadedItems[index] = SampleUploadedMediaItem(
|
||||
data: data,
|
||||
thumbnailData: image.jpegData(compressionQuality: 0.75),
|
||||
fileName: fileName,
|
||||
size: Int64(data.count),
|
||||
width: Int(image.size.width * image.scale),
|
||||
height: Int(image.size.height * image.scale)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -442,6 +489,18 @@ extension UploadSampleViewController: PHPickerViewControllerDelegate {
|
||||
self?.uploadPickedItems(loadedItems.compactMap { $0 })
|
||||
}
|
||||
}
|
||||
|
||||
private func resolvedFileName(provider: NSItemProvider, url: URL, fallbackExtension: String) -> String {
|
||||
let suggested = provider.suggestedName?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
if !suggested.isEmpty {
|
||||
return URL(fileURLWithPath: suggested).pathExtension.isEmpty
|
||||
? "\(suggested).\(fallbackExtension)"
|
||||
: suggested
|
||||
}
|
||||
let urlName = url.lastPathComponent
|
||||
if !urlName.isEmpty, urlName != "." { return urlName }
|
||||
return "sample_\(Int(Date().timeIntervalSince1970)).\(fallbackExtension)"
|
||||
}
|
||||
}
|
||||
|
||||
extension UploadSampleViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
||||
@ -544,6 +603,7 @@ private final class SampleUploadGridView: UIView {
|
||||
private var mediaType: SampleMediaType = .image
|
||||
var onAdd: (() -> Void)?
|
||||
var onDelete: ((Int) -> Void)?
|
||||
var onPreview: ((Int) -> Void)?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
@ -593,6 +653,7 @@ private final class SampleUploadGridView: UIView {
|
||||
if let item {
|
||||
cell.apply(item: item)
|
||||
cell.onDelete = { [weak self] in self?.onDelete?(index) }
|
||||
cell.onPreview = { [weak self] in self?.onPreview?(index) }
|
||||
} else {
|
||||
cell.applyAdd()
|
||||
cell.onAdd = { [weak self] in self?.onAdd?() }
|
||||
@ -605,14 +666,17 @@ private final class SampleUploadGridView: UIView {
|
||||
private final class SampleUploadThumbView: UIControl {
|
||||
private let imageView = UIImageView()
|
||||
private let addCircle = UIView()
|
||||
private let addIcon = UIImageView(image: UIImage(systemName: "plus"))
|
||||
private let addIcon = UIImageView(image: UIImage(named: "material_ic_add"))
|
||||
private let textLabel = UILabel()
|
||||
private let deleteButton = UIButton(type: .system)
|
||||
private let playIcon = UIImageView(image: UIImage(systemName: "play.fill"))
|
||||
private let deleteButton = SampleExpandedTouchButton(type: .system)
|
||||
private let playIcon = UIImageView(image: UIImage(named: "material_ic_play"))
|
||||
private let mediaType: SampleMediaType
|
||||
private let dashedLayer = CAShapeLayer()
|
||||
private var previewEnabled = false
|
||||
|
||||
var onAdd: (() -> Void)?
|
||||
var onDelete: (() -> Void)?
|
||||
var onPreview: (() -> Void)?
|
||||
|
||||
init(mediaType: SampleMediaType) {
|
||||
self.mediaType = mediaType
|
||||
@ -628,7 +692,7 @@ private final class SampleUploadThumbView: UIControl {
|
||||
textLabel.font = .systemFont(ofSize: 12)
|
||||
textLabel.textColor = UIColor(hex: 0x4B5563)
|
||||
textLabel.textAlignment = .center
|
||||
deleteButton.setImage(UIImage(systemName: "xmark"), for: .normal)
|
||||
deleteButton.setImage(UIImage(named: "material_ic_clear"), for: .normal)
|
||||
deleteButton.tintColor = .white
|
||||
deleteButton.backgroundColor = UIColor.black.withAlphaComponent(0.6)
|
||||
deleteButton.layer.cornerRadius = 10
|
||||
@ -665,7 +729,12 @@ private final class SampleUploadThumbView: UIControl {
|
||||
make.center.equalToSuperview()
|
||||
make.size.equalTo(32)
|
||||
}
|
||||
addTarget(self, action: #selector(addTapped), for: .touchUpInside)
|
||||
dashedLayer.strokeColor = UIColor(hex: 0xE5E7EB).cgColor
|
||||
dashedLayer.fillColor = UIColor.clear.cgColor
|
||||
dashedLayer.lineWidth = 1
|
||||
dashedLayer.lineDashPattern = [6, 6]
|
||||
layer.addSublayer(dashedLayer)
|
||||
addTarget(self, action: #selector(cellTapped), for: .touchUpInside)
|
||||
deleteButton.addTarget(self, action: #selector(deleteTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
@ -674,11 +743,18 @@ private final class SampleUploadThumbView: UIControl {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
dashedLayer.frame = bounds
|
||||
dashedLayer.path = UIBezierPath(roundedRect: bounds.insetBy(dx: 0.5, dy: 0.5), cornerRadius: 8).cgPath
|
||||
}
|
||||
|
||||
func applyAdd() {
|
||||
imageView.image = nil
|
||||
backgroundColor = .white
|
||||
layer.borderColor = UIColor(hex: 0xE5E7EB).cgColor
|
||||
layer.borderWidth = 1
|
||||
layer.borderWidth = 0
|
||||
dashedLayer.isHidden = false
|
||||
previewEnabled = false
|
||||
addCircle.isHidden = false
|
||||
textLabel.isHidden = false
|
||||
textLabel.text = mediaType == .image ? "点击上传图片" : "点击上传视频"
|
||||
@ -688,7 +764,8 @@ private final class SampleUploadThumbView: UIControl {
|
||||
}
|
||||
|
||||
func apply(item: SampleUploadedMediaItem) {
|
||||
layer.borderWidth = 0
|
||||
dashedLayer.isHidden = true
|
||||
previewEnabled = !item.ossUrl.isEmpty && !item.isUploading
|
||||
backgroundColor = UIColor(hex: 0xF5F5F5)
|
||||
addCircle.isHidden = true
|
||||
deleteButton.isHidden = item.ossUrl.isEmpty || item.isUploading
|
||||
@ -704,9 +781,12 @@ private final class SampleUploadThumbView: UIControl {
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func addTapped() {
|
||||
guard !addCircle.isHidden else { return }
|
||||
onAdd?()
|
||||
@objc private func cellTapped() {
|
||||
if !addCircle.isHidden {
|
||||
onAdd?()
|
||||
} else if previewEnabled {
|
||||
onPreview?()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func deleteTapped() {
|
||||
@ -718,18 +798,22 @@ private final class SampleUploadThumbView: UIControl {
|
||||
private final class SampleCoverUploadView: UIControl {
|
||||
private let imageView = UIImageView()
|
||||
private let addCircle = UIView()
|
||||
private let addIcon = UIImageView(image: UIImage(systemName: "plus"))
|
||||
private let addIcon = UIImageView(image: UIImage(named: "material_ic_add"))
|
||||
private let titleLabel = UILabel()
|
||||
private let hintLabel = UILabel()
|
||||
private let deleteButton = UIButton(type: .system)
|
||||
private let deleteButton = SampleExpandedTouchButton(type: .system)
|
||||
private let dashedLayer = CAShapeLayer()
|
||||
var onAdd: (() -> Void)?
|
||||
var onDelete: (() -> Void)?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
layer.cornerRadius = 8
|
||||
layer.borderColor = UIColor(hex: 0xE5E7EB).cgColor
|
||||
layer.borderWidth = 1
|
||||
dashedLayer.strokeColor = UIColor(hex: 0xE5E7EB).cgColor
|
||||
dashedLayer.fillColor = UIColor.clear.cgColor
|
||||
dashedLayer.lineWidth = 1
|
||||
dashedLayer.lineDashPattern = [6, 6]
|
||||
layer.addSublayer(dashedLayer)
|
||||
clipsToBounds = true
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.clipsToBounds = true
|
||||
@ -744,7 +828,7 @@ private final class SampleCoverUploadView: UIControl {
|
||||
hintLabel.font = .systemFont(ofSize: 12)
|
||||
hintLabel.textColor = UIColor(hex: 0xB6BECA)
|
||||
hintLabel.textAlignment = .center
|
||||
deleteButton.setImage(UIImage(systemName: "xmark"), for: .normal)
|
||||
deleteButton.setImage(UIImage(named: "material_ic_clear"), for: .normal)
|
||||
deleteButton.tintColor = .white
|
||||
deleteButton.backgroundColor = UIColor.black.withAlphaComponent(0.6)
|
||||
deleteButton.layer.cornerRadius = 12
|
||||
@ -791,13 +875,22 @@ private final class SampleCoverUploadView: UIControl {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
dashedLayer.frame = bounds
|
||||
dashedLayer.path = UIBezierPath(roundedRect: bounds.insetBy(dx: 0.5, dy: 0.5), cornerRadius: 8).cgPath
|
||||
}
|
||||
|
||||
func apply(item: SampleUploadedMediaItem?) {
|
||||
guard let item else {
|
||||
imageView.image = nil
|
||||
addCircle.isHidden = false
|
||||
titleLabel.isHidden = false
|
||||
titleLabel.text = "点击上传图片"
|
||||
titleLabel.numberOfLines = 1
|
||||
hintLabel.isHidden = false
|
||||
deleteButton.isHidden = true
|
||||
dashedLayer.isHidden = false
|
||||
return
|
||||
}
|
||||
if item.isUploading {
|
||||
@ -808,12 +901,14 @@ private final class SampleCoverUploadView: UIControl {
|
||||
titleLabel.numberOfLines = 2
|
||||
hintLabel.isHidden = true
|
||||
deleteButton.isHidden = true
|
||||
dashedLayer.isHidden = true
|
||||
} else {
|
||||
imageView.image = UIImage(data: item.data)
|
||||
addCircle.isHidden = true
|
||||
titleLabel.isHidden = true
|
||||
hintLabel.isHidden = true
|
||||
deleteButton.isHidden = item.ossUrl.isEmpty
|
||||
dashedLayer.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -881,3 +976,12 @@ private final class SampleUploadProgressOverlay: UIView {
|
||||
percentLabel.text = "\(max(0, min(100, state.progress)))%"
|
||||
}
|
||||
}
|
||||
|
||||
/// 在不改变 Android 视觉尺寸的前提下,将小图标按钮触控区域扩展到至少 44pt。
|
||||
private final class SampleExpandedTouchButton: UIButton {
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
let horizontalInset = max(0, (44 - bounds.width) / 2)
|
||||
let verticalInset = max(0, (44 - bounds.height) / 2)
|
||||
return bounds.insetBy(dx: -horizontalInset, dy: -verticalInset).contains(point)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user