Make collectionView scrolls as AppStore games section - ios

I have a collectionView with horizontal scroll direction and paging enabled, now I want to make it scroll as smoothly as the games section on the AppStore.
To be more specific this is how I want the first cell to be:
EDIT
I actually achieved both of the next images one with the smaller width
of the cell and the next with the .centeredHorizontally in selectItem,
my problem is the non smoothly scrolling.
As you can see a little of the second cell is appearing.
And here is the next cells:
As you can see now the previous and the next cell are appearing.
I managed to do this by setting this part of code but with bouncing no smoothly as AppStore.
This is the code I used:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
var visibleRect = CGRect()
visibleRect.origin = collectionView.contentOffset
visibleRect.size = collectionView.bounds.size
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
let visibleIndexPath: IndexPath = collectionView.indexPathForItem(at: visiblePoint) ?? IndexPath()
let indexPath = IndexPath(item: visibleIndexPath[1], section: 0)
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally )
}
And this is my collectionView code:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let frameSize = collectionView.frame.size
return CGSize(width: frameSize.width - 30, height: frameSize.height - 40)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 20, right: 0)
}

Example project here
In class property
var itemSize = CGSize(width: 0, height: 0)
#IBOutlet weak var collectionViewHeight: NSLayoutConstraint!
#IBOutlet weak var collectionView: UICollectionView!
fileprivate let sectionInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
In viewDidLoad or awakeFormNib(If in collectionViewCell or tableViewCell)
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
}
collectionView.contentInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 8)
collectionView.isPagingEnabled = false
layoutIfNeeded()
let width = collectionView.bounds.size.width-24
let height = width * (3/4)
itemSize = CGSize(width: width, height: height)
layoutIfNeeded()
Implement UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
return sectionInsets
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return itemSize
}
Implement ScrollViewDelegate
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let pageWidth = itemSize.width
targetContentOffset.pointee = scrollView.contentOffset
var factor: CGFloat = 0.5
if velocity.x < 0 {
factor = -factor
print("right")
} else {
print("left")
}
let a:CGFloat = scrollView.contentOffset.x/pageWidth
var index = Int( round(a+factor) )
if index < 0 {
index = 0
}
if index > collectionViewDataList.count-1 {
index = collectionViewDataList.count-1
}
let indexPath = IndexPath(row: index, section: 0)
collectionView?.scrollToItem(at: indexPath, at: .left, animated: true)
}
See example screenshot from my project at Past rides section
I have implemented uicollectionview in collectionViewCell

Set collectionView?.isPagingEnabled = true and remove your scrollViewDidEndDecelerating method

Related

ios swift UICollectionVIew Issue: The behavior of the UICollectionViewFlowLayout is not defined because

I am using UICollectionView using the flow layout. I have made a custom UICollectionView with horizontal. error-
The behavior of the UICollectionViewFlowLayout is not defined because:
the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
The relevant UICollectionViewFlowLayout instance is , and it is attached to ; layer = ; contentOffset: {-8, -8}; contentSize: {0, 100}> collection view layout: .
2019-11-18 11:51:59.017124+0530 eVyapaar_Grocery[3964:1811183] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
Here is my code:
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: "cellID", bundle: nil), forCellWithReuseIdentifier:"cellID")
collectionView.contentInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
collectionView.showsHorizontalScrollIndicator = false
collectionView.isScrollEnabled = true
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .horizontal
collectionView!.collectionViewLayout = layout
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if collectionView == myCcollectionView {
var width: CGFloat = 0
if UIDevice.isIpad {
width = ((UIScreen.main.bounds.width/6)-6)
} else {
width = ((UIScreen.main.bounds.width/3)-6)
}
return CGSize(width: width, height: width)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
As the error says, your cell size has to be smaller than your collectionView's height & width minus the content insets as to fit the cells inside the view. As I cant see the size of your collectionView with the code you have provided, I can't give you a definite answer, but try to reduce the size of your collectionView cells.
Also note that you are setting the contentInsets 2 different places,
here:
collectionView.contentInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
and here:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
edit: Try to give them this size:
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if collectionView == myCcollectionView {
var width: CGFloat = 0
if UIDevice.isIpad {
width = ((collectionView.frame.width/6)-8)
} else {
width = ((collectionView.frame.width/3)-8)
}
return CGSize(width: width, height: width)
}
}

