I have few cells on my UICollectionView and on rotations the number of rows and columns changes. I am able to achieve this with:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
if UIDevice.currentDevice().orientation.isPortrait.boolValue{
return CGSizeMake((collectionView.frame.size.width-2)/2, (collectionView.frame.size.height - 4)/3)
}
else{
return CGSizeMake((collectionView.frame.size.width-4)/3, (collectionView.frame.size.height - 4)/3)
}
}
All is well and fine until device is rotated. For rotation I have reloaded the collection view like this
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
myCollectionView!.reloadData()
}
The end result looks like this.(Sorry for bad gif)
The desired number of cells is achieved. But as you can see from the link, for a brief second there is a gap noticed when rotation occurs. This effect is not desired. Also it is bad to keep reloading the UICollectionView again and again just to fix the layout. I am able to achieve what I want but not in a proper way. Some expert ideas will be appreciated.
Here is a solution assuming you have a UICollectionViewFlowLayout:
First, add this to your ViewController:
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ResultsCollectionViewController.orientationDidChange(_:)), name: UIDeviceOrientationDidChangeNotification, object: nil)
Don't forget to remove self when VC is closed:
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Then implement the selector method :
func orientationDidChange(notification: NSNotification) {
collectionView!.collectionViewLayout.invalidateLayout()
}
Basically you just send a notification when the device orientation changes, then the selector invalidates your layout so that it gets refreshed.
EDIT :
Here is a basic UICollectionViewFlowLayout setup method :
func setupCollectionViewLayout(minimumInteritemSpacing minimumInteritemSpacing: CGFloat, minimumLineSpacing: CGFloat) {
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = minimumInteritemSpacing
layout.minimumLineSpacing = minimumLineSpacing
collectionView?.collectionViewLayout = layout
}
Then in viewDidLoad you can call :
// Setup a layout
setupCollectionViewLayout(minimumInteritemSpacing: 0, minimumLineSpacing: 0)
If you wish to use a UICollectionViewDelegateFlowLayout, you can use the following template, but keep in mind that you have to adapt it !
// ----------------------------------------------------------------------------------
// MARK: - UICollectionViewDelegateFlowLayout
// ----------------------------------------------------------------------------------
extension ResultsCollectionViewController: UICollectionViewDelegateFlowLayout {
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
switch UIDevice.currentDevice().userInterfaceIdiom {
case .Phone:
return CGSize(width: self.view.frame.width, height: 100.0)
case .Pad:
return CGSize(width: self.view.frame.width/2.0, height: 100.0)
default:
return CGSize(width: self.view.frame.width, height: 100.0)
}
}
}
Related
Im trying to get rounded UICollection cell but i found out that it's not working somehow and I can't come up why. I have a TableView in regular ViewController, in this tableview i've a custom table cell (in specified section) and in this custom table cell I've a CollectionView also with custom collectionCell and if i do anything rounded there, it's just not rounding anything. I cliped testBtn into bounds, but it wasn't work with this... I also tried testBtn.layer.masksToBounds with no success. However, if I'am doing a rounding in parent (tableView cell itself), it's working just good...
CollectionViewCell code:
import UIKit
class ProfileTileCollectionViewCell: UICollectionViewCell {
#IBOutlet var testBtn: UIButton!
#IBOutlet var contView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
testBtn.layer.cornerRadius = 20
testBtn.clipsToBounds = true
// Initialization code
}
#IBAction func testClicked(_ sender: Any) {
print("clicked")
}
}
Screenshot of the mainView: Screenshot.png
Blue color is a background of collectionView, gray is background of cell and violet is a button. I don't know if it's getting late but it's weird for me, not be able to get working this thing... I'm missing something... Any help I will appreciate !
Try to set testBtn.layer.masksToBounds = true
From the screenshot, I guess you have added corner radius in your collection view and set clipsToBounds true. If you don't need rounded collection view then remove cornerRadius and set clipsToBounds false. Or if you need both of them rounded then maintain padding between collection view and button. You can do this by fine-tuning the section inset property.
I had a great 8 hours of sleep and cuz that and Rob's comment, I tried to dig into debug for a bit, and I noticed a warning message which were saying that behaviour of UICollectionViewFlowLayout is not set... (and so on). So I decided to set up UICollectionViewDelegateFlowLayout like this:
extension ProfileTilesTableViewCell: UICollectionViewDelegateFlowLayout {
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, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (collectionView.frame.width - 40) / 3 , height: collectionView.frame.height - 5)
}
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 10
}
And finally :D somehow it started working properly !
So I deleted everything that I used for testing, deleted testBtn and changed testBtn.roundedCorners to contView.roundedCorners, set back the collectionView properties and it's looking near to what I'm looking for: Screenshot2.png
Thanx for your answers, especially to Rob's one. This is what I was looking for !
Best regards SB !
I am having 2 collection views in my view controller I want to have collectionView sizeForItemAt method just for one of them and not change the other one.
I have to set other delegates for both of them.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if (collectionView == collectionView1) {
return CGSize(width: 158, height: 90)
} else if (collectionView == collectionView2) {
let collectionWidth = (collectionView.bounds.size.width - 20) / 3
return CGSize(width: collectionWidth, height: collectionWidth)
}
}
I don't want to return anything for collectionview2 because its size is dynamic and based on device size. which is done by a flowLayout.
Is there a way just to implement this method for the first one?
It seems that all the cells of collectionView1 has the same size. If so, use estimatedItemSize. It is an instance property of UICollectionViewFlowLayout.
estimatedItemSize
The estimated size of cells in the collection view. developer.apple.com
let layoutOne = UICollectionViewFlowLayout()
layoutOne.estimatedItemSize = CGSize(width: 158, height: 90)
let collectionViewOne = CollectionViewOne(frame: .zero, collectionViewLayout: layoutOne)
// CollectionView2 must have its own layout
let layout = UICollectionViewFlowLayout()
let collectionViewTwo = CollectionView(frame: .zero, collectionViewLayout: layout)
If you do so, remove collectionView(_:layout:sizeForItemAt:).
I have a UICollectionView with horizontal scrolling and paging. The cells should be the screen size always, so when the orientation of the device changes there is the call collectionView.collectionViewLayout.invalidateLayout() and the item size and collection view offset get recalculated and it works well.
The issue occurs when going to another view controller, then rotating and going back to the view controller with the collection view. Then it looks like the collection view doesn't take into account the orientation change or something like this and there could be two cells visible, which should not happen. Also there is some weird animation when going back to the view controller with the collection view. How should these be fixed?
Here is some code:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
collectionView.collectionViewLayout.invalidateLayout()
coordinator.animate(alongsideTransition: { (context) in
}) { (context) in
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return collectionView.frame.size
}
func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
let width = collectionView.frame.size.width
let visibleCells = collectionView.visibleCells
if visibleCells.count == 0 {
return CGPoint.zero
}
let cell = visibleCells[0]
let indexPath = collectionView.indexPath(for: cell)
let index = indexPath?.item
let offsetX = CGFloat(index!) * width
let offsetY: CGFloat = 0
let offset = CGPoint(x: offsetX, y: offsetY)
return offset
}
Trying to fix the issue I make the following calls in viewDidAppear but no luck:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
collectionView.collectionViewLayout.invalidateLayout()
collectionView.layoutSubviews()
}
Scenario - I have to create a custom UICollectionView class programmatically which has to be presented in any place I want.
Code till now -
For custom UICollectionView
class ABSegmentView: UICollectionView,UICollectionViewDelegateFlowLayout,UICollectionViewDataSource {
var segmentProperties=segmentControlProperties()//segmentControlProperties is a modal class having relevant details regarding about population of collection view.
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
self.dataSource = self
self.delegate = self
self.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellIdentifier")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int{
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(segmentProperties.titleArray)
return segmentProperties.titleArray.count//data properly being received over here
}
//not getting called
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = self.dequeueReusableCellWithReuseIdentifier("cellIdentifier", forIndexPath: indexPath)
cell.backgroundColor = UIColor.redColor()
return cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize{
return CGSizeMake(self.segmentProperties.segmentHeight, self.segmentProperties.segmentWidth)
}
}
Code for adding this collection view in some place -
let segment = ABSegmentView(frame: CGRectMake(0, 0, 200, 200), collectionViewLayout: UICollectionViewLayout())
segment.segmentProperties.segmentWidth = 60
segment.segmentProperties.segmentHeight = 50
segment.segmentProperties.titleArray = ["heyy","heyy","heyy","heyy","heyy","heyy"]
self.view.addSubview(segment)
So what is getting added is only an empty collection view.
Reason Figured out -
On debugging I found that my data source method cellForItemAtIndexPath() & func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) are not getting called.
Question - I am not sure what I am doing for my required scenario is the right implementation or not. Please amend me if I am missing something somewhere or what might be my mistakes.
Edit :
Answer -
Thanks to Santosh's answer. I figured out that I misunderstood the concept of collectionViewLayout.
Findings -
I have to set a proper flow layout for the collection view as a
proper flow layout with correct spacing and other values are quite
essential for a collection view to be properly laid.
CollectionView Flow layout is what lays the UI of collection view i.e the grid view.
There are many questions in StackOverflow which relates of data source methods not being called due to improper laying of collectionViewFlowLayout.
References that worked out for me apart from accepted answer -
https://stackoverflow.com/a/14681999/5395919
Other instances when some one can encounter such problems -
-When we set our cell size quite bigger than our collection view.
-When our cell layout size is too big or isn't appropriately held by the collection view.
You are using UICollectionViewLayout to instantiate your custom collectionView with layout. Try using UICollectionViewFlowLayout. Below code may help you, let me know if it works:
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 20//you can change this value
layout.scrollDirection = .Vertical//or may be .Horizontal
let segment = ABSegmentView(frame: CGRectMake(0, 0, 200, 200), collectionViewLayout: layout)
segment.segmentProperties.segmentWidth = 60
segment.segmentProperties.segmentHeight = 50
segment.segmentProperties.titleArray = ["heyy","heyy","heyy","heyy","heyy","heyy"]
self.view.addSubview(segment)
segment.reloadData()
In order to get datasource updated you need to call reloadData()
What is the correct of of disabling the supplementary views (e.g. header)? One time I want to show them (iPhone) and one time I don't want to show them (iPad).
The only idea I currently have is to return a size of zero in referenceSizeForHeaderInSection. But I think it's kind of an overhead to create views, which aren't used at all. On the other side I have to implement collectionView:viewForSupplementaryElementOfKind:atIndexPath: and I can't return nil, because the app crashes then.
How can I disable the supplementary views in UICollectionView?
If you return CGSizeZero from collectionView:layout:referenceSizeForHeaderInSection: delegate method collectionView:viewForSupplementaryElementOfKind:atIndexPath: won't be called.
Apple Developer API Reference states about collectionView(_:layout:referenceSizeForHeaderInSection:):
If the size in the appropriate scrolling dimension is 0, no header is added.
The following Swift 5 code snippet shows a possible implementation of collectionView(_:layout:referenceSizeForHeaderInSection:) in order to display or not UICollectionView supplementary views:
// MARK: - UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad {
return CGSize.zero
} else {
return CGSize(width: 0, height: 40)
// or
//let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
//return flowLayout.headerReferenceSize
}
}