Uicollectionview cell width issue iOS 10 - ios

I have a collection view controller and I set up it like this:
class r : UICollectionViewCell {
override var bounds: CGRect {
didSet {
contentView.frame = bounds
}
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "1", for: indexPath) as? r
cell.backgroundColor = UIColor.red
return cell
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
I want that the cell's width is equals to screen's width , for this reason in storyboard I edited cell's width:
but I have a problem :
if I execute it on iPhone 6 simulator I get this(and this is what I want to get on all devices) :
but If execute on iPad Pro I get this :
And I don't understand why.
Can you help me?
P.S I don't want to use tableview for some reasons

In iOS 10 there is a minor difference in function prototype:
Here is Swift3.0 Delegate method for sizeAtItemAtIndexPath:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
let screenRect: CGRect = UIScreen.main.bounds
let screenWidth: CGFloat = screenRect.size.width
let screencellHeight: CGFloat = screenRect.size.height
let cellWidth: CGFloat = screenWidth / CGFloat(2)
let cellHeight:CGFloat = screencellHeight / CGFloat(10.0)
let size: CGSize = CGSize(width: cellWidth, height: cellHeight)
return size
}

If u use Constraint so first of all your cell to apply equal width constraint and also storyboard in you should check collection view setting in inspect editor for horizontal and vertical.proper all setting..

I assume you have set a fixed width for your cell somewhere. Maybe as constraint. Set it dynamically:
Conform to UICollectionViewDelegateFlowLayout
and override:
#objc(collectionView:layout:sizeForItemAtIndexPath:)
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let desiredNumberOfCollumns: CGFloat = 1
let width = collectionView.frame.width / desiredNumberOfCollumns
let height = 100 // your desired height
return CGSize(width: width, height: hight)
}

I think you are using constraints. If yes, then all you have to do is just fix the width of your cell in constraints.
Unless you didn't change that. No matter where you have set the width ind delegates.It will remain same.

Related

cellForItemAt not called if numberOfItemsInSection is 1 UICollectionView

Following is my code which is working fine if numberOfItemsInSection is greater than 1 but if numberOfItemsInSection is equal to 1 it should display single cell but it is not happening, for numberOfItemsInSection equals 1 noting is displayed and cellForItemAt is not called
Also initial numberOfItemsInSection is Zero after making api call and receiving data it becomes one and I call following code as well
tabsCollectionView.reloadData()
Other code
tabsCollectionView.delegate = self
tabsCollectionView.dataSource = self
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return menuResult?.foodMenuItems?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = tabsCollectionView.dequeueReusableCell(withReuseIdentifier: "foodTabsCollectionViewCellID", for: indexPath) as! FoodTabsCollectionViewCell
cell.foodMenuItem = menuResult?.foodMenuItems?[indexPath.row]
cell.setUI()
return cell
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
following code related sizing in side viewDidLoad()
if let flowLayout = tabsCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = CGSize(width: 200, height: 70)
flowLayout.itemSize = UICollectionViewFlowLayout.automaticSize
tabsCollectionView.collectionViewLayout = flowLayout
}
sizeForItemAt method
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var height = collectionView.frame.height
var width = collectionView.frame.width
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
width = flowLayout.estimatedItemSize.width
height = flowLayout.estimatedItemSize.height
}
return CGSize(width: width, height: height)
}
Following is code inside FoodTabsCollectionViewCell
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.width = ceil(size.width)
frame.size.height = ceil(size.height)
layoutAttributes.frame = frame
layoutIfNeeded()
return layoutAttributes
}
I had the same problem when I tried to set a flexible width to a cell containing a UILabel with variable width (actually the size was related to the width of the text of the UILabel)
So initially I was using this approach (THIS was causing the problems):
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize <--- THIS
layout.itemSize.height = 70 <-- THIS
Then I removed the automaticSize (and itemSize.height) as below:
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
And it worked.
To make sure that I had flexible cell width I used this:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let dummyCell = MyCell(frame: .init(x: 0, y: 0, width: frame.width, height: 40))
dummyCell.name = users[indexPath.item].name
// 3. Force the view to update its layout
dummyCell.layoutIfNeeded()
// 4. Calculate the estimated size
let estimatedSize = dummyCell.systemLayoutSizeFitting(.init(width: frame.width, height: 40))
return CGSize(width: estimatedSize.width, height: 40)
}
The problem will happen when the collectionView scroll direction is horizontal and the height of collectionView is less than 50. I had the same problem and I increased the collectionView height to 50 then the problem solved.

Swift: Create stack of imageView with collectionView

