I have to create a view in a Swift project with two differentiate sections.
The first one has seven cells with square shape. The second one has three cells with rectangular shape. Data inside cells are static.
In all cases, the sections should fit the entire screen width (mostly in landscape mode).
I don't have much experience using UICollectionViewController so I decided to accomplish this using one UICollectionViewController class and two reusable cells, but when I set the first section cell width the second one is also affected and then cells width is cut.
Is my point correct?
Shall I use different UICollectionViewControllers (one per section)?
Update: My Controller code
import UIKit
class InverterController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let moduleCell = "moduleCell"
let parameterCell = "parameterCell"
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if section > 0 {
return CGSizeZero
} else {
return CGSize(width: collectionView.frame.width, height: CGFloat(195))
}
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtPath indexPath: NSIndexPath) -> CGSize {
var newSize = CGSizeZero
if indexPath.section == 0 {
newSize = CGSizeMake(CGFloat(105), CGFloat(105))
} else {
newSize = CGSizeMake(CGFloat(313), CGFloat(200))
}
return newSize
}
override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "inverterHeader", forIndexPath: indexPath)
return headerView
default:
assert(false, "Unexpected element kind")
}
}
/**
Two sections in this UICollectionView: First one for clock items, second one for other parameters.
*/
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 2
}
/**
First section contains one cell
Second section contains three cells
*/
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
var items = 0
if section == 0 {
items = 7
} else {
items = 3
}
return items
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cellIdentifier = ""
if indexPath.section == 0 {
cellIdentifier = moduleCell
} else {
cellIdentifier = parameterCell
}
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath)
cell.layer.cornerRadius = 6
cell.layer.masksToBounds = true
return cell
}
}
Interface Builder configuration for CollectionView
Simulator screenshot (non desired result; all cells have the same width)
You can use collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize delegate method to adjust cell size.
In your case you could write something like this:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let width : CGFloat
let height : CGFloat
if indexPath.section == 0 {
// First section
width = collectionView.frame.width/7
height = 50
return CGSizeMake(width, height)
} else {
// Second section
width = collectionView.frame.width/3
height = 50
return CGSizeMake(width, height)
}
}
Put this code inside your collectionViewController class.
Set height to what your cell height should be. You might want to adjust width if you're using spacing between cells or insets in your collectionView.
for Swift 4
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width : CGFloat
let height : CGFloat
if indexPath.section == 0 {
// First section
width = collectionView.frame.width/7
height = 50
return CGSize(width: width, height: height)
} else {
// Second section
width = collectionView.frame.width/3
height = 50
return CGSize(width: width, height: height)
}
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
Related
I am new to Swift and I am trying to create a UICollectionViewCell. It builds successfully but the cells are not the size that I thought I was creating. There are supposed to be two columns and and it is supposed to contain an image, description and price. I know it is something simple but I cannot figure out what I am doing wrong. See the image below. I assume I need to fix something in the size inspector but I cannot figure out what.
import UIKit
class ProductsVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
#IBOutlet weak var productsCollection: UICollectionView!
private(set) public var products = [Product]()
override func viewDidLoad() {
super.viewDidLoad()
productsCollection.dataSource = self
productsCollection.delegate = self
}
func initProducts(category: Category){
products = DataService.instance.getProducts(forCategoryTitle: category.title)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return products.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ProductCell", for: indexPath) as? ProductCell {
let product = products[indexPath.row]
cell.updateViews(product: product)
return cell
}
return ProductCell()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height: CGFloat = 280
return CGSize(width: (collectionView.frame.size.width - 10) / 2, height: height)
//10 is minimum line spacing between two columns
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
}
FYI
Try this:
// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// You can change collection view cell height here, It's up to your design
let height: CGFloat = 150
let width: CGFloat = (collectionView.frame.size.width - 10 - a) / 2 // a is extra spacing of collection view (like leading or trailing constraints)
// Converting CGFloat to Int make sure the width of total cells are less than or equal the screen width
return CGSize(width: Int(width), height: Int(height)) // 10 is minimum line spacing between two columns
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
}
Please check extra spacings example with the below photo:
a = 20 with my example design (leading = 10, trailing = 10)
just provide the itemSize when you declare the collectionView:
let layout = UICollectionViewFlowLayout()
layout.itemSize = sizeForEachItem
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
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)
}
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)
}
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.
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);
}