When UICollectionView will fill cells and get height in swift - ios

I have a uiCollectionView with some elements. I need to know the collectionView's height before viewDidAppear to do some stuff magic. I use the below snippet code:
collectionView.collectionViewLayout.collectionViewContentSize.height
// or
collectionView.contentSize.height
However, this is only return true value in viewDidAppear and in viewWillAppear and viewDidLoad others return 0 (before viewDidAppear I need that). I need to know about collectionView's lifecycle and where the elements fill in the collectionView to determine it's height.

If you want to know about height of UICollectionView then use the code
collectionView.frame.size.height
If you want to know how much scrollable height is then use
collectionView.contentSize.height
The datasource methods of UICollectionView are called just before the viewDidLayoutSubviews function.

UICollectionView's loading is an asynchronous process so you can't be sure the loading is completed before viewDidAppear is called.
I suggest you to use KVO to observe value changes of your collectionView's contentSize, and perform your magic when the value changes.
Another option "dirty" option from my pov is to do something like
collectionView.reloadData()
DispatchQueue.main.async{
// This will be called when after reloadData() completes
}

Implement the following UICollectionViewDelegateFlowLayout
method which returns the size of specified item's cell before rendering it.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = collectionView.frame.size.height
// Your logic
}

How can you get contentSize of collectionView before the containing view(ViewController's view) is loaded?. I think you forgot the Life cycle of view.
If your controller contains subViews(like collectionView, tableView). Then first of all main parent view is loaded(viewDidLoad, viewWillApper or viewDidApper) and after that all subview is directly loaded in according to view hierarchy.
So you can only get full correctly contextSize after collectionView completely loaded. If you trying to access contentSize before view loaded then it return default value which is 0. So if you want to do some magic animation on collectionView you can hide when initialise, And after complete collectionView is loaded then Unhide it and perform animation.

Related

Strange issue with updating UICollectionView size

I have a UICollectionView, which initial height is 50.
I want to update it when I know the size of the cell.
For what I do:
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let categoriesCount = 16
let numberOfItemsInRow = 3
let numberOfRows = ceil(CGFloat(categoriesCount) / CGFloat(numberOfItemsInRow))
let newHeight = (numberOfRows * cell.frame.size.height)
collectionHeight.constant = newHeight
collectionView.collectionViewLayout.invalidateLayout()
}
But despite of calling invalidateLayout - the height of the collectionView stays the same - 50.
But when I switch screens it shows the correct height when I open that screen with UICollectionView for the second time.
What I am doing wrong and how can I update the height of my UICollectionView?
I even tried collectionView.updateConstraints() after the collectionView.collectionViewLayout.invalidateLayout but still no luck
UPDATE
I've tried also
collectionView.layoutIfNeeded()
self.superview?.layoutIfNeeded()
but no result.
For this, you might have to call the self.view.layoutIfNeeded() method to force the view to update it's layout.
As per the documentation:
...this method lays out the view subtree starting at the root.
hence, updating your view
For more info, you can read this thread where you can pick bits and pieces to add to your solution.
Instead of trying to update the collectionView later based on the size of the cell, can we not use estimated height adjustment of the cell.
Change the Estimated size option in size inspector of collection view to "None" from "Automatic".
See the Image Below for reference.
enter image description here
I tried many answers given in different threads, none worked in my case but this one.
Check https://stackoverflow.com/a/59783595/11806784 this thread for reference.

Auto-sizing UITableViewCell which contains UICollectionView

I am having troubles with an autosizing UITableViewCell which contains a UICollectionView. The view hierarchy is roughly like this
UITableViewCell
-- Content View
---- Custom View
------ UIStackView (vertical)
--------- UILabel
--------- UICollectionView (vertical flow layout)
When the UITableViewCell is created or reused, the collectionView receives a datasource which it then starts to process. I need all cells of the collectionView to show in the tableViewCell, so I observe changes to the collectionView's content size in the Custom View like this
self.collectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.old, context: nil)
Once the collectionView finished rendering its content, I read its contentSize and set the height constraint of the collection view accordingly. This works fine.
The problem now is that all values calculate correctly, but the cell size does not change in the tableView. There are no autolayout errors or warnings in the console, so I assume all constraints are correct. The label in the stackView is also autosizing and this works fine.
I can pass info about the new contentSize of the stackView all the way up to the TableViewController, but I don't know what to call where to force autolayout to recalculate the height of an individual cell.
If I call tableView.reloadData() at the controller level if the height constraint of the collectionView in a cell does not match its content size, it does work. But it causes a delay and is obviously too expensive.
If I pass the cell that changed to the tableView and ask it to reload just the cell, the tableView does not find the indexPath of the cell (probably because it is still off screen). So this approach does not work.
Thanks!
First of all thanks to everybody for their responses!
After two days of pulling out my hair, it looks like UITableViewCell layout not updating until cell is reused solved it for me.
The problem was the missing
cell.layoutIfNeeded()
at the end of
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
Here you can see what it looks like now. The setup is like in the original question.
Again thanks everybody for your pointers!
Even thought this is an already answered question. But I have found this easy method in this tutorial to resize the UITableViewCell height according to the UICollectionView height size.
Add a height constraint to the UICollectionView and create an outlet for it in the UITableViewCell and don't forget to disable UICollectionView Scrolling and set its flow to vertical.
#IBOutlet weak var collectionViewHeight: NSLayoutConstraint!
In the UITableViewDelegate cellForRow method use these
cell.frame = tableView.bounds
cell.collectionView.reloadData()
cell.layoutIfNeeded()
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height
I can tell it is working like charm and no need for observes.
Link for full tutorial

