cod > Я удалил весь код, связанный с загрузкой, чтобы сохранить рассматриваемое пространство и заменил его на таймер для моделирования прогресса загрузки файла < /p>
Коллекция: < /p>
Код: Выделить всё
struct DownloadItem {
var item: Int
var state: String = ".none"
var progress: Float = 0.0
init(item: Int) { self.item = item }
}
class CarouselController: UIViewController, UICollectionViewDelegate {
var progress = 0.0
var collectionView: UICollectionView!
var dataSource: UICollectionViewDiffableDataSource?
let sections = Bundle.main.decode([Section].self, from: "carouselData.json")
var items = [DownloadItem]()
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
setupScrollView()
setupDownloadItems()
}
func setupScrollView() {
collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .centeredHorizontally, animated: false)
}
func setupDownloadItems() {
let count = dataSource!.snapshot().numberOfItems
for index in 0...count { items.append(DownloadItem(item: index)) }
}
func createDataSource() {
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in
switch self.sections[indexPath.section].identifier {
case "carouselCell":
let cell = self.configure(CarouselCell.self, with: item, for: indexPath)
if indexPath.row < self.items.count {
let item = self.items[indexPath.row]
cell.title.text = "\(String(format: "%.f%%", item.progress * 100))"
}
return cell
default: return self.configure(CarouselCell.self, with: item, for: indexPath)
}
}
}
func configure(_ cellType: T.Type, with item: Item, for indexPath: IndexPath) -> T {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellType.reuseIdentifier, for: indexPath) as? T else { fatalError("\(cellType)") }
cell.configure(with: item)
return cell
}
func reloadData() {
var snapshot = NSDiffableDataSourceSnapshot()
snapshot.appendSections(sections)
for section in sections { snapshot.appendItems(section.item, toSection: section) }
dataSource?.apply(snapshot)
}
func setupCollectionView() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createCompositionalLayout())
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.isScrollEnabled = false
collectionView.delegate = self
collectionView.contentInsetAdjustmentBehavior = .never
view.addSubview(collectionView)
collectionView.register(CarouselCell.self, forCellWithReuseIdentifier: CarouselCell.reuseIdentifier)
createDataSource()
reloadData()
}
func createCompositionalLayout() -> UICollectionViewLayout {
UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupWidth = (layoutEnvironment.container.contentSize.width * 1.05)/3
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(groupWidth),
heightDimension: .absolute(groupWidth))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(
top: (layoutEnvironment.container.contentSize.height/2) - (groupWidth/2),
leading: 0,
bottom: 0,
trailing: 0)
section.interGroupSpacing = 64
section.orthogonalScrollingBehavior = .groupPagingCentered
section.contentInsetsReference = .none
section.visibleItemsInvalidationHandler = { (items, offset, environment) in
items.forEach { item in
let distanceFromCenter = abs((item.frame.midX - offset.x) - environment.container.contentSize.width / 2.0)
let minScale: CGFloat = 0.7
let maxScale: CGFloat = 1.1
let scale = max(maxScale - (distanceFromCenter / environment.container.contentSize.width), minScale)
item.transform = CGAffineTransform(scaleX: scale, y: scale)
}
}
return section
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("-")
_ = Timer.scheduledTimer(withTimeInterval: 0.10, repeats: true) { timer in
guard self.progress
cell < /p>
class CarouselCell: UICollectionViewCell, SelfConfiguringCell {
static let reuseIdentifier: String = "carouselCell"
var topSpaceView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var imageView: UIImageView = {
let image = UIImageView()
image.contentMode = .scaleAspectFill
image.translatesAutoresizingMaskIntoConstraints = false
return image
}()
var titleImageView: UIImageView = {
let image = UIImageView()
image.contentMode = .scaleAspectFill
image.translatesAutoresizingMaskIntoConstraints = false
return image
}()
var textView: UIView = {
let view = UIView()
view.backgroundColor = .blue
view.layer.borderColor = UIColor.red.cgColor
view.layer.borderWidth = 3
view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var title: UILabel = {
let label = UILabel()
label.textColor = .white
label.textAlignment = .center
label.sizeToFit()
label.font = UIFont(name: "Marker Felt", size: 24)
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var progressView: UIProgressView = {
let view = UIProgressView(progressViewStyle: .bar)
view.center = view.center
view.setProgress(0.0, animated: true)
view.trackTintColor = .clear
view.tintColor = .white.withAlphaComponent(0.15)
view.clipsToBounds = true
view.layer.borderColor = UIColor.red.cgColor
view.layer.borderWidth = 3
view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var bottomSpaceView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var stackView: UIView = {
let stack = UIView()
stack.layer.shadowColor = UIColor.black.cgColor
stack.layer.shadowOffset = CGSize.zero
stack.layer.shadowOpacity = 0.6
stack.layer.shadowRadius = 3
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
override var isHighlighted: Bool {
didSet { if oldValue == false && isHighlighted { highlight() }
else if oldValue == true && !isHighlighted { unHighlight() }
}
}
var imageTask: AnyCancellable?
var titleImageTask: AnyCancellable?
override func prepareForReuse() {
super.prepareForReuse()
imageView.image = nil
titleImageView.image = nil
imageTask?.cancel()
}
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
setupConstraints()
}
required init?(coder: NSCoder) {
fatalError("error")
}
func setupViews() {
stackView.addSubview(topSpaceView)
stackView.addSubview(imageView)
stackView.addSubview(titleImageView)
stackView.addSubview(textView)
stackView.addSubview(bottomSpaceView)
contentView.addSubview(stackView)
textView.addSubview(progressView)
textView.addSubview(title)
stackView.sendSubview(toBack: textView)
textView.bringSubview(toFront: title)
}
func setupConstraints() {
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0).isActive = true
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0).isActive = true
topSpaceView.topAnchor.constraint(equalTo: stackView.topAnchor, constant: 0).isActive = true
topSpaceView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 0.07).isActive = true
topSpaceView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
topSpaceView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: topSpaceView.bottomAnchor, constant: 0).isActive = true
imageView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 0.7).isActive = true
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0).isActive = true
imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0).isActive = true
titleImageView.topAnchor.constraint(equalTo: topSpaceView.bottomAnchor, constant: 0).isActive = true
titleImageView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 0.7).isActive = true
titleImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0).isActive = true
titleImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0).isActive = true
textView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 0.16).isActive = true
textView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24).isActive = true
textView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24).isActive = true
textView.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: -6).isActive = true
textView.layer.cornerRadius = contentView.frame.size.height*0.16/2
title.leadingAnchor.constraint(equalTo: textView.leadingAnchor, constant: 0).isActive = true
title.trailingAnchor.constraint(equalTo: textView.trailingAnchor, constant: 0).isActive = true
title.topAnchor.constraint(equalTo: textView.topAnchor, constant: 0).isActive = true
title.bottomAnchor.constraint(equalTo: textView.bottomAnchor, constant: 0).isActive = true
progressView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 0.16).isActive = true
progressView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 24).isActive = true
progressView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: -24).isActive = true
progressView.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: -6).isActive = true
progressView.layer.cornerRadius = contentView.frame.size.height*0.16/2
bottomSpaceView.topAnchor.constraint(equalTo: textView.bottomAnchor, constant: 0).isActive = true
bottomSpaceView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 0.07).isActive = true
bottomSpaceView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
bottomSpaceView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
}
private func animateScale(to scale: CGFloat, duration: TimeInterval) {
UIView.animate( withDuration: duration, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.5, options: [.beginFromCurrentState], animations: {
self.stackView.transform = .init(scaleX: scale, y: scale)
self.textView.transform = .init(scaleX: scale, y: scale)
}, completion: nil)
}
func highlight() {
animateScale(to: 0.9, duration: 0.4)
}
func unHighlight() {
animateScale(to: 1, duration: 0.38)
}
func configure(with item: Item) {
title.text = item.title
textView.backgroundColor = .green
textView.layer.borderColor = UIColor.green.cgColor
progressView.layer.borderColor = UIColor.green.cgColor
titleImageTask = Future() { promise in
UIImage(named: item.titleImage)?.prepareForDisplay(completionHandler: { loadedImage in
promise(Result.success(loadedImage))
})
}
.receive(on: DispatchQueue.main)
.sink { image in
self.titleImageView.image = image
}
imageTask = Future() { promise in
UIImage(named: item.image)?.prepareForDisplay(completionHandler: { loadedImage in
promise(Result.success(loadedImage))
})
}
.receive(on: DispatchQueue.main)
.sink { image in
self.imageView.image = image
}
}
}
//Also tried using `touchesBegan`/`touchesEnded` instead of isHighlighted but the result was the same:
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
highlight()
}
override func touchesEnded(_ touches: Set, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
unHighlight()
}
override func touchesCancelled(_ touches: Set, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
unHighlight()
}
< /code>
exother: < /p>
extension Bundle {
func decode(_ type: T.Type, from file: String) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
let decoder = JSONDecoder()
guard let loaded = try? decoder.decode(T.self, from: data) else {
fatalError("Failed to decode \(file) from bundle.")
}
return loaded
}
}
protocol SelfConfiguringCell {
static var reuseIdentifier: String { get }
func configure(with item: Item)
}
public struct Section: Decodable, Hashable {
let index: Int
let identifier: String
let title: String
let subtitle: String
let item: [Item]
}
public struct Item: Decodable, Hashable {
let index: Int
let title: String
let image: String
let titleImage: String
let backgroundColor: String
let borderColor: String
}
[
{
"index": 1,
"identifier": "carouselCell",
"title": "",
"subtitle": "",
"item": [
{
"index": 1,
"title": "0",
"image": "cover1",
"titleImage": "title1",
"backgroundColor": "5896CC",
"borderColor": "3274AF",
},
{
"index": 2,
"title": "0",
"image": "cover2",
"titleImage": "title2",
"backgroundColor": "895138",
"borderColor": "703A22",
},
{
"index": 3,
"title": "0",
"image": "cover3",
"titleImage": "title3",
"backgroundColor": "804136",
"borderColor": "62322A",
},
{
"index": 4,
"title": "0",
"image": "cover4",
"titleImage": "title4",
"backgroundColor": "3E78A3",
"borderColor": "2A597D",
},
]
},
]
var nameObservation: NSKeyValueObservation?
@objc dynamic var progress = 0.0
func createDataSource() {
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in
switch self.sections[indexPath.section].identifier {
case "carouselCell":
let cell = self.configure(CarouselCell.self, with: item, for: indexPath)
self.nameObservation = self.observe(\.progress, options: .new) { vc, change in
cell.title.text = "\(self.progress)"
}
return cell
default: return self.configure(CarouselCell.self, with: item, for: indexPath)
}
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
_ = Timer.scheduledTimer(withTimeInterval: 0.10, repeats: true) { timer in
guard self.progress
Я получаю прогресс внутри наблюдателя, но текст ячейки не обновляется. Почему?
Подробнее здесь: https://stackoverflow.com/questions/793 ... ction-view