我已经在UICollectionView
的自定义布局上工作了一段时间。到目前为止,我已经设法在左侧放置一个标题,使其变得粘滞以避免滚动,并且我还制作了一个放大输出算法。
在我的自定义UICollectionView
中,我UIPinchGestureRecognizer
告诉我们是否放大/缩小。
let pinchRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(zoom(_:)))
pinchRecognizer.delegate = self
isUserInteractionEnabled = true
collectionView.addGestureRecognizer(pinchRecognizer)
func zoom(_ gesture: UIPinchGestureRecognizer) {
let scale = gesture.scale
if gesture.state == .ended {
if scale > 1 {
self.lastZoomScale += self.zoomScale
} else if scale < 1 {
self.lastZoomScale -= self.zoomScale
}
}
collectionView.performBatchUpdates({
if scale > 1 {
self.flowLayout.isZoomed = true
self.zoomScale = scale * 4
// If last zoom is equal 0 then user does not zoom it yet.
if self.lastZoomScale == 0 {
// Assign current zoom.
self.flowLayout.previousCellCount = Int(self.zoomScale)
self.flowLayout.numberOfCells = Int(self.zoomScale)
} else {
// User did scroll before and max of zooming scale might be 24 becouse of 24 hours.
if self.lastZoomScale > 24 {
self.lastZoomScale = 24
}
self.flowLayout.previousCellCount = Int(self.lastZoomScale)
self.flowLayout.numberOfCells = Int(self.lastZoomScale)
}
} else if scale < 1 {
self.zoomScale = scale * 4
// User did not zoom in then lets mark it as a default number of cells.
if self.lastZoomScale == 0 {
self.flowLayout.numberOfCells = 4
} else {
let scrollingDifference = self.lastZoomScale - self.zoomScale
if scrollingDifference > 4 {
self.flowLayout.previousCellCount = Int(self.lastZoomScale)
self.flowLayout.isZoomed = false
self.flowLayout.numberOfCells = Int(scrollingDifference)
} else {
self.flowLayout.numberOfCells = 4
}
}
}
}) { _ in
// TODO: Change size of cells while zoomed in/out.
// self.collectionView.performBatchUpdates({
// self.flowLayout.zoomMultiplier = 2
// })
}
}
从第一个陈述中我们可以看到,当状态结束时,增加lastZoomScale
以增加规模,并在我们缩小回原始规模时减少。
稍后UICollectionView
以最少的4个可见单元格为例,最大值为24个。从评论中我们可以看到发生了什么。在完成声明中也有TODO
,但我们对此并不感兴趣。
自定义流程布局的代码时间如下:
import UIKit
class MyCollectionViewFlowLayout: UICollectionViewFlowLayout {
private var cellAttributes = [UICollectionViewLayoutAttributes]()
private var headerAttributes = [UICollectionViewLayoutAttributes]()
private var newIndexPaths = [IndexPath]()
private var removedIndexPaths = [IndexPath]()
private let numberOfSections = 4
private let headerWidth: CGFloat = 75
private let verticalDividerWidth: CGFloat = 1
private let horizontalDividerHeight: CGFloat = 1
fileprivate var contentSize: CGSize?
static let numberOfVisibleCells: Int = 4
var previousCellCount: Int = 0 {
willSet(newValue) {
self.previousCellCount = newValue
}
}
var numberOfCells: Int = 4 {
willSet(newValue) {
self.numberOfCells = newValue
} didSet {
invalidateLayout()
}
}
var zoomMultiplier: CGFloat = 1 {
willSet(newValue) {
self.zoomMultiplier = newValue
} didSet {
invalidateLayout()
}
}
var isZoomed: Bool = false {
willSet(newValue) {
self.isZoomed = newValue
}
}
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
removedIndexPaths.removeAll()
super.prepare(forCollectionViewUpdates: updateItems)
var howManyToDelete = previousCellCount - numberOfCells
while howManyToDelete > 1 {
removedIndexPaths.append(cellAttributes.last!.indexPath)
howManyToDelete -= 1
}
}
override func prepare() {
scrollDirection = .horizontal
minimumLineSpacing = 1
minimumInteritemSpacing = 1
sectionHeadersPinToVisibleBounds = true
if collectionView == nil { return }
cellAttributes.removeAll()
headerAttributes.removeAll()
var cellX: CGFloat = 0
let xOffset = collectionView!.contentOffset.x
// Calculate the height of a row.
let availableHeight = collectionView!.bounds.height - collectionView!.contentInset.top - collectionView!.contentInset.bottom - CGFloat(numberOfSections - 1) * horizontalDividerHeight
let rowHeight = availableHeight / CGFloat(numberOfSections)
// Calculate the width available for time entry cells.
let itemsWidth: CGFloat = collectionView!.bounds.width - collectionView!.contentInset.left - collectionView!.contentInset.right - headerWidth
// For each section.
for i in 0..<numberOfSections {
// Y coordinate of the row.
let rowY = CGFloat(i) * (rowHeight + horizontalDividerHeight)
// Generate and store layout attributes header cell.
let headerIndexPath = IndexPath(item: 0, section: i)
let headerCellAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, with: headerIndexPath)
headerAttributes.append(headerCellAttributes)
headerCellAttributes.frame = CGRect(x: 0, y: rowY, width: headerWidth, height: rowHeight)
// Sticky header while scrolling.
headerCellAttributes.zIndex = 1
headerCellAttributes.frame.origin.x = xOffset
// TODO: Count of the cells for each section.
// Guesing it is going to be 24 = 24hs per day.
// Set the initial X position for cell.
cellX = headerWidth
// For each cell set a width.
for j in 0..<numberOfCells {
//Get the width of the cell.
var cellWidth = CGFloat(Double(itemsWidth) / Double(numberOfCells)) * zoomMultiplier
cellWidth -= verticalDividerWidth
cellX += verticalDividerWidth
// Generate and store layout attributes for the cell
let cellIndexPath = IndexPath(item: j, section: i)
let entryCellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndexPath)
cellAttributes.append(entryCellAttributes)
entryCellAttributes.frame = CGRect(x: cellX, y: rowY, width: cellWidth, height: rowHeight)
cellX += cellWidth
}
}
contentSize = CGSize(width: cellX, height: collectionView!.bounds.height)
}
override var collectionViewContentSize: CGSize {
get {
if contentSize != nil {
return contentSize!
}
return .zero
}
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
// FIXME: Zoom-out crash.
return cellAttributes.first(where: { attributes -> Bool in
return attributes.indexPath == indexPath
})
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return headerAttributes.first(where: { attributes -> Bool in
return attributes.indexPath == indexPath
})
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributes = [UICollectionViewLayoutAttributes]()
for attribute in headerAttributes {
if attribute.frame.intersects(rect) {
attributes.append(attribute)
}
}
for attribute in cellAttributes {
if attribute.frame.intersects(rect) {
attributes.append(attribute)
}
}
print("Inside layout attribs \(cellAttributes.count)")
return attributes
}
override func indexPathsToDeleteForSupplementaryView(ofKind elementKind: String) -> [IndexPath] {
return removedIndexPaths
}
override func finalizeCollectionViewUpdates() {
removedIndexPaths.removeAll()
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
正如您所见,我已设法将评论粘贴到代码中。
我的问题是,无论何时我增加numberOfCells
,它都会像魅力一样,但每当我要减少它们的数量时,它会崩溃,但有例外:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no UICollectionViewLayoutAttributes instance for -layoutAttributesForItemAtIndexPath: <NSIndexPath: 0xc000000001600016> {length = 2, path = 0 - 11}'
我已经删除了这些索引(prepare(forCollectionViewUpdates...)
,稍后会在indexPathsToDeleteForSupplementaryView
内),并且不知道那里发生了什么。
任何人都知道如何解决这个问题?
谢谢!