I am trying to have my cells around 0.85% the screen width so that the next and previous cells would be partially shown to tell the user that there are more cells.
I tried using many of the solutions on here along with collectionView.isPagingEnabled = true but the visible cell is never centered.
I expect the main cell that is shown to be centered regardless of whether there are more cells to its left/right or not.
This is the wrong behavior and my code. Thanks for any help.
Video:
https://imgur.com/a/LgvYFCB
override func viewDidLoad() {
super.viewDidLoad()
// View preparation
view.addSubview(mainTabBarView)
mainTabBarView.configure()
// Collection view
mainTabBarView.collectionView.dataSource = self
mainTabBarView.collectionView.delegate = self
}
extension MainTabBarController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cells.previewCell, for: indexPath) as! PreviewCell
cell.configure()
return cell
}
}
extension MainTabBarController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width*0.85, height: collectionView.frame.size.height)
}
}
extension MainTabBarController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 15
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 15
}
}
class MainTabBarView {
// MARK:- Main configuration
func configure() {
...
setupCollectionView()
...
}
private func setupCollectionView() {
let flowLayout = UICollectionViewFlowLayout()
let screenWidth = UIScreen.main.bounds.width
flowLayout.itemSize = CGSize(width: Int(screenWidth * 0.85), height: 68)
flowLayout.minimumInteritemSpacing = 15
flowLayout.minimumLineSpacing = 15
flowLayout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: CGRect(), collectionViewLayout: flowLayout)
collectionView.backgroundColor = Colors.Primary.clear
collectionView.showsHorizontalScrollIndicator = false
collectionView.register(PreviewCell.self, forCellWithReuseIdentifier: Cells.previewCell)
collectionView.allowsSelection = false
collectionView.contentInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
collectionView.isPagingEnabled = true
}
}
Not a completed solution to the problem per se, but I see a couple of approaches which may help.
implement your own custom collection view layout so that you have full control on the cells layout and attributes, and you can make a logic in order to make the cell centered.
you make the collecview width 85% of the view controller width, and marking his clipToBounds as false, and finally setting the cell width to be like the collection view width. I did not test it but it could show the le left and right cell in the dequeue overflowing the boundaries of the collection view, as clipToBounds of the collection view is set to false. If you need to have a spacing between the cells, you could then actually wrap the visual content oer each cell in a view which has a certain margin from leading and trailing, and the result might be the same
I try to add footer view in my collection view layout with collection view delegate method viewForSupplementaryElementOfKind but it's not being called while reload collection view.
SeatArrangementViewController : UICollectionViewDelegate , UICollectionViewDataSource , QuiltViewDelegate ,UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
setCollection()
}
func setCollection() {
self.clnSeats.collectionViewLayout.invalidateLayout()
self.clnSeats.register(UINib(nibName: "SeatFooterView",bundle:nil),forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: "SeatFooterView")
let space = UIScreen.main.bounds.size.width - 262
self.clnSeats.contentInset = UIEdgeInsets(top: 0, left: space/2.0, bottom: 90, right: space/2.0)
let layout = self.clnSeats.collectionViewLayout as! QuiltView
layout.scrollDirection = UICollectionViewScrollDirection.vertical
layout.itemBlockSize = CGSize(width: 50, height: 50)
self.clnSeats.reloadData()
}
Collection View Methods
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind{
case UICollectionElementKindSectionFooter: .....
}
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, blockSizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
// get random width and height values for the cells
let width = self.numberWidths[indexPath.row]
let height = self.numberHeights[indexPath.row]
return CGSize(width: width, height: height)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetsForItemAtIndexPath indexPath: IndexPath) -> UIEdgeInsets {
return UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
}
I also add #objc (collectionView:viewForSupplementaryElementOfKind:atIndexPath:) but that not work for me.
Check if you've registered a supplementary view.
Check if delegate and data source are set.
If you are using custom size for footer or header ensure that it returns a size that greater than zero otherwise delegate will stop trying to get a supplementary view
func collectionView(_ collectionView: UICollectionView,layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize
How to make vertical uiCollectionViewCell Left side when CollectionView has only one item
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
collectionviw.delegate = self
collectionviw.dataSource = self
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 5, bottom: 10, right: 2)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionviw!.collectionViewLayout = layout
collectionviw.reloadData()
}
extension ViewController: UICollectionViewDelegate,
UICollectionViewDataSource,UICollectionViewDelegateFlowLayout
{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection
section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt i
ndexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionviw.dequeueReusableCell(withReuseIdentifier: "cell",
for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath:
IndexPath) -> CGSize {
let collectionWidth = collectionView.bounds.width / 2 - 50
return CGSize(width: collectionWidth, height: collectionWidth)
}
}
output
I think this is a bug in iOS that automatically centers the single cell in CollectionView because of UICollectionViewFlowLayoutAutomaticSize. You can create your own UICollectionViewFlowLayout
I followed the approach given here :
Stop Single UICollectionView cell Flowing to the centre of the screen
I want to show banner like this:
My approach is adding a CollectionView as a TableViewHeader
My code:
extension HomeViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func configureHeaderView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
let 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.dataSource = self
headerView.delegate = self
headerView.register(BannerCollectionViewCell.self, forCellWithReuseIdentifier: BannerCollectionViewCell.reuseIdentifier)
headerView.showsHorizontalScrollIndicator = false
tableView.tableHeaderView = headerView
}
// MARK: UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BannerCollectionViewCell.reuseIdentifier, for: indexPath) as! BannerCollectionViewCell
return cell
}
// MARK: UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: headerHeight)
}
}
My BannerCollectionViewCell has a default image.
class BannerCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var bannerImageView: UIImageView!
}
But I don't see that image on my header. It just show an empty header.
you use the NIB, so you should use func register(UINib?, forCellWithReuseIdentifier: String) instead of func register(AnyClass?, forCellWithReuseIdentifier: String)
Maybe you're looking for an image pager like KIImagePager on the top instead of a collection view. You can also create the same using inbuilt PageViewController.
You can add a TableView separately below this.
I've been trying to figure-out how can i make the cell fill the width, as you can see in the picture the width between the cells is too big. i am using custom cell with only one imageView.
I tried to customize it from the storyboard but i guess there is no option for that or it should be done programmatically.
my UICollectionViewController :
#IBOutlet var collectionView2: UICollectionView!
let recipeImages = ["angry_birds_cake", "creme_brelee", "egg_benedict", "full_breakfast", "green_tea", "ham_and_cheese_panini", "ham_and_egg_sandwich", "hamburger", "instant_noodle_with_egg.jpg", "japanese_noodle_with_pork", "mushroom_risotto", "noodle_with_bbq_pork", "starbucks_coffee", "thai_shrimp_cake", "vegetable_curry", "white_chocolate_donut"]
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
//#warning Incomplete method implementation -- Return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//#warning Incomplete method implementation -- Return the number of items in the section
return recipeImages.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! RecipeCollectionViewCell
// Configure the cell
cell.recipeImageView.image = UIImage(named: recipeImages[indexPath.row])
return cell
}
You need to do this programatically.
Implement UICollectionViewDelegateFlowLayout in your view controller and provide the size in collectionView:layout:sizeForItemAtIndexPath:
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let kWhateverHeightYouWant = 100
return CGSizeMake(collectionView.bounds.size.width, CGFloat(kWhateverHeightYouWant))
}
You will also want to call collectionView.collectionViewLayout.invalidateLayout() inside your view controller's viewWillLayoutSubviews() so that when the main view's dimensions change (on rotation, for example), the collection view is re-laid out.
Swift 4 Update
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let kWhateverHeightYouWant = 100
return CGSize(width: collectionView.bounds.size.width, height: CGFloat(kWhateverHeightYouWant))
}
Inside your view controller override viewDidLayoutSubviews method
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
let itemWidth = view.bounds.width / 3.0
let itemHeight = layout.itemSize.height
layout.itemSize = CGSize(width: itemWidth, height: itemHeight)
layout.invalidateLayout()
}
}
(collectionView property is your collectionView)
Use Following code for set the width of UICollectionViewCell.
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: screenWidth/3, height: screenWidth/3);
}
Also in Swift 3,
Make sure your view controller complies with the following:
UICollectionViewDelegate,
UICollectionViewDataSource,
UICollectionViewDelegateFlowLayout
Swift 3
If you are using swift 3, use this method:
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
}
Notice the changes:
add _ before collectionView in the method name
NSIndexPath changes to IndexPath
I have the same requirement, in my case below solution is worked. I set UIImageView top, left, bottom and right constraints to 0 inside UICollectionviewCell
#IBOutlet weak var imagesCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// flowlayout
let screenWidth = UIScreen.main.bounds.width
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 10, right: 0)
layout.itemSize = CGSize(width: screenWidth/3 - 5, height: screenWidth/3 - 5)
layout.minimumInteritemSpacing = 5
layout.minimumLineSpacing = 5
imagesCollectionView.collectionViewLayout = layout
}
For my case, I wanted to show two columns and 3 rows in the UICollectionView
I added UICollectionViewDelegateFlowLayout delegate to my class
then I override sizeForItemAt indexPath
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellHeight = (collectionView.bounds.size.height - 30) / 3 // 3 count of rows to show
let cellWidth = (collectionView.bounds.size.width - 20) / 2 // 2 count of colomn to show
return CGSize(width: CGFloat(cellWidth), height: CGFloat(cellHeight))
}
The 30 is the Line spacing, the 20 is the insent between cells
Programatically set the itemSize to [UIScreen mainScreen].bounds.size.width/3
In my case, assuming every cell has a width of 155 and a height of 220. If I want to show 2 cells per row in a portrait mode and 3 for landscape.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var itemsCount : CGFloat = 2.0
if UIApplication.sharedApplication().statusBarOrientation != UIInterfaceOrientation.Portrait {
itemsCount = 3.0
}
return CGSize(width: self.view.frame.width/itemsCount - 20, height: 220/155 * (self.view.frame.width/itemsCount - 20));
}
The best solution is to change the itemSize of your UICollectionViewFlowLayout in viewDidLayoutSubviews. That is where you can get the most accurate size of the UICollectionView. You don't need to call invalidate on your layout, that will be done for you already.
For my case, autolayout was only allowing the cell to grow to its content size even though I overrode the cell size in the storyboard, conformed to UICollectionViewDelegateFlowLayout, and implemented sizeForItemAt. So I made a dummy UIView the width of the cell's bounds.
UICollectionViewController:
extension CollectionViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.bounds.width - 40
return CGSize(width: width, height: collectionView.bounds.height / 3)
}
}
Cell (I'm calling this method on dependency injection):
private func updateViews() {
let padding:CGFloat = 8
let innerView = UIView()
innerView.translatesAutoresizingMaskIntoConstraints = false
innerView.layer.cornerRadius = 8
innerView.layer.borderColor = UIColor.black.cgColor
innerView.layer.borderWidth = 1
contentView.addSubview(innerView)
NSLayoutConstraint.activate([
innerView.widthAnchor.constraint(equalToConstant: bounds.width - padding*2),
innerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: padding),
innerView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -padding),
innerView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: padding),
innerView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -padding)
])
}