Pure code builds the collection but UICollectionViewDelegateFlowLayout is not executed

Pure code (I didn't use the storyboard, just use code) builds the collection but UICollectionViewDelegateFlowLayout is not executed.
class Calendarview : UIView , UICollectionViewDelegate , UICollectionViewDataSource , UICollectionViewDelegateFlowLayout {
var collectionview : UICollectionView!
override func didAddSubview(_ subview: UIView) {
}
override func didMoveToSuperview() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
collectionview = UICollectionView(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height) , collectionViewLayout: layout)
collectionview.frame = self.frame
collectionview.register(CalendarCell.self, forCellWithReuseIdentifier: "Cell")
collectionview.dataSource = self
collectionview.delegate = self
self.addSubview(collectionview!)
collectionview.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 42
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
return CGSize(width: self.bounds.width / 7, height: self.bounds.height / 3)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CalendarCell
cell.lbshow = UILabel(frame: cell.bounds)
cell.lbshow.text = String(indexPath.row)
cell.addSubview(cell.lbshow)
cell.backgroundColor = .blue
return cell
}
}
Set delegate as self.collectionView.delegate = self;. UICollectionViewDelegateFlowLayout inherits from UICollectionViewDelegate. So it will called all methods of UICollectionViewDelegateFlowLayout.
Hence, You need to set self.collectionView.collectionViewLayout to your flow layout.
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.estimatedItemSize = CGSize(width: 10, height: 10)
layout.itemSize = CGSize(width: 200, height: 200)
layout.minimumLineSpacing = 10
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.scrollDirection = .horizontal
collectionview.collectionViewLayout = layout

How to center UICollectionviewCell with cell width different than the frame width and paging enable

I'm trying to center the uicollectionview cell while paging enable, but when I slide it, it does not stay centered! I would want it to be just like the image below!
collectionView.dataSource = self
collectionView.delegate = self
collectionView.alwaysBounceHorizontal = true
collectionView.isPagingEnabled = true
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let inset: CGFloat = calculateSectionIntet()
return CGSize(width: view.frame.width - inset * 2, height: view.frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 16
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let inset: CGFloat = calculateSectionIntet()
return UIEdgeInsets(top: 0, left: inset, bottom: 0 , right: inset)
}
func calculateSectionIntet() -> CGFloat {
return 32
}
Image:
Swift solution(Originally Objective-C) from a 2012 WWDC video.
Subclass UICollectionViewFlowLayout and override this function:
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else { return .zero }
// Add some snapping behaviour so that the zoomed cell is always centered
let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.frame.width, height: collectionView.frame.height)
guard let rectAttributes = super.layoutAttributesForElements(in: targetRect) else { return .zero }
var offsetAdjustment = CGFloat.greatestFiniteMagnitude
let horizontalCenter = proposedContentOffset.x + collectionView.frame.width / 2
for layoutAttributes in rectAttributes {
let itemHorizontalCenter = layoutAttributes.center.x
if (itemHorizontalCenter - horizontalCenter).magnitude < offsetAdjustment.magnitude {
offsetAdjustment = itemHorizontalCenter - horizontalCenter
}
}
return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
}
Also for better snapping, add this to the collectionView's viewController:
collectionView.decelerationRate = .fast

UICollectionView cell spacing not working anymore

