I have a collection view where 2 cells are being placed horizontally in portrait. In landscape mode I want 5 cells to be placed accordingly.
There is a method setItemSizeForCollectionView that defines my layout based on a device orientation.
It gets called in method configureCollectionView() which is called in viewDidLoad() and in viewWillTransition() when device gets rotated.
Views are created programmatically, so the size of the main view and its safeAreaInsets are not available in viewDidLoad.
I was thinking about subtracting those insets from the device size based on the orientation.
Any hint how can I achieve proper layout?
private func setItemSizeForCollectionView(layout: UICollectionViewLayout, with size: CGSize) {
guard let layout = layout as? UICollectionViewFlowLayout else { return }
var numberOfElementsHorizontally: CGFloat
if UIDevice.current.orientation.isPortrait {
numberOfElementsHorizontally = 2
layout.itemSize = CGSize(width: size.width / numberOfElementsHorizontally,
height: size.width / numberOfElementsHorizontally)
}
if UIDevice.current.orientation.isLandscape {
numberOfElementsHorizontally = 5
layout.itemSize = CGSize(width: size.width / numberOfElementsHorizontally,
height: size.width / numberOfElementsHorizontally)
}
}
private func configureCollectionView() {
let layout = UICollectionViewFlowLayout()
let width = UIScreen.main.bounds.width
let height = UIScreen.main.bounds.height
setItemSizeForCollectionView(layout: layout, with: CGSize(width: width, height: height))
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.scrollDirection = .vertical
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(CharacterCell.self, forCellWithReuseIdentifier: CharacterCell.reuseIdentifier)
}
This is a general purpose method for selecting how many cells per row you want in a collectionView:
func sizeForCell(itemsPerRow: Int)
{
return CGSize( width: bounds.width - (minimumInteritemSpacing * itemsPerRow) / itemsPerRow, height: 200)
}
EDIT: A bit more explanation, what we're doing here is setting the cell width to the collectionView's width, then dividing it by however many cells we want in the row, then removing some more width to leave for padding (number of items * padding amount).
Related
I am using this 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 am able to get a 2 column collection view on all iPhones except iPhone X and iphone XR, I don't know why
How to force 2 columns for all iPhones?
You can set layout of your collectionView by creating new layout and set it's itemSize, minimumInteritemSpacing and minimumLineSpacing and then assign new layout as collectionView.collectionViewLayout:
func setCollectionViewLayout(withPadding padding: CGFloat) {
let layout = UICollectionViewFlowLayout()
let size = (collectionView.frame.width - padding) / 2
layout.itemSize = CGSize(width: size, height: size)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView.collectionViewLayout = layout
}
and then call this method in viewDidLayoutSubviews (this is moment when frames are loaded and you can calculate with collectionView's frame)
override func viewDidLayoutSubviews() {
setCollectionViewLayout(withPadding: 50)
}
Note: I would recommend you to set leading and trailing constraints of collectionView to constant 25 instead of using padding
I suggest that you calculate width according to safeAreaLayoytGuide and, if you're using UICollectionViewFlowLayout, sectionInset. For UICollectionViewFlowLayout the following code will calculate proper width:
let sectionInset = (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset
let width = collectionView.safeAreaLayoutGuide.layoutFrame.width
- sectionInset.left
- sectionInset.right
- collectionView.contentInset.left
- collectionView.contentInset.right
If you need two columns, than item width will be calculated like that:
let space: CGFloat = 10.0
let itemSize = CGSize(width: (width - space) / 2, height: 100 /*DESIRED HEIGHT*/)
I have a collection view that displays a detailed view of an article with dynamic content size.
I have multiple items in the collection view that can be scrolled horizontally.
When the horizontal flow layout is active, the dynamic cell cannot be scrolled vertically and the content gets hidden under the view.
If I disable the horizontal flow layout, I can scroll the content vertically. But, all the other items gets stacked on the bottom of each cell.
I am basically looking for a news portal like layout, where a user can scroll through the content of the article vertically, and also scroll horizontally through the list of articles.
I have setup my collection view this way.
lazy var blogDetailCollectionView: UICollectionView =
{
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = 0
//layout.itemSize = UICollectionViewFlowLayoutAutomaticSize
let blogDetailCollection = UICollectionView(frame: .zero, collectionViewLayout: layout)
blogDetailCollection.delegate = self
blogDetailCollection.dataSource = self
blogDetailCollection.backgroundColor = .white
blogDetailCollection.isPagingEnabled = true
blogDetailCollection.translatesAutoresizingMaskIntoConstraints = false
return blogDetailCollection
}()
The collection view cell has a dynamic height that needs to be vertically scrollable, which I have set here.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
guard let blogDescription = blogContainer?.blogs[indexPath.item].blogDescription else {return CGSize(width: frame.width, height: frame.height)}
let widthOfTextView = frame.width
let size = CGSize(width: widthOfTextView, height: 1000)
let attributes = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: UIFont.labelFontSize)]
let estimatedFrame = NSString(string: blogDescription).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil)
let blogImageHeight = frame.width * (2 / 3)
return CGSize(width: widthOfTextView, height: estimatedFrame.height + blogImageHeight)
}
The collection view has multiple items, which needs to be scrolled horizontally.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return (blogContainer?.blogs.count)!
}
Have you tried.Setting the collectionView
let layout:UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: screenWidth/2,height: 200)
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = 1
layout.minimumLineSpacing = 1
collectionView.setCollectionViewLayout(layout, animated: true)
And set the collection view for both horizontal or vertical scroll when the button tapped declare the layout globally above viewDidLoad().
Note, I have scoured the internet and have not found a place to both size and centers cells that works. I tried doing it myself but I keep running to bugs I can't avoid. I am new to Swift. My code:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath:IndexPath) -> CGSize {
let cellWidth = collectionView.frame.size.width / 7.0
let cellHeight = collectionView.frame.height - 4.0
let imageSideLength = cellWidth < cellHeight ? cellWidth : cellHeight
return CGSize(width: imageSideLength, height: imageSideLength)
}
//centers the cells
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
// Make sure that the number of items is worth the computing effort.
guard let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout,
let dataSourceCount = photoCollectionView.dataSource?.collectionView(photoCollectionView, numberOfItemsInSection: section),
dataSourceCount > 0 else {
return .zero
}
let cellCount = CGFloat(dataSourceCount)
let itemSpacing = flowLayout.minimumInteritemSpacing
let cellWidth = flowLayout.itemSize.width + itemSpacing
let cellHeight = flowLayout.itemSize.height
var insets = flowLayout.sectionInset
// Make sure to remove the last item spacing or it will
// miscalculate the actual total width.
let totalCellWidth = (cellWidth * cellCount) - itemSpacing
let contentWidth = collectionView.frame.size.width - collectionView.contentInset.left - collectionView.contentInset.right
let contentHeight = collectionView.frame.size.height
// If the number of cells that exist take up less room than the
// collection view width, then center the content with the appropriate insets.
// Otherwise return the default layout inset.
guard totalCellWidth < contentWidth else {
return insets
}
// Calculate the right amount of padding to center the cells.
let padding = (contentWidth - totalCellWidth) / 2.0
insets.left = padding
insets.right = padding
insets.top = (contentHeight - cellHeight) / 2.0
//insets.bottom = (contentHeight - cellHeight) / 2.0
return insets
}
}
I try to use two separate functions: the first to size the cells and the second to center the cells. (Note I only want new cells to expand horizontally, with a maximum of 6 cells.) However, my calculation of cell height and width in the 2nd function does not agree with how I set it in the first function, setting off a chain of issues. Any insight on how to both size and center the cells such that I can have 1-6 cells horizontally fit on my screen centered would be great.
Your layout calls are conflicting. Try following THIS Tutorial to get the hang of it.
Otherwise a good answer for this is HERE
var flowLayout: UICollectionViewFlowLayout {
let _flowLayout = UICollectionViewFlowLayout()
// edit properties here
_flowLayout.itemSize = CGSize(width: 98, height: 134)
_flowLayout.sectionInset = UIEdgeInsetsMake(0, 5, 0, 5)
_flowLayout.scrollDirection = UICollectionViewScrollDirection.horizontal
_flowLayout.minimumInteritemSpacing = 0.0
// edit properties here
return _flowLayout
}
Set it with:
self.collectionView.collectionViewLayout = flowLayout // after initializing it another way
// or
UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
I'm trying to create equal horizontal spacing between two UICollectionViewCell and between a UICollectionViewCell an the screen border. It should look like this.
I think it is possible to use UICollectionViewFlowLayout to adjust the width of every UICollectionViewCell, so they have equal spacings regardless of the phone's size. There should always only be 3 cells on each row.
First of all you should add constants for all parameters of UICollectionView to the controller:
let itemsPerRow = 3
let spaceBetweenItems: CGFloat = 20.0
let spaceBetweenLines: CGFloat = 40.0
let itemHeight: CGFloat = 50.0
You mentioned that item's width should be adjustable. You can calculate it with this function:
var itemWidth: CGFloat {
return (self.collectionView.frame.width - CGFloat(self.itemsPerRow + 1) * self.spaceBetweenItems) / CGFloat(self.itemsPerRow)
}
Of course you need to customise layout of your UICollectionView instance:
var collectionViewLayout: UICollectionViewFlowLayout {
let result = UICollectionViewFlowLayout()
result.itemSize = CGSize(width: self.itemWidth, height: self.itemHeight)
result.minimumInteritemSpacing = self.spaceBetweenItems
result.minimumLineSpacing = self.spaceBetweenLines
result.sectionInset = UIEdgeInsets(top: 0.0, left: self.spaceBetweenItems, bottom: 0.0, right: self.spaceBetweenItems)
result.scrollDirection = .vertical
return result
}
And in the function viewDidLoad or your controller you should call setupCollectionView(). Here is a code of this function:
func setupCollectionView() {
self.collectionView.frame = self.view.frame
self.collectionView.collectionViewLayout = self.collectionViewLayout
}
You will see something like this:
I'm really in need of some help here. I've been trying for a few days but can't seem to fix it...
I'm trying to lay out images in a UICollectionView grid style, with scrolling disabled. So I have 4 images/cells, and I'm trying to completely fill the UICollectionView with these images with 1 spacing between them.
The problem, however is that the spacing between the cells is split between center and the bottom.
I've noticed that when changing the scrollDirection of the UICollectionView from vertical to horizontal, the problem still exists, but just changes direction too, so I'm guessing it has something to do with that.
example:
code:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if isInFeed && self.postImages!.count > 1 {
guard let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout else {
return CGSize()
}
flowLayout.minimumInteritemSpacing = 1
flowLayout.minimumLineSpacing = 1
switch (self.postImages!.count) {
case 2:
// we split the collectionView into 2 parts (+ 2*0.5 spacing)
return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: self.collectionView!.bounds.height)
case 3:
// we split the collectionView into 3 parts, the first one taking up half, the other 2 images taking up 1/4th (+spacing)
if indexPath.row == 0 { return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: self.collectionView!.bounds.height) }
else { return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: (self.collectionView!.bounds.height/2)-CGFloat(0.5)) }
case 4:
// we split the collectionView into 4 parts (1/4th + spacing)
// if indexPath.section == 0 { return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: (self.collectionView!.bounds.height/2)-CGFloat(1))}
// else {return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: (self.collectionView!.bounds.height/2)-CGFloat(0))}
return CGSize(width: (self.collectionView!.bounds.width/2)-CGFloat(0.5), height: (self.collectionView!.bounds.height/2)-CGFloat(0.5))
default: break
}
}
// If not in Feed or just one image, take up entire collectionView
return CGSize(width: self.collectionView!.bounds.width, height: self.collectionView!.bounds.height)
}
I also tried subclassing UICollectionViewLayout and UICollectionViewFlowLayout, but without success, I've read Apple's documentation, but that doesn't seem to help much.
Hope this explains the problem clearly, I'd be happy to elaborate.
Have a great day!
Edit:
I changed the scroll direction from vertical to horizontal and added 2 extra calculated spacing to each cell just to show off what happens if I do that.
Edit 2:
Finally managed to fix it! (altough not as clean as I'd like it, it does work now)
I put the following code inside of the collectionViewLayout method:
if indexPath.section == 0 {
flowLayout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 1 * UIScreen.main.scale)
} else {
flowLayout.sectionInset = UIEdgeInsets.zero
}
Big thanks to everyone trying to help out!
The final solution that worked for me (for anyone that runs into a similar situation):
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
if section == 0 {
return UIEdgeInsetsMake(0, 0, 0, 1 * UIScreen.main.scale)
} else {
return UIEdgeInsets.zero
}
}
Try this code. Add in a function and call in view did load. You might need to make a little adjustments but it works in my case.
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
let width = UIScreen.main.bounds.width
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: width / 2, height: width / 2)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView!.collectionViewLayout = layout
In Storyboard try with Size Inspector with Minimum Spacing Zero and all Section Insects zero.
layout.minimumLineSpacing = 1.0f;
layout.minimumInteritemSpacing = 1.0f;
CGSize collectionSize = collectionView.frame.size;
CGFloat cellsPerRow = 2.0f;
CGFloat spacing = 0.5f;
CGFloat itemWidth = collectionSize.width/cellsPerRow - spacing;
CGFloat itemHeight = collectionSize.height/cellsPerRow - spacing;
return CGSizeMake(itemWidth,itemHeight);