UICollectionView Items are offsetting by its section position - ios

I have implemented UICollectionView for rendering WKWebview as contentView's subview of UICollectionViewCell and bound UIPageControl to it.
Note: Just for demoing the problem while rendering, I have made the size of the cell width as 100
Problem is first Webview(Which is a cell in the first section of collection view) is showing as expected but the second cell displaying with an offset of cell width.
UICollectionViewDelegate Override methods
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1;
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// set this width for just showing screenshot for this question
return CGSize(100, height: view.frame.height-view.safeAreaInsets.top);
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0;
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
self.webviewsPageControl.currentPage = indexPath.section;
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
self.webviewsPageControl.numberOfPages = self.webviews?.count ?? 0;
return self.webviews?.count ?? 0;
}
For identification, I made the UICollectionViewCell background as red.
So the question is, how to remove the offset so that webviews can be rendered side by side. And Why is this behavior.
Thanks in advance.

You need to set the minimumLineSpacing = 0 on the flow layout, e.g.
let layout = UICollectionViewFlowLayout();
layout.minimumLineSpacing = 0
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)

I use the following code to reduce spacing between columns:
let cellsPerRow = 2 //number of columns or number of cells in each row
In viewDidLoad():
guard let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout else { return }
flowLayout.minimumInteritemSpacing = 0 //setting margin between cell columns = 0
flowLayout.minimumLineSpacing = 0// setting margin between cell lines = 0
flowLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) //Not required, solutions works without it as well
Finally,
//function to set number of columns
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
let marginsAndInsets = flowLayout.sectionInset.left + flowLayout.sectionInset.right + flowLayout.minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)
return CGSize(width: itemWidth, height: itemWidth)
}

Related

collectionview grid layout issue with different devices

Hello i am creating App using Swift and i am creating grid view using Collectionview but i got different output on different devices here is the code for populating and setting layout of collectionview
var imageArray = [UIImage]()
var nameArray = ["General album","Videos","New album","Custom name album"]
private var numberOfItemsInRow = 2
private var minimumSpacing = 10
private var edgeInsetPadding = 10
extension GalleryViewController:UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! GalleryCollectionViewCell
cell.imgView.image = imageArray[indexPath.row]
cell.lblName.text = nameArray[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let inset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
edgeInsetPadding = Int(inset.left+inset.right)
return inset
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return CGFloat(minimumSpacing)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return CGFloat(minimumSpacing)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = (Int(UIScreen.main.bounds.size.width) - (numberOfItemsInRow - 1) * minimumSpacing - edgeInsetPadding) / numberOfItemsInRow
return CGSize(width: width, height: width)
}
}
from this code i am not able to achive exact grid layout so i tried below code
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let padding: CGFloat = 50
let collectionViewSize = collectionView.frame.size.width - padding
return CGSize(width: collectionViewSize/2, height: collectionViewSize/2)
}
I got this code from this link:
Display UICollectionView with 2 columns of equal size square cells
but i am not able to achive exact grid layout its changed based on device i am sharing two screen shot
iPhone 11 Max Pro screen shot:
iPhone 7 Screen Shot:
in iphone 7 i am getting layout perfect but on iPhone 11 Max Pro i am not getting perfect any solution? than please help me
Add the following code in the "viewDidLoad" method of your controller:-
collectionView.delegate = self
collectionView.dataSource = self
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical //.horizontal
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 10
collectionView.setCollectionViewLayout(layout, animated: true)
Add only "insetForSectionAt" and "sizeForItemAt" these methods to handle the layout of your collection View. As I have modified the delegate and data source you can directly use it.
extension GalleryViewController:UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! GalleryCollectionViewCell
cell.imgView.image = imageArray[indexPath.row]
cell.lblName.text = nameArray[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)//here your custom value for spacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let lay = collectionViewLayout as! UICollectionViewFlowLayout
let widthPerItem = collectionView.frame.width / 2 - lay.minimumInteritemSpacing
return CGSize(width:widthPerItem, height: widthPerItem)
}
}
Im assuming your issue here is that the horizontal spacing is too much in iphone 11 Pro Xmax . The reason to this is that the iphone xsMax screen is much larger than the iphone 7 Screen. Take a look at this link below it has all the IOS screen sizes and widths and pixels which may come in handy.
https://www.paintcodeapp.com/news/ultimate-guide-to-iphone-resolutions
As for your issue , the width of the device is different from iphone 7 to iphone 11 so the issue is you are setting the minimumSpacing ,width , height for an iphone 7 screen and you need to use constraints for the sides and use the constarints to adjust the widths , and heights and the spacing needs to be edited so that it is similar to all screens .
The chances of it coming perfectly equal in all your screens are minor but you can do much better with this approach .

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

UICollectionView showing 2 items instead of 3