Hey i want to create stack of UIImageView like the photo, how to do this
It must be dynamic. How can I move the cells so that they are one below the other?
Use UICollectionViewDelegateFlowLayout methods to make this kind of UI.
Example:
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.layer.borderWidth = 2.0
cell.layer.borderColor = UIColor.white.cgColor
cell.layer.cornerRadius = 50.0
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.height, height: collectionView.bounds.height)
}
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 -50
}
}
In the above code
Configure minimumLineSpacingForSectionAt and minimumInteritemSpacingForSectionAt methods of UICollectionViewDelegateFlowLayout.
Since the cell in collectionView are aligned left to right default, so to get the desired result we need to align them right to left.
As shown in above screenshot, use semantic property of collectionView for right to left alignment of cells.
Screenshot:
Edit-1:
One way to centre the cells in collectionView is to play with collectionView's width and centre it horizontally.
CollectionView constraints - top, width, centeredorizontally
In viewDidAppear, manually calculate the width of collectionView according to the numberOfItems. In your case numberOfItems = 3
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let width = min((collectionView.bounds.height) * CGFloat((numberOfItems-1)/2 + 1), UIScreen.main.bounds.width)
self.collectionViewWidthConstraint.constant = width
}
You can use UICollectionView Custom Layout .
Collection view layouts are subclasses of the abstract UICollectionViewLayout class. They define the visual attributes of every item in your collection view. The individual attributes are instances of UICollectionViewLayoutAttributes and contain the properties of each item in your collection view, such as the item’s frame or transform.
The similar Tutorial
This is a more complex layout , Change some parameters , you will get what you want.
Copyed from github , mpospese/IntroducingCollectionViews
translate some to Swift
class SpiralLayout: UICollectionViewLayout{
let itemSize: CGFloat = 170
var pageSize: CGSize!
var contentSize: CGSize!
var radius: CGFloat!
var cellCounts: [Int]!
var pageRects: [CGRect]!
var pageCount: Int!
override func prepare() {
super.prepare()
pageSize = collectionView?.bounds.size
let iPad = UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad
let scaleFactor: CGFloat = iPad ? 1 : 0.5
let side = itemSize * scaleFactor
radius = min(pageSize.width - side, pageSize.height - side * 1.2 )/2 - 5
pageCount = collectionView?.numberOfSections
var counts = [Int]()
var rects = [CGRect]()
for section in 0..<pageCount{
counts.append((collectionView?.numberOfItems(inSection: section))!)
rects.append(CGRect(origin: CGPoint(x: CGFloat(section) * pageSize.width, y: 0), size: pageSize))
}
cellCounts = counts
pageRects = rects
contentSize = CGSize(width: pageSize.width * CGFloat(pageCount), height: pageSize.height)
}
override var collectionViewContentSize: CGSize{
return contentSize
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return !pageSize.equalTo(newBounds.size)
}
func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
var attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.size = CGSize(width: itemSize, height: itemSize)
// ...
return attributes
}
}

How to align these collectionview cells properly?

I am creating a page where I use collection view. The layout will be like Apple's Music app where each row displays two square-shaped cells
I want a layout like this-this layout has super equal margins around but in my app the first collection cell is stuck to the left edge and the 2nd cell is stuck to the right edge
private let cellReuseIdentifier = "cellReuseIdentifier"
class FeedViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = .white
setupNavBar()
setupCollectionView()
}
func setupCollectionView() {
collectionView?.register(FeedCollectionViewCell.self, forCellWithReuseIdentifier: cellReuseIdentifier)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 6
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseIdentifier, for: indexPath)
return cell
}
}
extension FeedViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let padding: CGFloat = 25
let collectionViewSize = collectionView.frame.size.width - padding
return CGSize(width: collectionViewSize/2, height: collectionViewSize/2)
}
}
You can use the layout properties to set the collection view inset and the space you want between your cells, in your collection view setup function:
guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
layout.sectionInset = UIEdgeInsets(top: yourTopValue, left: yourLeftValue, bottom: yourBottomValue, right: yourRightValue)
layout.minimumInteritemSpacing = yourSpacing
layout.minimumLineSpacing = yourSpacing
Use following code to achieve your needs.
Padding value should be addition of all three spaces(Left, Center, Right)
Let's suppose padding value = 25 then it should consider 75 in layout calculation to get exact spacing.
And set left, top, center and right space with value 25(As you want to add space there).
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellWidth: CGFloat = (collectionView.frame.size.width / 2) - ((padding value * 3)/2)
let cellHeight: CGFloat = (collectionView.frame.size.height / 2) - ((padding value * 3)/2)
return CGSize.init(width: cellWidth, height: cellHeight)
}
Sure it will work

Editing the width of a CollectionViewCell

I am trying to edit the width of the cell so that no matter what screen size, I will have score1.count cells in a row. The code I am using for my Collection View is the following:
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCollectionViewCell
cell.myLabel.text = String(self.items[indexPath.item])
return cell
}
This is the function targeting the width of the cell.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = Int(self.view.frame.size.width)/(score1.count + 1)
return CGSize(width: CGFloat(size), height: 80)
}
I have spent many hours looking at different sources and trying different things, but the collection view never has the right number of cells. One idea is that it may be because I have the cell size set in auto layout but whenever I go to change it xcode crashes. Any ideas on bypassing the auto layout? Or how to change the auto layout?
Thanks!
try this
extension YourViewController : UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let rowItems:CGFloat = 4
let padding:CGFloat = 2
let width = (collectionView.bounds.width / rowItems) - padding //Change width as per your requirement
let height = collectionView.bounds.height - (2 * padding)//Change height as per your requirement
return CGSize(width: width, height: height)
}
}
You can change your sizeForItemAtIndexPath as. You can assign size of cell after substracting margin and diving by number of cell.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = (Int(collectionView.bounds.width) - (score1.count+1)*5) /(score1.count) // 5 is the margin you want to keep in between 2 cells
return CGSize(width: CGFloat(size), height: 80)
}
Do let me know if you have any queries. I would try and clarify.

UICollectionView Custom Cell to fill width In Swift

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)
])
}

Resources