MSPeekCollectionViewDelegateImplementation icon indicating copy to clipboard operation
MSPeekCollectionViewDelegateImplementation copied to clipboard

Support for more than one item in the cross-axis

Open gcox opened this issue 5 years ago • 7 comments

Currently, this lib forces the cross axis size of a cell to match that of the collection view. The App Store has another type of horizontally peeking collection view where multiple cells are displayed vertically in each page. The lib could allow specifying the number of cross-axis items to show, then use that value to calculate the appropriate item size in the collectionView:layout:sizeForItemAt: delegate func.

Screenshot of the UI I'm referring to img_0906

gcox avatar Mar 04 '19 12:03 gcox

Thanks @gcox for raising this issue! That's a good idea to implement. However, it's a bit challenging implement as we need to take into account inter-item/cell spacing and size for items. Also, calculation of current index and scrolling to items at specific indices will change. I'd consider this as nice to have since you can do a similar functionality by implementing a collection view cell that has a UITableView/UIStackView which shows x items at a time. If you have any ideas of how to make it simpler to implement this functionality it'd be of great help. Please let me know what you think

MaherKSantina avatar Mar 04 '19 13:03 MaherKSantina

@MaherKSantina Here's the basic implementation details. It is not thoroughly tested and I'm sure I'm missing some edge cases, just want to see if you had any other thoughts on this. I can submit a PR later this week if you don't see any glaring issues here.

fileprivate lazy var itemCrossLength: (UIView) -> CGFloat = {
  var length: CGFloat = self.scrollDirection.crossLength(for: $0)
  let allItemsLength = (length
    - (CGFloat(self.numberOfItemsToShowInCrossAxis + 1) * (self.cellSpacing))
    - 2)
  let finalLength = allItemsLength / CGFloat(self.numberOfItemsToShowInCrossAxis)
  return max(0, finalLength)
}

open func scrollView(_ scrollView: UIScrollView, indexForItemAtContentOffset contentOffset: CGPoint) -> Int {
  let mainAxisItemLength = itemLength(scrollView) + cellSpacing
  guard mainAxisItemLength > 0 else {
    return 0
  }
  let mainAxisOffset = self.scrollDirection.value(for: contentOffset)
  guard numberOfItemsToShowInCrossAxis > 1 else {
    return Int(round(mainAxisOffset / mainAxisItemLength))
  }
  let crossAxisItemLength = itemCrossLength(scrollView) + cellSpacing
  let crossAxisOffset = self.scrollDirection.crossValue(for: contentOffset)
  let minimumIndex = Int(round(mainAxisOffset / mainAxisItemLength)) * numberOfItemsToShowInCrossAxis
  let index = minimumIndex + Int(round(crossAxisOffset/crossAxisItemLength))
  return index
}

open func scrollView(_ scrollView: UIScrollView, contentOffsetForItemAtIndex index: Int) -> CGFloat {
  return CGFloat(index / numberOfItemsToShowInCrossAxis) * (itemLength(scrollView) + cellSpacing)
}

// Assumes we want the full height taken up by equally sized items
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
  var defaultSize = collectionView.frame.size
  if numberOfItemsToShowInCrossAxis > 1 {
    let crossAxisItems = CGFloat(numberOfItemsToShowInCrossAxis)
    let crossAxisGapTotal = (crossAxisItems - 1) * cellSpacing

    switch scrollDirection {
    case .horizontal:
      defaultSize.height = (defaultSize.height - crossAxisGapTotal) / crossAxisItems
    case .vertical:
      defaultSize.width = (defaultSize.width - crossAxisGapTotal) / crossAxisItems
    }
  }
  return scrollDirection.size(for: itemLength(collectionView), defaultSize: defaultSize)
}

public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
  ...

  // Only line that needs to change in this function
  let numberOfItemsToScroll = getNumberOfItemsToScroll(scrollDistance: currentScrollDistance, scrollWidth: itemLength(scrollView)) *
    numberOfItemsToShowInCrossAxis
  
  ...
}

That code depends on a couple of new extension methods on UICollectionViewScrollDirection just to return the 'crossLength', 'crossValue' numbers. Left them out since they're so simple.

gcox avatar Mar 04 '19 17:03 gcox

Ohh @gcox thank you for the code snippet! I'll definitely take a look at it in the weekend

MaherKSantina avatar Mar 05 '19 06:03 MaherKSantina

this is actually supported?

Sk8er22 avatar Jan 10 '20 09:01 Sk8er22

@Sk8er22 sadly it's not implemented yet. I was focusing on other features that I thought were more important. I deprioritized this one because you can achieve the same behavior by creating a collection view cell that has 3 items. I'm thinking about the complexity this feature adds to the library and it feels like there are a lot of things to take into consideration so I'm still hesitant about it.

Do you think you can achieve the same behavior by creating a collection view cell with a vertical stack view and add the views in it?

MaherKSantina avatar Jan 10 '20 10:01 MaherKSantina

I did it doing a custom big cell that contains all the possible stacks. Thanks anyway!

Sk8er22 avatar Jan 10 '20 14:01 Sk8er22

@Sk8er22 sounds great! I'll add a help-wanted tag in the meantime

Have a great day!

MaherKSantina avatar Jan 10 '20 15:01 MaherKSantina