I have implemented my UICollectionView like this
extension MyViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return photos.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath)
cell.backgroundColor = .black
return cell
}
}
extension MyViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let widthPerItem = view.frame.width / 3
return CGSize(width: widthPerItem, height: widthPerItem)
}
}
but instead of 3 items in a row I'm getting 2 items
how can i fix it? i didn't set any UIEdgeInset for my items and sections
Try this:
extension MyViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let numRowItems:CGFloat = 3
let padding:CGFloat = 2
let width = (collectionView.bounds.width / numRowItems) - padding
let height = collectionView.bounds.height - (2 * padding)
return CGSize(width: width, height: height)
}
}
You forgot to account for minimumInteritemSpacing and minimumLineSpacing properties of UICollectionViewFlowLayout - they both default to 10.0 if you didn't set them.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let spacing : CGFloat = (collectionViewLayout as? UICollectionViewFlowLayout)?.minimumInteritemSpacing ?? 0.0
let widthPerItem = (view.frame.width - spacing * 2)/ 3
return CGSize(width: widthPerItem, height: widthPerItem)
}
This is because your screan width can not fit 3 cells. This is because collection view has cell min line spacing property in your storyboard if you try with small width in sizeForItemAt they will fit.
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let widthPerItem = view.frame.width / 3 - ((collectionViewLayout as? UICollectionViewFlowLayout)?.minimumInterItemSpacing ?? 0.0)
return CGSize(width: widthPerItem, height: widthPerItem)
}
in obj c
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
//If you want your cell should be square in size the return the equal height and width, and make sure you deduct the Section Inset from it.
return CGSizeMake((self.view.frame.size.width/2) - 16, (self.view.frame.size.width/2) - 16);
}
swift
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// Compute the dimension of a cell for an NxN layout with space S between
// cells. Take the collection view's width, subtract (N-1)*S points for
// the spaces between the cells, and then divide by N to find the final
// dimension for the cell's width and height.
let cellsAcross: CGFloat = 3
let spaceBetweenCells: CGFloat = 1
let dim = (collectionView.bounds.width - (cellsAcross - 1) * spaceBetweenCells) / cellsAcross
return CGSize(width: dim, height: dim)
}

UICollectionView with multiple rows

I use UICollectionView (views with UIButtons) to book the time:
override func viewDidLoad() {
super.viewDidLoad()
collectionViewTime.register(UINib(nibName: "TimeCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "timecell")
collectionViewTime.dataSource = self
collectionViewTime.delegate = self
if let layout = collectionViewTime.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .vertical
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Timeline.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
let numberOfItems = CGFloat(collectionView.numberOfItems(inSection: 0))
let combinedItemWidth = (numberOfItems * flowLayout.itemSize.width) + ((numberOfItems - 1) * flowLayout.minimumInteritemSpacing)
let padding = (collectionView.frame.width - combinedItemWidth) / 2
return UIEdgeInsets(top: 0, left: padding, bottom: 0, right: padding)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "timecell", for: indexPath) as! TimeCollectionViewCell
let time = Timeline[indexPath.row][0]
cell.btnTime.setTitle(DateManager.dateToTime(date: time.0), for: .normal)
return cell
}
And I got all views (UIButtons) in one line:
How can I set automatically moving UIViews in new rows?
So, in my case (iPad) one row will contain 7 UIViews, iPhone will contain 4-5 UIViews in one row.
For that you need to implement UICollectionViewDelegateFlowLayout method collectionView(_:layout:sizeForItemAt:) and change the collectionView's Scroll direction to Vertical
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let minCellSpace: CGFloat = 10 //Set minimum cell space that you want
if UIDevice.current.userInterfaceIdiom == .pad {
//For iPad return 7 cell as you mention in question
return CGSize(width: (collectionView.frame.size.width / 7) - minCellSpace, height: yourCellHeight)
}
else {
//For iPhone return 4 cell, if you want to return 5 cell divide width to 5
return CGSize(width: (collectionView.frame.size.width / 4) - minCellSpace, height: yourCellHeight)
}
}
Please use one more flow delegate method as below
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize;
------
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width:(button width), height:button hieght);
}

Weird drawing when using UICollectionView created programmatically

I am creating a UICollectionView and its cells programatically. In my custom UICollectionView the only method that is implemented is layoutSubviews. Here is the code below for setting up my collection view and handling the delegation. As you can see from the gif once in a while a cell loads correctly, but the majority are misaligned or off screen. I suspect it is something wrong with how they are reused. I usually use tableviews and scrollviews, so I'm not as comfortable with collection views. How can I fix this?
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.itemSize = CGSize(width: 160, height: 200)
featuredCollectionView = UICollectionView(frame: frame), collectionViewLayout: layout)
featuredCollectionView.delegate = self
featuredCollectionView.dataSource = self
featuredCollectionView.register(ItemCollectionViewCell.self, forCellWithReuseIdentifier:
"cell")
// MARK: CollectionView Delegate
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat
{
return 8
}
func numberOfSections(in collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
return CGSize(width: 160, height: featuredCollectionView.frame.size.height)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return featuredItems.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ItemCollectionViewCell
cell.item = featuredItems[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
{
return UIEdgeInsetsMake(0, 0, 0, 0)
}
To see your main issue, check the frame of backgroundCardView, you're setting it based on the cell's origin within the collection view.
backgroundCardView.frame = CGRect(x: self.frame.origin.x + 8, y: self.frame.origin.x + 8, width: self.frame.size.width - 16, height: self.frame.size.height - 16)
This is going to be way outside the cell's bounds, which is why it appears to be laying out incorrectly. Also, you should add subviews to the cell's contentView instead of directly to it's view. As noted in other comments, you shouldn't add views in layoutSubviews. A better place would be inside an init method.

Resources