Anonymous
Как исправить анимационную сцепку в представлении коллекции?
Сообщение
Anonymous » 30 янв 2025, 14:48
У меня есть представление о коллекции. Когда я прикасаюсь к ячейке, анимация из класса Celloction Cellcelcell играет. Когда я нажимаю на ячейку, процесс загрузки файла начинается в методе DidSelectiTemat . Чтобы обновить процесс, я использую код здесь ReloadItem (IndexPath: IndexPath) Проблема в том, что, если при загрузке файла я пытаюсь коснуться ячейки, анимация будет воспроизводиться один раз, например, прыжок или зацеп Это вообще не будет работать при прикосновении, пока файл не будет загружен. Кроме того, во время загрузки, когда ячейка снова нажимается, метод DidSelectiTemat не будет вызван до тех пор, пока файл не будет загружен. Тот факт, что метод DidSelectiTemat не вызывается во время загрузки файлов, хорош. Это предотвращает загрузку одного и того же файла несколько раз. Но проблема в том, что я не понимаю, почему это происходит, потому что я нигде не написал такого кода. Как исправить анимацию и почему selectiTemat не вызывается во время загрузки файла?
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",
},
]
},
]
на основе ответа @Rob [/b]
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
1738237735
Anonymous
У меня есть представление о коллекции. Когда я прикасаюсь к ячейке, анимация из класса Celloction Cellcelcell играет. Когда я нажимаю на ячейку, процесс загрузки файла начинается в методе DidSelectiTemat . Чтобы обновить процесс, я использую код здесь ReloadItem (IndexPath: IndexPath) Проблема в том, что, если при загрузке файла я пытаюсь коснуться ячейки, анимация будет воспроизводиться один раз, например, прыжок или зацеп Это вообще не будет работать при прикосновении, пока файл не будет загружен. Кроме того, во время загрузки, когда ячейка снова нажимается, метод DidSelectiTemat не будет вызван до тех пор, пока файл не будет загружен. Тот факт, что метод DidSelectiTemat не вызывается во время загрузки файлов, хорош. Это предотвращает загрузку одного и того же файла несколько раз. Но проблема в том, что я не понимаю, почему это происходит, потому что я нигде не написал такого кода. Как исправить анимацию и почему selectiTemat не вызывается во время загрузки файла? [b] cod > Я удалил весь код, связанный с загрузкой, чтобы сохранить рассматриваемое пространство и заменил его на таймер для моделирования прогресса загрузки файла < /p> Коллекция: < /p> [code]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", }, ] }, ] [/code] на основе ответа @Rob [/b] 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 Я получаю прогресс внутри наблюдателя, но текст ячейки не обновляется. Почему? Подробнее здесь: [url]https://stackoverflow.com/questions/79397856/how-to-fix-animation-hitch-in-collection-view[/url]