Good afternoon everyone. I can’t understand why, when you click a button, the animation runs in another cell?
For example, I click on cell 1, the animation starts on cell 4.
Cells are redrawn after the animation runs. I made an closure for this.
This is link video [https://www.youtube.com/shorts/8qvAIxn61zU][1]
This method for animation icon.
// MARK: - Animation.
private extension CellSeries {
/// This is method, for work the animation.
func startAnimation(_ angle: CGFloat, completionAnimation: @escaping () -> Void) {
UIView.animate(
withDuration: self.style.Time.animationAngle,
delay: .zero,
options: [.curveLinear],
animations: {
self.buttonTitle.transform = CGAffineTransform(rotationAngle: angle)
}, completion: { _ in
/// The animation stops.
completionAnimation()
})
}
}
This is CollectionView.
// MARK: - Protocol.
protocol IMainRepairViewLogic: AnyObject {
/// Отображение устройств MainRepairDevicesModel.ViewModel.Device
func renderDevices(viewModel: [MainRepairDevicesModel.ViewModel.FilterItem])
/// Отображение данный модельного ряда, для устройства MainRepairDevicesModel.ViewModel.Device
func renderSeries(viewModel: [MainRepairSeriesModel.ViewModel.Series])
}
// MARK: - MainRepairViewController
final class MainRepairViewController: FilterCollectionViewController {
// MARK: - Dependencies
var iterator: IMainRepairIterator?
// MARK: - Private properties
private let style = GlobalStyleSettings.self
/// Модель для отображение списка устройств.
private var modelDeviceDisplay: [MainRepairDevicesModel.ViewModel.FilterItem] = []
/// Модель для отображение списка серии для определенного устройства.
private var modelSeriesDisplay: [MainRepairSeriesModel.ViewModel.Series] = []
/// Текущий индекс устройства, например iPad, iPhone, и. т. п.
private var currentIndexDevice: Int = 0
/// Текущий индекс серии например iPhone 5, и. т. п.
private var currentIndexSeries: Int = 0
/// Текущее устройство.
private var currentDevice = MainRepairSeriesModel.RequestSeries.Devices(
deviceID: 0,
parentID: 0,
title: ""
)
// MARK: - Initializator
init(title: String) {
super.init()
headerMenu = HeaderMenuView(
title: "iPhone",
step: GlobalTitles.TitleForHeaderStep.first,
isBack: true,
delegate: self
)
// делегаты FilterCollectionView.
handlerFilterCellDelegate = self
delegateSettingFilterCell = self
// делегаты VerticalCollectionView.
handlerCellDelegate = self
delegateSettingCell = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - Настройки Filter Cell CollectionView.
extension MainRepairViewController: IHandlerFilterCollectionDelegate {
func getFiltersCount() -> Int {
modelDeviceDisplay.count
}
func itemFilterSelect(index: Int) {
self.currentIndexDevice = index
modelDeviceDisplay.enumerated().forEach {
let curentStatus: Status = currentIndexDevice == $0.element.index ? .activity : .notActive
modelDeviceDisplay[$0.element.index].status = curentStatus
}
reloadCollectionFilterView()
fitchDevice()
}
}
extension MainRepairViewController: ISettingFilterCollectionDelegate {
func registerFilterCell(collectionFilterView collectionView: UICollectionView) {
collectionView.register(CellDevices.self, forCellWithReuseIdentifier: CellDevices.reuseIdentifier)
}
func createFilterCell(
collectionFilterView collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath
) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: CellDevices.reuseIdentifier,
for: indexPath
) as? CellDevices {
let currentDevice = modelDeviceDisplay[indexPath.item]
if let image = UIImage(named: currentDevice.imageName) {
cell.reloadData(image: image)
}
cell.changeAction(status: currentDevice.status)
return cell
}
return UICollectionViewCell()
}
func calculateFilterCellSize() -> CGSize {
let size = style.Size.cellDevice
return CGSize(width: size.width, height: size.height)
}
}
// MARK: - Настройки Vertical Cell CollectionView.
extension MainRepairViewController: IHandlerCollectionCell {
func itemSelect(index: Int) { }
func getCellCount(section: Int) -> Int {
modelSeriesDisplay.count
}
}
extension MainRepairViewController: ISettingCollectionCell {
func createCell(collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: CellSeries.reuseIdentifier, for: indexPath
) as? CellSeries {
let series = modelSeriesDisplay[indexPath.item]
cell.reloadData(index: indexPath.item, series: series)
cell.delegate = self
cell.changeVisibility(isHidden: series.isHide)
return cell
}
return UICollectionViewCell()
}
func registerCell(collectionView: UICollectionView) {
collectionView.register(CellSeries.self, forCellWithReuseIdentifier: CellSeries.reuseIdentifier)
}
func calculateCellSize(indexPath: IndexPath) -> CGSize {
// Количество моделей в текущем модельном ряде.
let currentGadget = modelSeriesDisplay[indexPath.item]
let countModel = CGFloat(currentGadget.models.count)
// Отступы ячейки.
let padding = style.Padding.allHorizontal * 2
// Высота ячейки.
let heightCell = style.Size.cellSeries.height
var totalHeight: CGFloat = heightCell
/*
switch currentGadget.isHide {
case .hide:
break
case .notHide, .unknown:
// К высоте ячейки += высота ячейки * на количество моделей.
totalHeight += (totalHeight * countModel)
}
*/
totalHeight += (totalHeight * countModel)
return CGSize(width: view.frame.width - padding, height: totalHeight)
}
}
// MARK: - Render
extension MainRepairViewController: IMainRepairViewLogic {
func renderDevices(viewModel: [MainRepairDevicesModel.ViewModel.FilterItem]) {
for (index, element) in viewModel.enumerated() {
let item = MainRepairDevicesModel.ViewModel.FilterItem(
id: element.id,
index: index,
title: element.title,
imageName: element.imageName,
status: index == currentIndexDevice ? .activity : .notActive
)
self.modelDeviceDisplay.append(item)
}
reloadCollectionFilterView()
fitchDevice()
}
func renderSeries(viewModel: [MainRepairSeriesModel.ViewModel.Series]) {
self.modelSeriesDisplay = viewModel
self.currentIndexSeries = 0
reloadCollectionView()
}
}
// MARK: - Logic.
private extension MainRepairViewController {
func fitchDevice() {
currentDevice = MainRepairSeriesModel.RequestSeries.Devices(from: modelDeviceDisplay[currentIndexDevice])
iterator?.fetchSeriesAndModel(device: currentDevice)
}
}
// MARK: - Navigation.
extension MainRepairViewController: IHeaderMenuDelegate {
func backToView() { }
}
// MARK: - Delegate.
extension MainRepairViewController: ICellSeriesDelegate {
func handlerCell(index: Int) {
self.currentIndexSeries = index
self.modelSeriesDisplay.enumerated().forEach {
if $0.offset != self.currentIndexSeries {
self.modelSeriesDisplay[$0.offset].isHide = true
}
}
self.modelSeriesDisplay[self.currentIndexSeries].isHide.toggle()
reloadCollectionView()
}
func checkItem(index: Int, tag: Int) {
let currentGadget = modelSeriesDisplay[index].models[tag]
let convertModel = MainRepairSeriesModel.RequestSeries.Devices(from: currentGadget)
iterator?.nextScene(device: convertModel)
}
}
This is cell.
import UIKit
protocol ICellSeriesDelegate: AnyObject {
func handlerCell(index: Int)
func checkItem(index: Int, tag: Int)
}
final class ButtonForCellSeries: UIButton {
var isShow: Bool
internal init(isShow: Bool) {
self.isShow = isShow
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
final class CellSeries: UICollectionViewCell {
// MARK: - Dependencies
var delegate: ICellSeriesDelegate!
// MARK: - Public properties
static let reuseIdentifier = "CellSeries.cell"
// MARK: - Private properties
private lazy var labelTitle = createLabel()
private lazy var stackVertical = createStack()
private lazy var buttonTitle = createButton()
private let style = GlobalStyleSettings.self
private var index: Int = 0
private var isShow: Bool = true
// MARK: - Initializator
convenience init(delegate: ICellSeriesDelegate?) {
self.init(frame: CGRect.zero)
self.delegate = delegate
}
override init(frame: CGRect) {
super.init(frame: frame)
addUIView()
setupConfiguration()
setupLayout()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
// MARK: - Public methods
func reloadData(index: Int, series: MainRepairSeriesModel.ViewModel.Series) {
self.index = index
self.isShow = series.isHide
labelTitle.text = series.title
buttonTitle.tag = index
generateContent(models: series.models)
}
func changeVisibility(isHidden: Bool) {
labelTitle.text = "(isHidden)"
if isHidden {
labelTitle.textColor = UIColor.green
self.buttonTitle.transform = CGAffineTransform(rotationAngle: .zero)
#warning("TODO: This is method, don`t work correctly")
// startAnimation(.zero, completionAnimation: {})
} else {
labelTitle.textColor = UIColor.red
self.buttonTitle.transform = CGAffineTransform(rotationAngle: style.Angle.turnIconButton)
}
}
private func generateContent(models: [MainRepairSeriesModel.ViewModel.Model]) {
while stackVertical.arrangedSubviews.count < models.count {
stackVertical.addArrangedSubview(ButtonViewCell(delegate: self))
}
let zipArray = zip(stackVertical.arrangedSubviews, models)
for (index, model) in zipArray.enumerated() {
if let currentButton = model.0 as? ButtonViewCell {
currentButton.reloadData(title: model.1.title, tag: index)
setupButton(currentButton)
}
}
// Прячем лишние кнопки
for (index, currentButton) in stackVertical.arrangedSubviews.enumerated() {
currentButton.isHidden = !(index < models.count)
}
}
}
// MARK: - Add UIView.
private extension CellSeries {
func addUIView() {
let views: [UIView] = [
labelTitle,
buttonTitle,
stackVertical
]
views.forEach(addSubview)
}
}
// MARK: - UI configuration.
private extension CellSeries {
func setupConfiguration() {
buttonTitle.addTarget(self, action: #selector(showList), for: .touchUpInside)
}
}
// MARK: - Add constraint.
private extension CellSeries {
func setupLayout() {
let sizeButton = style.Size.sizeButton
let padding = style.Padding.allHorizontal
let heightCell = style.Size.cellSeries.height
NSLayoutConstraint.activate([
labelTitle.topAnchor.constraint(equalTo: self.topAnchor),
labelTitle.leftAnchor.constraint(equalTo: self.leftAnchor),
labelTitle.rightAnchor.constraint(equalTo: self.rightAnchor),
labelTitle.heightAnchor.constraint(equalToConstant: heightCell),
buttonTitle.widthAnchor.constraint(equalToConstant: sizeButton.width),
buttonTitle.heightAnchor.constraint(equalToConstant: sizeButton.height),
buttonTitle.centerYAnchor.constraint(equalTo: labelTitle.centerYAnchor),
buttonTitle.rightAnchor.constraint(equalTo: labelTitle.rightAnchor, constant: -padding),
stackVertical.topAnchor.constraint(equalTo: labelTitle.bottomAnchor, constant: 0),
stackVertical.leftAnchor.constraint(equalTo: self.leftAnchor),
stackVertical.rightAnchor.constraint(equalTo: self.rightAnchor),
stackVertical.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
func setupButton(_ button: ButtonViewCell) {
NSLayoutConstraint.activate([
button.leftAnchor.constraint(equalTo: self.leftAnchor),
button.rightAnchor.constraint(equalTo: self.rightAnchor)
])
}
func setupSeparator(_ separator: SeparatorViewCell) {
NSLayoutConstraint.activate([
separator.leftAnchor.constraint(equalTo: self.leftAnchor),
separator.rightAnchor.constraint(equalTo: self.rightAnchor),
separator.heightAnchor.constraint(equalToConstant: 2)
])
}
}
// MARK: - UI Fabric.
private extension CellSeries {
func createLabel() -> UILabel {
let label = UILabel()
label.textAlignment = .center
label.textColor = Theme.mainColor
label.font = UIFont.systemFont(ofSize: Styles.Fonts.average, weight: .semibold)
label.layer.borderWidth = 1
label.layer.cornerRadius = Styles.Radius.radiusTextField
label.layer.borderColor = Theme.mainColor.cgColor
label.backgroundColor = Colors.grey
label.clipsToBounds = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}
func createStack() -> UIStackView {
let stack = UIStackView()
stack.axis = .vertical
stack.distribution = .fillEqually
stack.layer.borderColor = Theme.mainColor.cgColor
stack.layer.cornerRadius = style.Radius.radiusCell
stack.layer.borderWidth = style.Border.little
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}
func createButton() -> UIButton {
let button = UIButton()
let image = UIImage(named: "Images/Buttons/leftArrowFill")?.withTintColor(Theme.mainColor)
button.backgroundColor = UIColor.clear
button.setImage(image, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}
}
// MARK: - UI Action.
private extension CellSeries {
@objc func showList() {
startAnimation(style.Angle.turnIconButton, completionAnimation: {
// After the animation is completed, we transfer the data.
self.delegate.handlerCell(index: self.index)
})
}
}
// MARK: - Animation.
private extension CellSeries {
/// This is method, for work the animation.
func startAnimation(_ angle: CGFloat, completionAnimation: @escaping () -> Void) {
UIView.animate(
withDuration: self.style.Time.animationAngle,
delay: .zero,
options: [.curveLinear],
animations: {
self.buttonTitle.transform = CGAffineTransform(rotationAngle: angle)
}, completion: { _ in
/// The animation stops.
completionAnimation()
})
}
}
// MARK: - Delegate.
extension CellSeries: IButtonViewCellDelegate {
func checkButton(tag: Int) {
delegate.checkItem(index: index, tag: tag)
}
}
[1]: https://www.youtube.com/shorts/8qvAIxn61zU
1