What I want to achieve is a layout like this.
Inside to box there will be other content like image, text, button and so on. what I want is make the box sizing dynamic so that it's it takes the height of it's content. The first option came to my mind is use a collection view flow layout.
After some trial and error I end up with this layout.
My Problem is the inter item spacing I want them to be equal, is there any way to achieve this?
Any suggestion I greatly appreciated.
What I have tried so far.
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCollectionViewCell", for: indexPath) as! MyCollectionViewCell
cell.configure(title: data[indexPath.row].title)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let spacing: CGFloat = 10
let numberOfItemsPerRow:CGFloat = 2
let spacingBetweenCells:CGFloat = 10
let totalSpacing = (2 * spacing) + ((numberOfItemsPerRow - 1) * spacingBetweenCells)
let approximateWidthOfContent = view.frame.width - 32
let size = CGSize(width: approximateWidthOfContent, height: 1000)
let attributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15)]
let estimatedFrame = NSString(string: data[indexPath.row].title).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil)
if let collection = self.collectionView{
let width = (collection.bounds.width - totalSpacing)/numberOfItemsPerRow
return CGSize(width: width, height: estimatedFrame.height + 50)
}else{
return CGSize(width: 0, height: 0)
}
}
}
I know there are similar questions but I could not find any which meets my requirement.
After following a tutorial from raywenderlich I finally managed to achieve the layout I wanted, Thanks people who have helped me suggesting with important clues.
What I achieved
The link for the Instructions I have followed is here.
Related
I am working on the collectionView layout as below:
But the last row is not getting align properly not sure why?
Here is the collectionView setup code:
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 7
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "\(collectionViewCell.self)", for: indexPath) as? collectionViewCell {
cell.imageView.backgroundColor = .black
return cell
}
return UICollectionViewCell()
}
Here is the implementation of the flow layout delegate method:
let sectionInsets = UIEdgeInsets(top: 30.0, left: 20.0, bottom: 30.0, right: 20.0)
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.row == 2 { // This item should occupy the entire available width
let paddingSpace = sectionInsets.left + sectionInsets.right
let availableWidth = collectionView.frame.width - paddingSpace
let widthPerItem = availableWidth
return CGSize(width: widthPerItem, height: (widthPerItem / 2))
}
// adjust 2 cells in the single row
let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
let availableWidth = collectionView.frame.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
return CGSize(width: widthPerItem, height: widthPerItem)
}
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
}
What you are seeing is normal for a flow layout. All rows are left and right justified except for the last row, which is left justified only. It’s just like a printed book: the last line of a paragraph does not get spaced out to touch the right margin.
I had the same problem. But I could fix by setting collectionView(_:layout:minimumInteritemSpacingForSectionAt:).
I dragged a collectionview on to my xib, set the constraints as leading,trailing,top and height.
The coding part is given as...
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 5
layout.minimumInteritemSpacing = 5
collectionview2.delegate = self
collectionview2.dataSource = self
collectionview2.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "ident")
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
switch indexPath.item {
case 0,1:
return CGSize(width: (UIScreen.main.bounds.width - 16) / 2, height: (UIScreen.main.bounds.width - 16) / 2)
default:
return CGSize(width: (UIScreen.main.bounds.width - 32) / 3, height: (UIScreen.main.bounds.width) / 3)
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ident", for: indexPath)
cell.backgroundColor = .red
return cell
}
This is the link I have referred.
But it is not working and I'm seeing just 5 equal width and height cells one after the other.
What am I doing wrong...?
EDIT 1:
I would also like to show one long cell on left and 2 small cells on right like so...
What changes do I make to my answer so that I can achieve this...?
Your code looks perfect but you may forgot to confirm the FlowLayout Protocol.
so,
you just need to confirm the below protocol in your code,
UICollectionViewDelegateFlowLayout
Hope this way may help you.
You've to try this
Confirm UICollectionViewDelegateFlowLayout
e.g.
class yourViewController: UIViewController,UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let screeSize = UIScreen.main.bounds
let numOfCell:Int = 3 //Number of items you want
let spaceOfCell:CGFloat = (CGFloat((numOfCell + 1)*10)) //Space between Cells
return CGSize(width: Int(screeSize.width - spaceOfCell) / numOfCell, height: Int(screeSize.width - spaceOfCell) / numOfCell)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10.00
//Verticle space between line
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 10.00 //Space Between Items You want
}
Hope it helps :)
How can I change the X position of every single cell programmatically? I can change the width and height of the cell as my wish but it is unable to complete the project because of XY position.
My result ,
Expected result is,
and my full view controller code is,
import UIKit
class ReviewVC: UIViewController , UICollectionViewDataSource , UICollectionViewDelegate , UICollectionViewDelegateFlowLayout{
var cellWidthArr : [CGFloat] = [60.0 , 170.0 , 110.0 , 120.0]
var titleArr = [" Polite " , " Showed up on time ", " Quick responses " , " Fair prices "]
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "Review your experiance"
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView!.collectionViewLayout = layout
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ReviewCollectionCell", for: indexPath)as? ReviewCollectionCell
cell?.titleLabel.text = self.titleArr[indexPath.row]
cell?.titleLabel.layer.cornerRadius = (cell?.titleLabel.bounds.size.height)!/2
cell?.titleLabel.clipsToBounds = true
cell?.titleLabel.layer.borderColor = (UIColor.init(red: 240/255, green: 36/255, blue: 70/255, alpha: 1.0)).cgColor
cell?.titleLabel.layer.borderWidth = 1.0
return cell!
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
return CGSize(width: self.cellWidthArr[indexPath.row], height: 30)
}
}
Anyone please help me to findout the solution.Thanks...
You can use the open source project TagCellLayout written by riteshhgupta in GitHub.
Sample Code to initialize the layout:
import TagCellLayout
let tagCellLayout = TagCellLayout(alignment: .left, delegate: self)
collectionView.collectionViewLayout = tagCellLayout
I know you might write the lots of code for this, but it would be helpful greatly and even you can customize to different layout alignments.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return (ArrayName[indexPath.row] as NSString).size(withAttributes: nil)
}
Rather than giving Cell Fixed Size in an Array there is a way to find label size in sizeForItemAtIndexPath return the size of the text So it will find the size and work
Right now, i'm facing an issue.
I manage to wrap the cell's content view to its label, but there's some spacing which doesn't fit to the design. There's some spaces available between the cell
Here's my code
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 4
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let hastag = Hastag.fetchHastag()[indexPath.row]
let approximateWidth = view.frame.width - 20
let size = CGSize(width: approximateWidth, height: 28)
let attribute = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 12)]
let estimatedFrame = NSString(string: hastag.hastag).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attribute, context: nil)
return CGSize(width: estimatedFrame.width + 20, height: 26)
}
Below is the result
And this is what i want to achieve, it will be something like this
UPDATE
Here is my full code of collection view data source and delegate. Hastag.hastag is type of string
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Hastag.fetchHastag().count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "hastagCell", for: indexPath) as! HastagCollectionViewCell
cell.hastag = Hastag.fetchHastag()[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 4
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let hastag = Hastag.fetchHastag()[indexPath.row]
let approximateWidth = view.frame.width - 20
let size = CGSize(width: approximateWidth, height: 28)
let attribute = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 12)]
let estimatedFrame = NSString(string: hastag.hastag).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attribute, context: nil)
return CGSize(width: estimatedFrame.width + 16, height: 26)
}
You can use AlignedCollectionViewFlowLayout, its not a library just one file. Just add this file to your project and from your UICollectionView xib just set the flow layout as AlignedCollectionViewFlowLayout. It is very easy to use. Please check the below link.
https://github.com/mischa-hildebrand/AlignedCollectionViewFlowLayout
You can use the size(withAttributes:) function of a NSString to give you the CGSize of a string with a specific font and size.
Assuming that Hastag.hastag is a String I think the following should work:
extension YourViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let hastag = Hastag.fetchHastag()[indexPath.row]
let title = hastag.hastag as NSString
let titleSize = title.size(withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 12)])
return CGSize(width: titleSize.width + 20, height: 26)
}
}
Note that your ViewController must implement the UICollectionViewDelegateFlowLayout protocol and you must set your ViewController as the delegate for the UICollectionView for this to work.
Hope that helps.
After several try, i finally give up on customizing cell size and instead i'm using the library TagListView
So I have a CollectionView that has the following code:
class MyViewController: UIViewController {
...
#IBOutlet weak var imageCollectionView: UICollectionView!
fileprivate let sectionInsets = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1)
fileprivate let itemsPerRow: CGFloat = 3
...
override func viewDidLoad() {
super.viewDidLoad()
imageCollectionView.delegate = self
imageCollectionView.dataSource = self
}
}
...
extension MyViewController : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
let availableWidth = collectionView.bounds.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
return CGSize(width: widthPerItem, height: widthPerItem)
}
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
}
}
What I'm going for is 3 cells per row with very little space between but whenever I run the app I get something like this:
Where each black block is a cell.
For similar questions I've found, the answer is usually to implement the UICollectionViewDelegateFlowLayout functions which I've done and my functions are being called but just not working as expected! Even if I manually change the widthPerItem value to be slightly smaller, it still doesn't work!
Can anyone see if I'm missing something obvious here?
Thank you!
Did you make sure the minimum inter-item spacing is set to only 1 point?
You can set this up in Interface Builder or by overriding the following delegate method:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1.0;
}
Another possible reason I see is that the result of dividing availableWidth by itemsPerRow is a floating number. In the end the sum of the cell widths and the inset and might actually exceed the collection view width by 0.x pixels. To make sure this doesn't happen, you may want to use floor:
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
let availableWidth = collectionView.bounds.width - paddingSpace
let widthPerItem = floor(availableWidth / itemsPerRow)
return CGSize(width: widthPerItem, height: widthPerItem)
}