I am encountering a problem on landscape orientation for my collection view cell. When the app is in portrait it gives me the correct number of cell per row which is 2. But when I rotate my app to landscape it display 1 cell per row. Here's the screen of what I got:
Portrait:
Landscape:
Here's my code for adding the size of the cell:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var screenSize = CGFloat()
if UIDevice.currentDevice().orientation.isLandscape.boolValue {
screenSize = UIScreen.mainScreen().bounds.size.width
}
else {
screenSize = UIScreen.mainScreen().bounds.size.width
}
let cellWidth = screenSize / 2.0
return CGSizeMake(cellWidth, cellWidth)
}
Here is what you can do
let orientation = UIApplication.sharedApplication().statusBarOrientation
if(orientation == .LandscapeLeft || orientation == .LandscapeRight)
{
return CGSizeMake((yourCollectionView.frame.size.width-10)/2, (yourCollectionView.frame.size.height-10)/2)
}
else{
return CGSizeMake((yourCollectionView.frame.size.width-5)/2, (yourCollectionView.frame.size.height-10)/3)
}
What this code achieves:- If your view is in portrait you will see 2 column and 3 row and if your view is in landscape you will see 3 columns and 2 row. The deduction you see in the code is the spacing between two consecutive cells. So if there is 3 column the deduction is 15 and if there is 2 column the deduction is 10 assuming that the spacing between two cell is 5. Same goes for the row.
You can use the screen size as well if you want but I had auto layout constraints in my collection view to match the size of the screen so it resulted the same. I hope this is what you were looking for.
Just reload your UICollectionView in
override
func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
yourCollectionView.reloadData()
}
and your collection view layout will refresh again when orientation changed
Update:
This is code snippet for collectionView layout making 4 image view in each row from my code, and its working even it changing orientation from portrait to landscape and vice versa. You can change it to your need:
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width / 4.8, height: UIScreen.main.bounds.width / 4.8)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 16.0
}
Hope it will helps you.
Cheers!
Swift 5
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var flag:CGSize? = nil
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad
{
if UIApplication.shared.statusBarOrientation.isPortrait{
let cellWidth = floor((collectionView.bounds.width - 5) / 2)
flag = CGSize(width: cellWidth, height: cellWidth)
}
else if UIApplication.shared.statusBarOrientation.isLandscape{
let cellwidth = floor((collectionView.bounds.width - 5) / 4)
flag = CGSize(width: cellwidth, height: cellwidth)
}
}
else if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.phone{
if UIApplication.shared.statusBarOrientation.isLandscape {
let cellWidth = floor((collectionView.bounds.width - 5) / 2)
flag = CGSize(width: cellWidth, height: cellWidth)
} else if UIApplication.shared.statusBarOrientation.isPortrait{
// let cellWidth = floor((collectionView.bounds.width - 5))
flag = CGSize(width: 402 , height: 185)
}
}
return flag!
}
don't reload your collection view just invalidate your collection view flowlayour Done
if you don't understand what is that means just copy and paste this code and change your collection view name
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
fiveCellCustomCollectionView is my custom view and customCollectionView is collection view
guard let flowLayout = fiveCellCustomCollectionView.customCollectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
flowLayout.invalidateLayout()
}
Swift 4.2
let orientation = UIApplication.shared.statusBarOrientation
if(orientation == .landscapeLeft || orientation == .landscapeRight) {
return CGSize(width: (collectionView.frame.size.width-10)/2, height: (collectionView.frame.size.height-10)/2)
}
else {
return CGSize(width: (collectionView.frame.size.width-5)/2, height: (collectionView.frame.size.height-10)/3)
}
Related
I'm trying to show a collection view with each cell displaying a string inside border. I have set the direction to VERTICAL. So there can be multiple cells in each row with dynamic size. But cells are being displayed with different inter items space.
Current State of Collection View:
My collection view configuration is below:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if(collectionView == tagsCollectionView)
{
if(self.propertyTypes.count > 0)
{
if(indexPath.row == 0)
{
return CGSize(width: "You searched for properties in: ".width(withConstrainedHeight: 25, font: UIFont(name: "Avenir-Book", size: 13)!), height: 30)
}
return CGSize(width: self.propertyTypes[indexPath.row - 1].width(withConstrainedHeight: 25, font: UIFont(name: "Avenir-Book", size: 13)!), height: 30)
}
return CGSize(width: 0, height: 0)
}
return "String".size(withAttributes: nil)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
However, the collection view works absolutely fine when the direction is HORIZONTAL.
You can use a pod: https://github.com/mischa-hildebrand/AlignedCollectionViewFlowLayout to achieve your functionality.
You can achieve left, right, justified etc. with this library.
Use it like:
let alignedFlowLayout = collectionView?.collectionViewLayout as? AlignedCollectionViewFlowLayout
alignedFlowLayout?.horizontalAlignment = .left
alignedFlowLayout?.verticalAlignment = .top
Check library for more detailed info.
I have a horizontal UICollectionView which works fine. But I want to center cell when there is no need for scrolling. i.e.
if all cell are able to fit within the view width & user don't need to scroll then Do center the cell
if all cell are not able to fit within the view width & user need to scroll then Don't center the cell (default behaviour)
Graphical example:
UICollectionView that needs scrolling then keep default behavior
UICollectionView that doesn't need scrolling
What I have tried so far:
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var dataList:[String] = ["icon_rotate", "icon_rotate2", "icon_rotate3", "icon_rotate4", "icon_rotate5", "icon_rotate6"]
override func viewDidLoad() {
super.viewDidLoad()
//other stuff
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return (UIDevice.current.userInterfaceIdiom == .pad) ? 20.0 : 10.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return (UIDevice.current.userInterfaceIdiom == .pad) ? 20.0 : 10.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.height * 0.75, height: collectionView.frame.size.height * 0.75)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataList.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "editorCell", for: indexPath) as! EditorCell
cell.iconView.image = UIImage(named: dataList[indexPath.row])
return cell
}
}
This results in default behavior of UICollectionView so I searched in SO and found this post: How to center horizontally UICollectionView Cells?
and I added this code:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
let totalCellWidth = CellWidth * CellCount
let totalSpacingWidth = CellSpacing * (CellCount - 1)
let leftInset = (collectionViewWidth - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
let rightInset = leftInset
return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
}
This code does work fine when there's few cells to be loaded but I want this to dynamically detect if centering is needed or let the default behavior stay in action.
Thanks
Assuming your current code is working when you the cells will fit - that is, it centers the "row of cells" when desired - change this line:
let leftInset = (collectionViewWidth - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
to:
let leftInset = max(0, (collectionViewWidth - CGFloat(totalCellWidth + totalSpacingWidth)) / 2.0)
That way your insets will never be less than Zero
I am having a spacing issue with my Collection.i Couldn't figure out why. I want the images from the Second row till the last low to be equally spaced. can someone help me to fix this. tnx.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if (indexPath.row == 0) {
return singleUserModel.isevenItems(totalCount: CurrentUser.items.count) ? CGSize(width: self.view.frame.width * 0.4 , height: self.view.frame.height / 4) : CGSize(width: self.view.frame.width , height: self.view.frame.height / 4)
} else {
return CGSize(width: self.view.frame.width * 0.4, height: self.view.frame.height / 4)
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return CurrentUser.items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "UserDetailCell", for: indexPath) as! UserDetailCell
configureCell(cell: cell, forRowAtIndexPath: indexPath as NSIndexPath)
return cell
}
func configureCell(cell: UserDetailCell, forRowAtIndexPath indexPath: NSIndexPath) {
DispatchQueue.global(qos: .background).async {
cell.ItemImage.image = self.singleUserModel.loadImage(imageUrl: self.CurrentUser.items[indexPath.row])
cell.activityIndicator.isHidden = true
}
}
View look Like this
Actual Output
You need to set layout accordingly, Now your layout will be changed according to screen size So you need to calculate according to one screen size then it will work in other screen sizes:
Now, the layout will like this, In this 2 image will be shown in the width and if you want to set space from left and right side then set layout width accordingly.
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if (indexPath.row == 0) {
return singleUserModel.isevenItems(totalCount: CurrentUser.items.count) ? CGSize(width: self.view.frame.width * 0.4 , height: self.view.frame.height / 4) : CGSize(width: self.view.frame.width , height: self.view.frame.height / 4)
} else {
return CGSize(width: (self.view.frame.width/2), height: self.view.frame.height / 4)
}
}
Remove minimins spacing for cell and lines and section inset will also Zero like below image:
I have a collection view with 6 items and want to display them in a 2 cells per row and 3 rows format. The following code achieves this (as taken from this question: Swift: Collection View not adjusting correctly to change in size) in iPhone format nicely.
However on the any iPad the views layout is correct initially but if the screen is rotated to landscape and then back to portrait then the layout does not fully fit within the view and requires horizontal scrolling to see the second cell (cells width has somehow increased meaning the second cell in each row is partially cut off).
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 5
flowLayout.minimumInteritemSpacing = 5
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 6
}
override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
self.collectionView.collectionViewLayout.invalidateLayout()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if UIDevice.current.orientation.isLandscape {
let totalWidth = collectionView.frame.width
let totalHeight = collectionView.frame.height
let heightOfCell = totalHeight / 2
let numberOfCellsPerRow = 1
let widthOfCell = CGFloat(Int(totalWidth) / numberOfCellsPerRow)
return CGSize(width: widthOfCell , height: heightOfCell)
} else {
let totalWidth = collectionView.frame.width
let totalHeight = collectionView.frame.height
let heightOfCell = (totalHeight / 3)
let numberOfCellsPerRow = 2
let widthOfCell = CGFloat(Int(totalWidth) / numberOfCellsPerRow)
return CGSize(width: widthOfCell , height: heightOfCell)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(0, 5, 0, 5)
}
The problem here appears to be that, frame of the view is not updated when 'willRotate' is invoked. Instead try 'didrotate' or add the observer 'NSNotification.Name.UIDeviceOrientationDidChange' and invalidateLayout of collection inside the selector method. Don't forget to remove the observer on deinit().
I am trying to get 100% of the width of my collectionView with Swift 3 but I am confused. The method I am trying does not work. So far I have:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 25)
}
Any ideas?
I think you are trying to accomplish this:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width, height: 25)
}
collectionView.frame.size.width will get the width of the collectionView.
Note: The the parameters passed in are points, not percentages. So what you are returning is 100 points x 25 points. Thanks #Randy for the reminder!
I have a custom UICollectionViewFlowLayout subclass that I use. Note that you probably want to take into account insets, etc.
class AutoSizingCollectionViewFlowLayout: UICollectionViewFlowLayout {
#IBInspectable var height: CGFloat = 0
#IBInspectable var spacing: CGFloat = 0
override func prepareLayout() {
let containerBounds = (self.collectionView?.window != nil) ? self.collectionView?.bounds : UIScreen.mainScreen().bounds
var size = self.itemSize
size.width = CGRectGetWidth(containerBounds!)
size.width -= (self.sectionInset.left + self.sectionInset.right)
size.height = height
self.itemSize = size
self.minimumLineSpacing = spacing
super.prepareLayout()
}
}
Use this code in Swift 4:
var collectionWidth = collectionView.frame.size.width
if #available(iOS 11.0, *) {
collectionWidth -= collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right
}
The new iPhone nudge needs the safe area to be checked