Calculations of the 80% of screen and 20% in UICollectionView - ios

I write function that return size of the cell for the UICollectionView.
fileprivate func getCellSize(with row: Int) -> CGSize {
let percentage: CGFloat = row == 0 ? 0.8 : 0.2
let width = self.collectionView?.frame.width
let height = self.collectionView?.frame.height
let expectedWidth = width! * percentage
let expectedHeight = height! * 0.5
return CGSize(width: expectedWidth, height: expectedHeight)
}
This function is working fine but it has small issue that is connected with rounding it seams to me. Because layout that I receive is not fully covered with cells as it is expected.
The result of the function for the iPhone 6 emulator is following:
0: ROW = (533.60000000000002,187.5)
1: ROW = (133.40000000000001,187.5)
Actual result:
Expected result:

How about
if let frameWidth = self.collectionView?.frame.width {
let row0Width = Int(Double(frameWidth) * 0.8)
let otherWidth = frameWidth - row0Width
let expectedWidth = row == 0 ? row0Width : otherWidth
// ...
}
to avoid rounding issues?

Related

How can I apply a CGAffineTransform to a UICollectionViewCell while the UICollectionView is scrolling

I am creating a kind of horizontally scrolling menu with multiple items that the user can scroll through.(see picture at the bottom for a preview of what I mean)
The UICollectionView offset already always centers on one of its items. What I want to do is that when an item is the next one to approach the center, I want to apply a transformation to make it larger. This is the code that I'm using to achieve this (the logic doesn't handle animating out of the center or scrolling the other direction yet):
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let scrolledCollectionView = scrollView as? UICollectionView,
let flowLayout = scrolledCollectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
let itemWidth = flowLayout.itemSize.width
let collectionViewCenter = collectionView.bounds.width * 0.5 + scrolledCollectionView.contentOffset.x
let itemToEnlarge = Int((scrolledCollectionView.contentOffset.x + (itemWidth * 0.5)) / (itemWidth + flowLayout.minimumInteritemSpacing))
let itemEnlargeIndexpath = IndexPath(row: itemToEnlarge, section: 0)
guard let cellToAnimate = collectionView.cellForItem(at: itemEnlargeIndexpath) else { return }
let diff = cellToAnimate.center.x - collectionViewCenter
var transformationVolume: CGFloat = 1
if diff == 0 {
transformationVolume += 0.2
} else {
transformationVolume += (0.2 / diff)
}
DispatchQueue.main.async {
cellToAnimate.transform = CGAffineTransform(scaleX: transformationVolume, y: transformationVolume)
}
}
The problem that I'm having is that the transformation is only applied once the collectionview has stopped scrolling. Does anyone know if there's a way to apply the transformation dynamically? So that if you scroll the item towards the center little by little, the transformation is applied incrementally.

Resizing UICollectionView with Custom Layout after deleting cells