Reset Scrollview in UICollectionViewCell after scrolling

I'm creating a photo gallery with this concept. For listing the images, I've used UICollectionView. Each image is stored in full screen size custom cells. Inside the cell, I have UIScrollView and inside it I have UIImageView. ScrollView is used to zoom the image. All working fine but, when I zoom in one image and scroll to another cell without zooming out, I want that previous cell's scrollview to be reset.
Inside custom cell class, I set ZoomScale for each cell's scrollview when they are initiated like so:
func configureCell(_ photo: String){
albumPhoto.image = UIImage(named: photo)
scrollView.setZoomScale(1.0, animated: false)
}
I configure each cell in cellForItemAt function before returning the cell.
When I zoom in in the first cell, scroll to second and scroll back to the first cell, the image is still zoomed in. But if I scroll to second and third cell and then return to first cell, the image is zoomed out to default.
How can I achieve that even after scrolling immediately back from second cell to first, the scroll view will be set to default zoom scale.
you need to implement this method: https://developer.apple.com/documentation/uikit/uicollectionviewdelegate/1618087-collectionview
and zoom out the scrollView before displaying the cell. This method will handle your case. Happy coding ;)
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
First make your class conforms to
class profViewController: UIScrollViewDelegate
As UICollectionView is a subclass of UIScrollView
Second implement this delegate method
func scrollViewDidScroll(_ scrollView: UIScrollView)
{
let cd = areaSettTable.visibleCells as!
[profTableViewCell]
//////
loop here
}
then loop through this array and call the function that resets the zoom for every cell
note : you should change profTableViewCell name to your cell

UICollectionView custom cell and make horizontal scroll

I'm a newbie of IOS app develop. Now a day , I want to make a screen likes below picture.
How to make it?
I'm thinking about using UICollectionView but I don't know how to customize it?
You have implemented method named: "sizeForItemAt", it is used to set the size of every cell of your collection view. And your condition is satisfied because of bidirectional scrolling property of collection view. If you change the size of cell, than this may be worst for your UI.
Follow the steps:-
Select collection view from storyboard (if using).
Than go to Attributes Inspector.
And set the Scroll Direction Vertical to Horizontal.
(Vertical is default)
I can do that now by below workaround
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout{
...
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 40.0, height: 500.0)
}
}
You can do it after seeing tutorial of content offset and content size of collection view. This two parameter help you to check for overlapped cell.
Check content offset of every cell with the x origin of green view.
create cocoa touch file.
step 1
step 2
Now you will have two files created.
use xib to design your UI and CollectionViewCell class to connect xib's outlets.
Now in the main view controller where you are confirming UIcollectionViewDelegate and UIcollectionViewDataSource register the xib for an identifier.
collectionView.register(UINib(nibName: "CollectionViewCell", bundle: Bundle.main), forCellWithReuseIdentifier: "yourIdentifier")
you can use this yourIdentifer to deque item for collection view in ItemForRowAtIndexPath.
Now to Change the Scroll Direction Property. Below is the image you can refer
Or programatically you can change it by following piece of code.
if let layout = self.yourCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .horizontal
}

Dynamically Changing all of the Uitableviewcells height to fill the uitableview in swift

I have been having an issue for quite some time with regards to an ios app I have been building in swift. I have a Uitableview ,with dynamic properties, embedded within my view controller. And what I want to do is size all of the cells to fill the table view. I have tried:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
tableView.reloadData()
let height = tableView.contentSize.height
return height
}
but this just returned zero for the height of the uitableview.
Help would be very appreciated.
You don't want to call reloadData() from this delegate method, since this method is getting called because the data is being reloaded.
In fact, you don't want to use this method at all if all of your cells are going to be the same height. Just set the table view's rowHeight property.
Assuming this is happening from a view controller, I'd override viewDidLayoutSubviews() to get notified when the table view size changes and set the row height there:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.rowHeight = tableView.bounds.height
}
You may or may not need to add reloadData() there to get the table view to update. Try it without it first. Setting the row height may automatically trigger a reload.
Also notice that I don't use contentSize to get the height. That's the height of all of the cells, and it's determined by the row height you set. So again, using it gets a little circular.

Resources