I have been working on this app for a few days, I am working on laying out a UICollectionView to appear like a grid with no spacing in between the cells. It was working fantastically after I found this answer and implemented it into my own program. I worked on some other files and updated my computer, then I came back and tested this app just to see where I was and for some reason there is horizontal spacing in between cells. I have looked at a few other questions similar to this one, but I'm just wondering how to fix this and why this happened when I didn't change anything related to the layout.
Here's is what it looks like now:
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
var model = BoardModel()
var board = Board()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
collectionView.delegate = self
collectionView.dataSource = self
board = model.newGame(4, 4)
// Remove the spacing between the cells in collectionView
// Also make the cells the correct size to form a square/rectangle
let screenSize: CGRect! = collectionView.bounds
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: screenSize.width/CGFloat(board.columns), height: screenSize.width/CGFloat(board.columns))
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView!.collectionViewLayout = layout
// Center the collectionView
collectionView.contentInset.top = max((collectionView.frame.height - collectionView.contentSize.height) / 5, 0)
}
The reason I'm using the collectionView bounds instead of the UIScreen.mainScreen().bounds is because my collectionView has 20 spacing around all the edges.
Can you try
let width = UIScreen.main.bounds.width - 40 // convert it if it's not swift 4
let height = UIScreen.main.bounds.height - 40
//
or implement
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize.init(width: , height: )
}
import UIKit
class ViewController: UIViewController {
fileprivate let sectionInsets = UIEdgeInsets(top: 5.0, left: 5.0, bottom: 5.0, right: 5.0)
fileprivate let itemsPerRow: CGFloat = 2
override func viewDidLoad() {
super.viewDidLoad()
}
}
// MARK: UICollectionViewDelegateFlowLayout
extension ViewController : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
let availableWidth = view.frame.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
let heightPerItem = widthPerItem + 21
return CGSize(width: widthPerItem, height: heightPerItem)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets{
return sectionInsets
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat{
return sectionInsets.left
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat{
return 0
}
}

CollectionView show items paging circularly

I have a CollectionView with have 2 items.
CollectionView has pagingEnabled, I want to show them circularly like:
[1] 2 -> [2] 1 -> [1] 2 -> [2] 1
Is it possible to do this with CollectionView
My code:
extension HomeViewController: UICollectionViewDelegateFlowLayout {
func configureHeaderView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
headerView = UICollectionView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: headerHeight), collectionViewLayout: layout)
headerView?.backgroundColor = .blue
headerView?.isPagingEnabled = true
headerView?.isUserInteractionEnabled = true
headerView?.rx.setDelegate(self).addDisposableTo(disposeBag)
let nib = UINib(nibName: BannerCollectionViewCell.reuseIdentifier, bundle: nil)
headerView?.register(nib, forCellWithReuseIdentifier: BannerCollectionViewCell.reuseIdentifier)
headerView?.showsHorizontalScrollIndicator = false
tableView.tableHeaderView = headerView
if let headerView = headerView {
self.bannerItems.asObservable().bindTo(headerView.rx.items(cellIdentifier: BannerCollectionViewCell.reuseIdentifier, cellType: BannerCollectionViewCell.self)) {
row, banner, cell in
cell.banner = banner
}.addDisposableTo(disposeBag)
}
}
// MARK: UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: headerHeight)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
Make use of UICollectionView's scrollToItemAtIndexPath method to move collectionView back to the first item if it reached the end and the user swiped right. Move the collectionView back to the last item if user swiped left from first item. You should set the animation property to false
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
// Calculate where the collection view should be at the right-hand end item
let offsetRight = collectionView.frame.size.width * CGFloat(yourDataArray.count)
if scrollView.contentOffset.x == offsetRight {
// user is scrolling to the right from the last item to the 'fake' item 1.
// reposition offset to show the 'real' item 1 at the left-hand end of the collection view
let newIndexPath = IndexPath(row: 1, section: 0)
collectionView.scrollToItem(at: newIndexPath, at: .left, animated: false)
} else if scrollView.contentOffset.x == 0 {
// user is scrolling to the left from the first item to the fake 'item N'.
// reposition offset to show the 'real' item N at the right end of the collection view
let newIndexPath = IndexPath(row: (yourDataArray.count-2), section: 0)
collectionView.scrollToItem(at: newIndexPath, at: .left, animated: false)
}
}

Resources