I downloaded this Pintrest-esc Custom Layout for my CollectionView from : https://www.raywenderlich.com/164608/uicollectionview-custom-layout-tutorial-pinterest-2 (modified it a little bit to do with cache code)
So the layout works fine, i got it set up so when you scroll to the bottom, i make another API call, create and populate additional cells.
Problem: If a User scrolls down and loads some more cells (lets say 20 for example) and then uses the "Search Feature" i have implemented at the top, it will then filter some of the data via price from MinRange - MaxRange leaving you with (lets say 5 results), thus deleting some cells and repopulating those. BUT - All that space and scrollable space that existed for the first 20 cells still exists as blank white space.... How can i remove this space ? and why can it resize when adding more cells, but not when removing them ?
Code of my API Call:
DispatchQueue.global(qos: .background).async {
WebserviceController.GetSearchPage(parameters: params) { (success, data) in
if success
{
if let products = data!["MESSAGE"] as? [[String : AnyObject]]
{
self.productList = products
}
}
DispatchQueue.main.async(execute: { () -> Void in
self.pintrestCollectionView.reloadData()
self.pintrestCollectionView.collectionViewLayout.invalidateLayout()
self.pintrestCollectionView.layoutSubviews()
})
}
}
Code Of Custom Layout Class & protocol:
protocol PinterestLayoutDelegate: class {
func collectionView(_ collectionView:UICollectionView, heightForPhotoAtIndexPath indexPath:IndexPath) -> CGFloat
}
class PintrestLayout: UICollectionViewFlowLayout {
// 1
weak var delegate : PinterestLayoutDelegate!
// 2
fileprivate var numberOfColumns = 2
fileprivate var cellPadding: CGFloat = 10
// 3
fileprivate var cache = [UICollectionViewLayoutAttributes]()
// 4
fileprivate var contentHeight: CGFloat = 0
fileprivate var contentWidth: CGFloat {
guard let collectionView = collectionView else {
return 0
}
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
// 5
override var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func prepare() {
// 1
//You only calculate the layout attributes if cache is empty and the collection view exists.
/*guard cache.isEmpty == true, let collectionView = collectionView else {
return
}*/
cache.removeAll()
guard cache.isEmpty == true || cache.isEmpty == false, let collectionView = collectionView else {
return
}
// 2
//This declares and fills the xOffset array with the x-coordinate for every column based on the column widths. The yOffset array tracks the y-position for every column.
let columnWidth = contentWidth / CGFloat(numberOfColumns)
var xOffset = [CGFloat]()
for column in 0 ..< numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth)
}
var column = 0
var yOffset = [CGFloat](repeating: 0, count: numberOfColumns)
// 3
//This loops through all the items in the first section, as this particular layout has only one section.
for item in 0 ..< collectionView.numberOfItems(inSection: 0) {
if collectionView.numberOfItems(inSection: 0) < 3
{
collectionView.isScrollEnabled = false
} else {
collectionView.isScrollEnabled = true
}
let indexPath = IndexPath(item: item, section: 0)
// 4
//This is where you perform the frame calculation. width is the previously calculated cellWidth, with the padding between cells removed.
let photoHeight = delegate.collectionView(collectionView, heightForPhotoAtIndexPath: indexPath)
let height = cellPadding * 2 + photoHeight
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
let insetFrame = frame.insetBy(dx: 7, dy: 4)
collectionView.contentInset = UIEdgeInsets.init(top: 15, left: 12, bottom: 0, right: 12)
// 5
//This creates an instance of UICollectionViewLayoutAttribute, sets its frame using insetFrame and appends the attributes to cache.
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
// 6
//This expands contentHeight to account for the frame of the newly calculated item.
contentHeight = max(contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + height
column = column < (numberOfColumns - 1) ? (column + 1) : 0
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
{
var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()
// Loop through the cache and look for items in the rect
for attributes in cache {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
}
return visibleLayoutAttributes
}
FYI: In MainStoryboard, i disabled "Adjust Scroll View Insets" as many of other threads and forums have suggested...
I'm guessing you've already found the answer, but I'm posting here for the next person since I ran into this issue.
The trick here is contentHeight actually never gets smaller.
contentHeight = max(contentHeight, frame.maxY)
This is a nondecreasing function.
To fix it, just set contentHeight to 0 if prepare is called:
guard cache.isEmpty == true || cache.isEmpty == false, let
collectionView = collectionView else {
return
}
contentHeight = 0

UICollectionViewFlowLayout Cell with custom anchorPoint reused too soon

I've subclassed a UICollectionViewFlowLayout to achieve a small scaling effect during horizontal scroll. Therefore I had to subclass a UICollectionViewCell as well as to change the layer.anchorPoint of the cell (my scaling is from the bottom left of the cell rather than from the default center). Now all fine and well except the fact that when I am scrolling horizontally , my cell is reused too soon (I still can see the half cell when it suddenly disappear ).
I have the feeling that collection view still bases its calculations for reusing the cell on the anchor point positioned in the center of the cell...
However , this is my collection view . You can see how the item getting bigger as it reaches the left side of the collection view. This is the scaling I wanted.
Now here I am scrolling to the left. You can see how the right item became bigger and the left is getting out of the screen.
And here you see that the left item didn't get off the screen but already dissapeared. Only the right item remeained visible :/
So what I want is , to make the left item disappear only when it's right boundaries reaching the very left of the screen.Simply saying , to dissapear only when I don't see it anymore.
And here is my code:
class SongsCollectionViewCell : UICollectionViewCell {
#IBOutlet weak var imgAlbumCover: UIImageView!
override func applyLayoutAttributes(layoutAttributes: UICollectionViewLayoutAttributes) {
super.applyLayoutAttributes(layoutAttributes)
//we must change the anchor point for propper cells positioning and scaling
self.layer.anchorPoint.x = 0
self.layer.anchorPoint.y = 1
}
}
Here is the layout itself :
class SongsCollectionViewLayout : UICollectionViewFlowLayout {
override func prepareLayout() {
collectionView?.decelerationRate = UIScrollViewDecelerationRateFast
self.scrollDirection = UICollectionViewScrollDirection.Horizontal;
//size of the viewport
let size:CGSize = self.collectionView!.frame.size;
let itemWidth:CGFloat = size.width * 0.7//0.7//3.0;
self.itemSize = CGSizeMake(itemWidth, itemWidth);
self.sectionInset = UIEdgeInsetsMake(0,0,0,0);
self.minimumLineSpacing = 0
self.minimumInteritemSpacing = 0
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes:[UICollectionViewLayoutAttributes] = super.layoutAttributesForElementsInRect(rect)!
var visibleRect:CGRect = CGRect()
visibleRect.origin = self.collectionView!.contentOffset;
visibleRect.size = self.collectionView!.bounds.size;
for layoutAttributes in attributes {
if (CGRectIntersectsRect(layoutAttributes.frame, rect)) {
//we must align items to the bottom of the collection view on y axis
let frameHeight = self.collectionView!.bounds.size.height
layoutAttributes.center.y = frameHeight
layoutAttributes.center.x = layoutAttributes.center.x - self.itemSize.width/2
//find where ite, left x is
let itemLeftX:CGFloat = layoutAttributes.center.x
//distance of the item from the left of the viewport
let distanceFromTheLeft:CGFloat = itemLeftX - CGRectGetMinX(visibleRect)
let normalizedDistanceFromTheLeft = abs(distanceFromTheLeft) / self.collectionView!.frame.size.width
//item that is closer to the left is most visible one
layoutAttributes.alpha = 1 - normalizedDistanceFromTheLeft
layoutAttributes.zIndex = abs(Int(layoutAttributes.alpha)) * 10;
//scale items
let scale = min(layoutAttributes.alpha + 0.5, 1)
layoutAttributes.transform = CGAffineTransformMakeScale(scale, scale)
}
}
return attributes;
}
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
// Snap cells to centre
var newOffset = CGPoint()
let layout = collectionView!.collectionViewLayout as! UICollectionViewFlowLayout
let width = layout.itemSize.width + layout.minimumLineSpacing
var offset = proposedContentOffset.x + collectionView!.contentInset.left
if velocity.x > 0 {
//ceil returns next biggest number
offset = width * ceil(offset / width)
} else if velocity.x == 0 { //6
//rounds the argument
offset = width * round(offset / width)
} else if velocity.x < 0 { //7
//removes decimal part of argument
offset = width * floor(offset / width)
}
newOffset.x = offset - collectionView!.contentInset.left
newOffset.y = proposedContentOffset.y //y will always be the same...
return newOffset
}
}
Answering my own question.
So as I suspected , the layout was taking an old center into account that is why I had to correct the center of the cell right after changing the anchor point . So my custom cell now looks like this :
class SongsCollectionViewCell : UICollectionViewCell {
#IBOutlet weak var imgAlbumCover: UIImageView!
override func prepareForReuse() {
imgAlbumCover.hnk_cancelSetImage()
imgAlbumCover.image = nil
}
override func applyLayoutAttributes(layoutAttributes: UICollectionViewLayoutAttributes) {
super.applyLayoutAttributes(layoutAttributes)
//we must change the anchor point for propper cells positioning and scaling
self.layer.anchorPoint.x = 0
self.layer.anchorPoint.y = 1
//we need to adjust a center now
self.center.x = self.center.x - layoutAttributes.size.width/2
}
}
Hope it helps someone

Visible lag while scrolling two way scrolling UICollectionView with large number of cells (250,000 or more)

I am subclassing UICollectionViewFlowLayout in order to get two way scrolling in a UICollectionView. The scrolling works fine for smaller number of row and section count (100-200 rows and sections) but there is visible lag while scrolling when I increase row and section count over 500 i.e 250,000 or more cells in the UICollectionView. I have traced the source of the lag to be for in loop in the layoutAttributesForElementsInRect. I am using a Dictionary to hold UICollectionViewLayoutAttributes of each cell to avoid recalculating it and looping through it to return attributes of cells from layoutAttributesForElementsInRect
import UIKit
class LuckGameCollectionViewLayout: UICollectionViewFlowLayout {
// Used for calculating each cells CGRect on screen.
// CGRect will define the Origin and Size of the cell.
let CELL_HEIGHT = 70.0
let CELL_WIDTH = 70.0
// Dictionary to hold the UICollectionViewLayoutAttributes for
// each cell. The layout attribtues will define the cell's size
// and position (x, y, and z index). I have found this process
// to be one of the heavier parts of the layout. I recommend
// holding onto this data after it has been calculated in either
// a dictionary or data store of some kind for a smooth performance.
var cellAttrsDictionary = Dictionary<NSIndexPath, UICollectionViewLayoutAttributes>()
// Defines the size of the area the user can move around in
// within the collection view.
var contentSize = CGSize.zero
override func collectionViewContentSize() -> CGSize {
return self.contentSize
}
override func prepareLayout() {
// Cycle through each section of the data source.
if collectionView?.numberOfSections() > 0 {
for section in 0...collectionView!.numberOfSections()-1 {
// Cycle through each item in the section.
if collectionView?.numberOfItemsInSection(section) > 0 {
for item in 0...collectionView!.numberOfItemsInSection(section)-1 {
// Build the UICollectionVieLayoutAttributes for the cell.
let cellIndex = NSIndexPath(forItem: item, inSection: section)
let xPos = Double(item) * CELL_WIDTH
let yPos = Double(section) * CELL_HEIGHT
let cellAttributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: cellIndex)
cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT)
// Save the attributes.
cellAttrsDictionary[cellIndex] = cellAttributes
}
}
}
}
// Update content size.
let contentWidth = Double(collectionView!.numberOfItemsInSection(0)) * CELL_WIDTH
let contentHeight = Double(collectionView!.numberOfSections()) * CELL_HEIGHT
self.contentSize = CGSize(width: contentWidth, height: contentHeight)
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// Create an array to hold all elements found in our current view.
var attributesInRect = [UICollectionViewLayoutAttributes]()
// Check each element to see if it should be returned.
for (_,cellAttributes) in cellAttrsDictionary {
if CGRectIntersectsRect(rect, cellAttributes.frame) {
attributesInRect.append(cellAttributes)
}
}
// Return list of elements.
return attributesInRect
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
return cellAttrsDictionary[indexPath]!
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return false
}
}
Edit:
Following are the changes that I have come up with in the layoutAttributesForElementsInRect method.
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// Create an array to hold all elements found in our current view.
var attributesInRect = [UICollectionViewLayoutAttributes]()
let xOffSet = self.collectionView?.contentOffset.x
let yOffSet = self.collectionView?.contentOffset.y
let totalColumnCount = self.collectionView?.numberOfSections()
let totalRowCount = self.collectionView?.numberOfItemsInSection(0)
let startRow = Int(Double(xOffSet!)/CELL_WIDTH) - 10 //include 10 rows towards left
let endRow = Int(Double(xOffSet!)/CELL_WIDTH + Double(Utils.getScreenWidth())/CELL_WIDTH) + 10 //include 10 rows towards right
let startCol = Int(Double(yOffSet!)/CELL_HEIGHT) - 10 //include 10 rows towards top
let endCol = Int(Double(yOffSet!)/CELL_HEIGHT + Double(Utils.getScreenHeight())/CELL_HEIGHT) + 10 //include 10 rows towards bottom
for(var i = startRow ; i <= endRow; i = i + 1){
for (var j = startCol ; j <= endCol; j = j + 1){
if (i < 0 || i > (totalRowCount! - 1) || j < 0 || j > (totalColumnCount! - 1)){
continue
}
let indexPath: NSIndexPath = NSIndexPath(forRow: i, inSection: j)
attributesInRect.append(cellAttrsDictionary[indexPath]!)
}
}
// Return list of elements.
return attributesInRect
}
I have calculated the offset of the collectionView and used it to calculate the cells that will be visible on screen(using height/width of each cell). I had to add extra cells on each side so that when user scrolls there are no missing cells. I have tested this and the performance is fine.
By taking advantage of the layoutAttributesForElementsInRect(rect: CGRect) with your known cell size you don't need to cache your attributes and could just calculate them for a given rect as the collectionView requests them. You would still need to check for the boundary cases of 0 and the maximum section/row counts to avoid calculating unneeded or invalid attributes but that can be easily done in where clauses around the loops. Here's a working example that I've tested with 1000 sections x 1000 rows and it works just fine without lagging on the device:
Edit: I've added the biggerRect so that attributes can be pre-calculated for before the scrolling gets there. From your edit it looks like you're still caching the attributes which I don't think is needed for performance. Also it's going to lead to a much larger memory footprint with the more scrolling you do. Also is there a reason your don't want to use the supplied CGRect from the callback rather than manually calculate one from the contentOffset?
class LuckGameCollectionViewLayout: UICollectionViewFlowLayout {
let CELL_HEIGHT = 50.0
let CELL_WIDTH = 50.0
override func collectionViewContentSize() -> CGSize {
let contentWidth = Double(collectionView!.numberOfItemsInSection(0)) * CELL_WIDTH
let contentHeight = Double(collectionView!.numberOfSections()) * CELL_HEIGHT
return CGSize(width: contentWidth, height: contentHeight)
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let biggerRect = rect.insetBy(dx: -2048, dy: -2048)
let startIndexY = Int(Double(biggerRect.origin.y) / CELL_HEIGHT)
let startIndexX = Int(Double(biggerRect.origin.x) / CELL_WIDTH)
let numberOfVisibleCellsInRectY = Int(Double(biggerRect.height) / CELL_HEIGHT) + startIndexY
let numberOfVisibleCellsInRectX = Int(Double(biggerRect.width) / CELL_WIDTH) + startIndexX
var attributes: [UICollectionViewLayoutAttributes] = []
for section in startIndexY..<numberOfVisibleCellsInRectY
where section >= 0 && section < self.collectionView!.numberOfSections() {
for item in startIndexX..<numberOfVisibleCellsInRectX
where item >= 0 && item < self.collectionView!.numberOfItemsInSection(section) {
let cellIndex = NSIndexPath(forItem: item, inSection: section)
if let attrs = self.layoutAttributesForItemAtIndexPath(cellIndex) {
attributes.append(attrs)
}
}
}
return attributes
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let xPos = Double(indexPath.row) * CELL_WIDTH
let yPos = Double(indexPath.section) * CELL_HEIGHT
let cellAttributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT)
return cellAttributes
}
}
You need to come up with a data structure for your cells which is ordered by dimension(s), in order to come up with some algorithm using those dimension(s) to narrow down the search range.
Let's take the case of a table with cells 100 pixels tall by the full width of the view, and 250_000 elements, asked for the cells intersecting { 0, top, 320, bottom }. Then your data structure would be an array ordered by top coordinate, and the accompanying algorithm would be like
let start: Int = top / 100
let end: Int = bottom / 100 + 1
return (start...end).map { cellAttributes[$0] }
Add as much complexity as your actual layout requires.
cellAttrsDictionary is inappropriate to store the UICollectionViewLayoutAttributes. It's indexed by NSIndexPath but in layoutAttributesForElementsInRect you are searching for an area. If you need cellAttrsDictionary for something else then you could leave it but store each cellAttribute additionally in another data structure which is faster to search.
For example a nested array:
var allCellAttributes = [[UICollectionViewLayoutAttributes]]()
The first level array describes an area, let's say it's in chunks of 1000 pixel high and full screen width. So all cellAttributes which intersect with the rectangle { 0, 0, screenwidth, 1000 } go into:
allCellAttributes[0].append(cellAttributes)
All cellAttributes which intersect with the rectangle { 0, 1000, screenwidth, 2000 } go into:
allCellAttributes[1].append(cellAttributes)
And so on...
Then, in layoutAttributesForElementsInRect you can search in this data-structure by jumping directly into the array depending on the given CGRect. If the rect is e.g. {0, 5500, 100, 6700} then you just need to search in this range:
allCellAttributes[5]
allCellAttributes[6]
This should give you the basic idea, I hope you understand what I mean.

