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
Related
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
Here the output of the code but I want to make it into 2 column
I want to make a collection view with more than 1 column but this code just shows one column. Can anyone help me how to fixed it??? Thank You....
import UIKit
class SearchViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
Here the array
var collectionArray : [String] = ["1", "2", "3", "4"]
let labelnamee = [("Long Sleeve Abstract Print Top"), ("Logo Graphic Print Top"), ("Material Study T-Shirt"), ("Mission Statement Print T-Shirt"), ("Printed Logo T-Shirt"), ("Tie-Dye Print T-Shirt")]
let labelpricee = [("RM 1021"), ("RM 860"), ("RM 750"), ("RM 750") , ("RM 670"), ("RM 760")]
let imagee = [UIImage(named: "abstractprint"),
UIImage(named: "longsleeve"),UIImage(named: "studymaterial"),UIImage(named: "mission"), UIImage(named: "white"), UIImage(named: "tiedye")]
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return labelnamee.count
}
for the collection cell border
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
let cellIndex = indexPath.item
cell.imageView.image = imagee[cellIndex]
cell.labelname.text = labelnamee[cellIndex]
cell.labelprice.text = labelpricee[cellIndex]
cell.contentView.layer.cornerRadius = 10
cell.contentView.layer.borderWidth = 1.0
cell.contentView.layer.borderColor = UIColor.blue.cgColor
cell.backgroundColor = UIColor.white
cell.layer.shadowColor = UIColor.gray.cgColor
cell.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
cell.layer.shadowRadius = 2.0
cell.layer.shadowOpacity = 1.0
cell.layer.masksToBounds = false
cell.layer.shadowPath = UIBezierPath(roundedRect: cell.bounds, cornerRadius: cell.contentView.layer.cornerRadius).cgPath
return cell
}
}
Here the code for the layout. I think I made mistake in the height or weight..
extension SearchViewController : UICollectionViewDelegateFlowLayout {
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 {
let bounds = collectionView.bounds
let heigtVal = self.view.frame.height
let widthVal = self.view.frame.width
let cellsize = (heigtVal < widthVal) ? bounds.height/2: bounds.width/2
return CGSize(width: cellsize - 10, height: cellsize - 10)
}
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 10
}
}
If the width of the cell or the contents of the cells is such that the collectionView cannot accommodate be able to see them within the visible portion of the screen, the collection view automatically places next to them. You have to scroll your collecctionView to see your next column.
If you want to see two columns within the visible screen portion, pls follow the steps:
Firstly place all your views inside a StackView and align them vertically.
Then set the right width & height constraints to your imageView to maintain the aspect ratio for your requirements. I have set like below just to demonstrate:
Along with the above constraints, I have also set the cell size to automatic in Storyboard itself rather than doing programmatically. Removed sizeForItemAt indexPath from code
:
Finally, after following all the above steps the output looked like this
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 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'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)
])
}