I have a collectionView inside a UITableViewCell. The collectionView's content is dynamic, hence, its height is dynamic too and the tableView cell's height depends on the collectionView's height. The collectionView cells have different sizes. The collectionView's height is always set to fit its contentSize height.
My problem is that the tableView's function heigthForRowAtIndexPath, is called before the collectionView is created (in cellForRowAtIndexPath) so, to return the height that fits the collectionView, I need to calculate it manually (which doesn't seem like a good idea for a collectionView with cells of different sizes).
I tried to use autolayout in order to use UITableViewAutomaticDimension but it didn't work (maybe I did it in a wrong way).
What is the best aproach to make a UITableViewCell consider the height of its subview in heightForRowAtIndexPath? Can I know a collectionView's estimated size without creating it?
Use self sizing, which is available in iOS 8. There are plenty of good tutorials online, like this one: http://www.appcoda.com/self-sizing-cells/.
The idea is that you can use auto layout and a few lines of code in viewDidLoad to render a table view cell that dynamically fits the content in it.
Some more tutorials:
http://useyourloaf.com/blog/2014/08/07/self-sizing-table-view-cells.html
https://github.com/smileyborg/TableViewCellWithAutoLayoutiOS8
Related
I want to make a layout using UICollectionVIew. But I am not sure it is possible or not.
I have tried changing height of one Cell but it is not working. Here is the layout which I want to acheive:
In this Layout height of each cell is fixed only the height and width of first cell is different.
How can I achieve this.
I'm experimenting with autolayout and am running into trouble with UITableViewCell since they're created at runtime. My cells are loaded from a xib from the main ViewController. This xib has View mode set to Aspect Fill.
I've read about different ways to do this online and have yet to get any of them working. What's considered the best way to handle this?
It looks like your constraints aren't set properly, as the cell is shorter than the image's height.
Using AutoLayout and self-sizing cells is the easiest way to handle what you want to do. Once your constraints are setup properly for your custom cell, tableView:cellForRowAtIndexPath: can call dequeueReusableCellWithIdentifier:forIndexPath: and all the subview layout will be handled for you.
See the detailed walkthrough by smileyborg in his answer to Using Auto Layout in UITableView for dynamic cell layouts & variable row heights.
He also provides workarounds for the minor issue with the initial cell width being based on the storyboard cell, instead of the tableView width. I worked around it by setting the cell's initial width to the tableView's width, as Rasputin had suggested.
I'm using UICollectionViewFlowLayout. My cells contain UILabels that differ in height (number of lines).
It seems that the best way to get the cell height would be in the subclass of UICollectionViewCell, because that is where I set the layout and have access to intrinsic size of my views, BUT:
collectionView: layout: sizeForItemAtIndexPath: is called before the collectionView: cellForItemAtIndexPath: delegate method, which means that I need to know the cell height before I have the actual layout of the cell.
Everything I came up with so far seems too complicated, like starting with fixed cell height, referencing actual height after labels in the cell load and reloading the data again with correct height. Is there a better way to do this?
Unfortunately, no.
For dynamically sized items in UICollectionView you need to know or compute the size of the cell before it is created. The traditional way to do this is to store the data for each row in an array and then compute the size of that data in collectionView:layout:sizeForItemAtIndexPath:.
For example, if you had an array of text to be displayed you could store the NSString objects in an array, measure that string in collectionView:layout:sizeForItemAtIndexPath: and return the size. UICollectionView then takes that size and calls initWithFrame: or setFrame: when configuring your cell's view.
It's also not a bad idea to cache these sizes if they don't change often.
I made a UICollectionView with a vertical scroll.
The width of the cell is more than than the screen width, so I created a customFlowLayout based on UICollectionViewFlow layout returning the right calculated content size.
However, this doesn't work. When the width of the cell is less than the screen width it works. Does it mean that we can't have width more than than screen width in vertical scroll?
It is the same for horizontal scroll, but then the height of the CollectionView is limited to screen height.
Is there any way to make it work?
As others have already said, the UICollectionView can only scroll one direction using a flow layout. However you can accomplish this very easily without creating a custom layout or using a third party library.
When you lay your view out in story board, you can put your UICollectionView embedded in a UIScrollView. Have the scrollview set up to scroll horizontally and the UICollectionView to scroll Vertically. Then set the UICollectionView.delaysContentTouchesto true so touches will pass through to the UIScrollView and not think you are trying to scroll the collectionview.
When you set up the UICollectionView, set it's size and the size of the cells to be what you actually want them to be (Wider than the actual screen) and lay them out accordingly.
Now in the containing UIViewController put this code in the view lifecycle
- (void)viewDidLayoutSubviews {
self.myScrollView.contentSize = self.myCollectionView.frame.size;
}
That's literally all you have to do to accomplish what you are describing. Now your scrollview should allow you to scroll horizontally to view your entire cell and your collectionView should scroll vertically through your cells.
Happy programming.
I'm not sure I've understood your problem.
But if you have made a custom layout, make sure you have implemented :
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
and that your layout attributes frame and size are set with correct values for your "large cell" index path.
Also make sure you have implemented :
- (CGSize) collectionViewContentSize;
This method returns the contentSize of the collection View. If contentSize.width > youAppFrame.width you should have horizontal scrolling. Same for height and vertical scrolling.
Also make sure your collectionView allows scrolling and that your layout is prepared correctly using :
- (void)prepareLayout
By the way, for your layout have you overloaded UICollectionViewLayout or UICollectionViewFlowLayout ?
Before you can do that you MUST use a different type of layout. The flow layout represents its items as a list and it spans these items in cells based on the available width.
If you want to have both horizontal and vertical scrolling you need to somehow specify the number of columns for your grid. the FlowLayout doesn't have that. A simple sollution is to make a subclass of UICollectionViewLayout and override collectionViewContentSize to make it retun a width = to the added sum of the cells widths of one row (this is where knowing how many collumns you want is necessary), plus any additional spacing between them. This will work fine if your cells have the same size per column, similar to a grid.
You should embed a UITableView into a UIScrollView.
ScrollView and TableView will have the same height but different widths.
This way UITableView will scroll vertical and UIScrollView will scroll horizontal.
Xcode 11+, Swift 5.
I solved my issue, I prepared video and code
When I want to have a custom cell, I generally add a UIView subclass to the cell's content view. For the layout of my subviews I use a nib. Then I wire up the nib to my UIView subclass. My issue is how to dynamically size content. Say my view has a lot UILabels inside it. I use layoutSubviews to position all the subviews - but it is only until that is done that I truly know the height of my cell. So currently in tableView:heightForRowAtIndexPath I setup my subview and call layoutIfNeeded so everything is positioned properly. Now I know the height of my cell and return it in the tableView:heightForRowAtIndexPath method. But now when tableView:cellForRowAtIndexPath is called the cell that I am given has a height of 44.0. When I added my subview to it - my subview is outside of its parent's bounds. Then when the cell is later resized in iOS to the height that I said I needed, my content is thrown off because of my autoresizingMask. Just trying to figure out if this is an issue others deal with or if I'm approaching it completely wrong. It just seems backwards that we ask for the height, then create a cell that is not that height.
Unfortunately, this is how UITableViews work: You need to provide the heights before the UITableViewCells are actually rendered.
And yes, everyone has to deal with it. :)
You could create an NSArray, add all your custom contentViews, set their frame according to the expected contentView bounds and then use this array as data source in tableView:heightForRowAtIndexPath: and tableView:cellForRowAtIndexPath:. While this isn't exactly efficient, it works fine for small data sets.
Also, here's a nice tutorial with this topic:
UITableViewCell Dynamic Height (by Matt Long)
A similar question on SO:
How can I do variable height table cells on the iPhone properly?