targetContentOffsetForProposedContentOffset - wrong proposed offset

I have a custom UICollectionViewLayout and it implements targetContentOffsetForProposedContentOffset in order to set paging. The center item in the UICollectionView is the full "large" size, while each other item has a CGAffineTransformScale of the "shrunk" scale value.
My problem is that there appears to be an upper limit on the contentOffset so that I can only scroll to item 5 of 7, and it bounces back. Specifics after the code:
I'm setting the collectionViewContentSize() as follows:
#IBInspectable var shrunkScale: CGFloat = 0.5 // 0.5 in IB
#IBInspectable var largeSize: CGSize = CGSizeZero // 360x490 in IB
#IBInspectable var itemSpacing: CGFloat = 0 // 32 in IB
var widthPerAdditionalItem: CGFloat {
return largeSize.width * shrunkScale + itemSpacing
}
override func collectionViewContentSize() -> CGSize {
guard let collectionView = self.collectionView else {
return CGSizeZero
}
let count = CGFloat(collectionView.numberOfItemsInSection(0))
let width = largeSize.width + (count) * widthPerAdditionalItem
let height = collectionView.bounds.height
let size = CGSize(width: width, height: height)
return size
}
the targetOffset... methods reference a single helper method:
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint) -> CGPoint {
let closestPlace = round(proposedContentOffset.x / widthPerAdditionalItem)
guard let offsetX = offsetXForItemAtIndex(Int(closestPlace)) else {
return proposedContentOffset
}
print("Calculated: \(offsetX), Proposed: \(proposedContentOffset.x), ContentWidth: \(collectionView?.contentSize.width ?? 0 )")
return CGPoint(x: offsetX, y: proposedContentOffset.y)
}
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
return targetContentOffsetForProposedContentOffset(proposedContentOffset)
}
func contentOffsetForItemAtIndexPath(indexPath: NSIndexPath) -> CGPoint? {
guard
let collectionView = self.collectionView,
let offsetX = offsetXForItemAtIndex(indexPath.item)
else {
return nil
}
print("Tap Offset: - \(offsetX) vs. \(collectionView.contentOffset.x)")
return CGPoint(x: offsetX, y: collectionView.contentOffset.y)
}
private func offsetXForItemAtIndex(index: Int) -> CGFloat? {
guard
let count = collectionView?.numberOfItemsInSection(0)
else {
return nil
}
let proposed = CGFloat(index) * widthPerAdditionalItem
let maximum = CGFloat(count) * widthPerAdditionalItem
// bound min = 0, max = count*width
return max( min(maximum, proposed), 0)
}
Here's What I get:
My content Width is 1844.0
I finish dragging the view at offset.x = 1187.5
The targetContentOffsetForProposedContentOffset receives a proposed offset.x = 820.0
I return the "paged" offset.x value of 848.0
The collectionView scrolls to offset.x of 820.0
What I am expecting:
My content Width is 1844.0
I finish dragging the view at offset.x = 1187.5
The targetContentOffsetForProposedContentOffset receives a
proposed offset.x = 1187.5
I return the "paged" offset.x value of 1272.0
The collectionView scrolls to offset.x of 1272.0
Debugging:
If I manually call setContentOffset with the calculated offset of 1272.0 then it scrolls to the correct position. But the instant I try to scroll it snaps back to 820.0
After sitting down and doing some math I figured out the following:
contentWidth = 1844
poposedContentOffset.x = 820
1844 - 820 = 1024 // <--- Width of the screen.
The content was being displayed from the center of the screen for the first item, to the center of the screen for the second item.
This means I needed to add half the collectionView's frame.width so the first item can be centered, and half again so the final item could be centered.
The collectionViewContentSize() now returns
let width = (count-1) * widthPerAdditionalItem + collectionView.bounds.width
let height = collectionView.bounds.height
let size = CGSize(width: width, height: height)

Resources