我有一个
UICollectionViewCompositionalLayout
。我的应用程序类似于App Store。在真实的应用程序商店中,当我想用应用程序目录滚动“UICollectionViewCell”并且我的手指不小心点击了应用程序图标(操作按钮)时,我可以进一步滚动而没有任何问题。但在我的应用程序中,如果我这样做,我将无法继续滚动,因为按钮操作被触发。怎么解决?
展示问题的视频:
手机号码:
import UIKit
class MediumTableCell: UICollectionViewCell, SelfConfiguringCell {
static let reuseIdentifier: String = "MediumTableCell"
let name = UILabel()
let subtitle = UILabel()
let imageView = UIImageView()
let button = UIButton(type: .custom)
let buyButton = UIButton(type: .custom)
override init(frame: CGRect) {
super.init(frame: frame)
name.font = UIFont.preferredFont(forTextStyle: .headline)
name.textColor = .label
subtitle.font = UIFont.preferredFont(forTextStyle: .subheadline)
subtitle.textColor = .secondaryLabel
buyButton.setImage(UIImage(systemName: "icloud.and.arrow.down"), for: .normal)
button.setContentHuggingPriority(.defaultHigh, for: .horizontal)
buyButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
button.setImage(UIImage(named: "iOS2"), for: .normal)
let innerStackView = UIStackView(arrangedSubviews: [name, subtitle])
innerStackView.axis = .vertical
let outerStackView = UIStackView(arrangedSubviews: [button, innerStackView, buyButton])
outerStackView.translatesAutoresizingMaskIntoConstraints = false
outerStackView.alignment = .center
outerStackView.spacing = 10
contentView.addSubview(outerStackView)
NSLayoutConstraint.activate([
outerStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
outerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
outerStackView.topAnchor.constraint(equalTo: contentView.topAnchor)
])
}
func configure(with app: App) {
name.text = app.name
subtitle.text = app.subheading
}
required init?(coder: NSCoder) {
fatalError("Just… no")
}
}
控制器代码:
class AppsViewController: UIViewController {
let sections = Bundle.main.decode([Section].self, from: "appstore.json")
var collectionView: UICollectionView!
var dataSource: UICollectionViewDiffableDataSource<Section, App>?
override func viewDidLoad() {
super.viewDidLoad()
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createCompositionalLayout())
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.backgroundColor = .systemBackground
view.addSubview(collectionView)
collectionView.register(SectionHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: SectionHeader.reuseIdentifier)
collectionView.register(FeaturedCell.self, forCellWithReuseIdentifier: FeaturedCell.reuseIdentifier)
collectionView.register(MediumTableCell.self, forCellWithReuseIdentifier: MediumTableCell.reuseIdentifier)
collectionView.register(SmallTableCell.self, forCellWithReuseIdentifier: SmallTableCell.reuseIdentifier)
createDataSource()
reloadData()
}
func configure<T: SelfConfiguringCell>(_ cellType: T.Type, with app: App, for indexPath: IndexPath) -> T {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellType.reuseIdentifier, for: indexPath) as? T else {
fatalError("Unable to dequeue \(cellType)")
}
cell.configure(with: app)
return cell
}
func createDataSource() {
dataSource = UICollectionViewDiffableDataSource<Section, App>(collectionView: collectionView) { collectionView, indexPath, app in
switch self.sections[indexPath.section].type {
case "mediumTable":
return self.configure(MediumTableCell.self, with: app, for: indexPath)
case "smallTable":
return self.configure(SmallTableCell.self, with: app, for: indexPath)
default:
return self.configure(FeaturedCell.self, with: app, for: indexPath)
}
}
dataSource?.supplementaryViewProvider = { [weak self] collectionView, kind, indexPath in
guard let sectionHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: SectionHeader.reuseIdentifier, for: indexPath) as? SectionHeader else {
return nil
}
guard let firstApp = self?.dataSource?.itemIdentifier(for: indexPath) else { return nil }
guard let section = self?.dataSource?.snapshot().sectionIdentifier(containingItem: firstApp) else { return nil }
if section.title.isEmpty { return nil }
sectionHeader.title.text = section.title
sectionHeader.subtitle.text = section.subtitle
return sectionHeader
}
}
func reloadData() {
var snapshot = NSDiffableDataSourceSnapshot<Section, App>()
snapshot.appendSections(sections)
for section in sections {
snapshot.appendItems(section.items, toSection: section)
}
dataSource?.apply(snapshot)
}
func createCompositionalLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
let section = self.sections[sectionIndex]
switch section.type {
case "mediumTable":
return self.createMediumTableSection(using: section)
case "smallTable":
return self.createSmallTableSection(using: section)
default:
return self.createFeaturedSection(using: section)
}
}
let config = UICollectionViewCompositionalLayoutConfiguration()
config.interSectionSpacing = 20
layout.configuration = config
return layout
}
func createFeaturedSection(using section: Section) -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
layoutItem.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.93), heightDimension: .estimated(350))
let layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
layoutSection.orthogonalScrollingBehavior = .groupPagingCentered
return layoutSection
}
func createMediumTableSection(using section: Section) -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(0.33))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
layoutItem.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.93), heightDimension: .fractionalWidth(0.55))
let layoutGroup = NSCollectionLayoutGroup.vertical(layoutSize: layoutGroupSize, subitems: [layoutItem])
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
layoutSection.orthogonalScrollingBehavior = .groupPagingCentered
let layoutSectionHeader = createSectionHeader()
layoutSection.boundarySupplementaryItems = [layoutSectionHeader]
return layoutSection
}
func createSmallTableSection(using section: Section) -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(0.2))
let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
layoutItem.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 0)
let layoutGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.93), heightDimension: .estimated(200))
let layoutGroup = NSCollectionLayoutGroup.vertical(layoutSize: layoutGroupSize, subitems: [layoutItem])
let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
let layoutSectionHeader = createSectionHeader()
layoutSection.boundarySupplementaryItems = [layoutSectionHeader]
return layoutSection
}
func createSectionHeader() -> NSCollectionLayoutBoundarySupplementaryItem {
let layoutSectionHeaderSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.93), heightDimension: .estimated(80))
let layoutSectionHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: layoutSectionHeaderSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
return layoutSectionHeader
}
}
更新:
我在 CollevtionView 单元格中添加按钮,以在用户单击它时显示动画。但没有人找到一种方法来解决我上述的问题。我找到了两种方法来解决这个问题。
第一:
我删除按钮并添加此代码以显示动画。此代码修复了上述滚动问题并在单元格触摸时显示动画。
手机号码:
import UIKit
class MediumTableCell: UICollectionViewCell, SelfConfiguringCell {
static let reuseIdentifier: String = "MediumTableCell"
var highlightedScale: CGFloat = 0.9
var scaleDownDuration: TimeInterval = 0.4
var scaleUpDuration: TimeInterval = 0.38
override var isHighlighted: Bool {
didSet {
if oldValue == false && isHighlighted {
highlight() }
else if oldValue == true && !isHighlighted {
unHighlight()
}
}
}
private func animateScale(to scale: CGFloat, duration: TimeInterval) {
UIView.animate( withDuration: duration, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.5, options: [], animations: {
self.stackView.transform = .init(scaleX: scale, y: scale)
self.textView.transform = .init(scaleX: scale, y: scale)
}, completion: nil)
}
func highlight() {
backgroundColor = .white
animateScale(to: highlightedScale, duration: scaleDownDuration)
}
func unHighlight() {
backgroundColor = .white.withAlphaComponent(0.88)
animateScale(to: 1, duration: scaleUpDuration)
isSelected.toggle()
}
}
但在这种情况下,有时当我滚动
CollectionView
并且不单击单元格时,我会看到自发的单元格动画。怎么解决?
第二:
控制器代码:
class AppsViewController: UIViewController, UICollectionViewDelegate {
collectionView.delegate = self
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MediumTableCell.reuseIdentifier, for: indexPath) as! MediumTableCell
UIView.animate( withDuration: duration, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.5, options: [], animations: {
cell.stackView.transform = CGAffineTransform(scaleX: scale, y: scale)
cell.textView.transform = CGAffineTransform(scaleX: scale, y: scale)
}, completion: nil)
let index = indexPath.item
print("\(index)")
}
在这种情况下,当我单击单元格时,我可以看到正确的indexPath.item,但动画不起作用。
此线路不呼叫手机
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MediumTableCell.reuseIdentifier, for: indexPath) as! MediumTableCell
为什么?
所有事件都在单元格内容视图和集合视图上。
当 UIButton 位于 contentView 上方时,它会首先响应,因此 UIButton 单击事件将被调用。
您应该使用 didSelectRow 事件而不是 UIButton 打开应用程序详细信息的弹出窗口。
将 UIButton 更改为 UIImageView (相信会有图像而不是 2 个)或根据要求更改为其他内容。