Autoresize UICollectionViewCell size inside UITableView - ios

I am trying to create simple educational app about movies, and I need to create movie frames horizontal scroller.
I create collection view for that:
But if I build it for another target (iPhone 6s Plus for example), I have this:
How to solve this problem?
My scene controller code (Swift 3):
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
cell.backgroundColor = UIColor.black
// Configure the cell
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}

I think that you can try to implement the method of UICollectionView delegate:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(yourHeight, self.view.frame.size.width)
}
in this way, your collectionView-cell inside in tableView will appear as big as the screen.

First I want to say something about UITableViewAutomaticDimension you use, in case you not exactly understand how it works.
Your expectations probably are, that UITableViewAutomaticDimension will infer tableView to make a cell with size of entire tableView, but it doesn't work this way.
UITableViewAutomaticDimension is just infers tableView, that inner size of your cell will be used as a returned here; so if you add some constraints, views with constraints, or whatever inside your tV, its size will be used.
and second - your comment doesn't make sense at all for me, because your posted code isn't related to cell of collectionView and I can't comment anything related to size of its cells
EDITED:
to make all cells with width of your screen, return correct size from delegate method of collectionViewFlowLayout - sizeForItem

Related

UICollectionView cellForItemAt is called twice when scrolling

I have a UICollectionView with the following settings:
Estimate Size = None
Content Insets = Never
Scroll Direction = Vertical
Paging Enabled = true
When I scroll to the second cell, the cellForItemAt method is triggered twice, with indexes 1 and 2 at the same time, and accordingly, when I swipe to the last cell, the cellForItemAt method does not work.
cellForItemAt does not work correctly only on swiping to the second cell, in all other cases it is OK (one index per swipe)
I also noticed that cellForItemAt is triggered not as soon as a new visible cell appears, but with a delay (about 100 pixels on top, the standard height is 568 pixels of cells)
Because of this problem, I do not work cellForItemAt on the last cell, also in the cells there are photos that are loaded by URL (I tried without loading images, the problem is the same), and they are in random cells, this is also a problem.
I do not know how to explain and solve this issue, thank you for any help
UP
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UINib(nibName: TestCell.identifier, bundle: nil), forCellWithReuseIdentifier: TestCell.identifier)
collectionView.dataSource = self
collectionView.delegate = self
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("cellForItemAt: \(indexPath.item)")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TestCell.identifier, for: indexPath) as! TestCell
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
If I scroll to the second cell, the cellForItemAt works twice, and when the last cell, the cellForItemAt does not work at all. Accordingly, the configuration methods for the last cell do not work. I gave a simple example, but the problem is the same.
How do I load a cell on a user's scroll correctly?
As #PaulW11 said in his comment, your data source's collectionView(_:cellForItemAt:) method can be called at any time, with any indexPath. You should code your data source to provide any cell, for any valid indexPath, at any time, in any order.
The only limitation is that the collection view first calls your data source's numberOfSections(in:) and collectionView(_:numberOfItemsInSection:) methods, and won't ask for a cell for an indexPath that is out of the valid ranges indicated by those methods. Other than that, you should not make any assumptions about the cells the collection view will ask for, or the order in which it requests them.

Need to show all items, one item in a row

I have a collectionView that is populated from Firebase Realtime Database. It should work as whatsapp.
I have created a collectionView cell to show the messages in a label.
I have set the collectionView scroll direction to vertical.
I have set the cell height to 40, and here you have the method sizeForItemAt:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width:collectionView.frame.size.width, height:40)
}
The items are shown, but not at full width and not one item per row.
Here you have a screenshot:
What do I need to change to get each item in a row?
Seems the method is not getting called here. You need to set the delegate of collectionView to the class. Add this to viewDidLoad.
collectionView.delegate = self
Suggested Approach: My suggested approach for your requirement is to use UITableView. All the cells extend the entire width of the UITableView. You just need to provide the height of the cell by this method.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
40
}

How to set the height of a uicollectionview inside an uitableviewcell?

I have a viewcontroller which manages an uitableview with uicollectionviews in the uitableviewcells.
However, the uicollectionviews may contain varying numbers of items.
Therefore I need to adjust the height of the uicollectionviews.
But when I try to do this the heights of the other uicollectionviews are also changed.
The uicollectionview is bound by an IBOutlet, as well as the constraint for the height of the collection view.
I'm setting the height of the collectionview in
tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath):
cell.collectionView.reloadData();
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height;
cell.collectionView.setNeedsLayout();
cell.collectionView.layoutIfNeeded();
First I'm reloading the data and then I'm setting the height constraint and finally I'm applying the new layout.
This seems to work at first - but I recognized that the other cells are also effected - probably because I'm dequeueing the reusable cell.
But how can I change my code, such that it works?
You should implement 'tableView:heightForRowAtIndexPath:'
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50.0
// Or customize the way you want using the indexPath
}
or for UICollectionView 'collectionView:sizeForItemAtIndexPath:'
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: widthPerItem, height: heightPerItem)
}

UICollectionView items order not reversed in right to left languages

I noticed a big issue where in right to left languages, the cells order is not properly reversed, only the alignment is correct. But only for horizontal flow layout, and if the collection view contain different cell sizes! Yes, I know this sound insane. If all the cells are the same size, the ordering and alignment is good!
Here is what I got so far with a sample app (isolated to make sure this is the actual issue, and not something else):
(First bar should always be drawn blue, then size increase with index.)
Is this an internal bug in UICollectionViewFlowLayout (on iOS 11)? Or is there something obvious that I am missing?
Here is my test code (Nothing fancy + XIB with UICollectionView):
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 6
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "test", for: indexPath)
cell.backgroundColor = (indexPath.item == 0) ? UIColor.blue : UIColor.red
return cell
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (10 * indexPath.item) + 20, height: 170)
}
Automatic right-to-left support on dynamically-sized UICollectionViews is not a supported configuration. For this to work, you need to explicitly sign up for automatic layout mirroring as follows:
Create a subclass of UICollectionViewFlowLayout
Override flipsHorizontallyInOppositeLayoutDirection, and return true in Swift or YES in Objective-C
Set that as the layout of your collection view
This property is defined on UICollectionViewLayout (parent of Flow), so you can technically use this property on any custom layout you already have.
I believe that for this you will have to implement your own custom collectionViewLayout - although I understand that one would expect that it would automatically work just as the right-to-left on the rest of the components.

Variable width & height of UICollectionViewCell using AutoLayout with Storyboard (without XIB)

Here is an design issue in the app that uses AutoLayout, UICollectionView and UICollectionViewCell that has automatically resizable width & height depending on AutoLayout constraints and its content (some text).
It is a UITableView list like, with each cell that has it's own width & height calculated separately for each row dependant on its content. It is more like iOS Messages build in app (or WhatsUp).
It is obvious that app should make use of func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize.
Issue is that within that method, app cannot call func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell nor dequeueReusableCellWithReuseIdentifier(identifier: String, forIndexPath indexPath: NSIndexPath!) -> AnyObject to instantiate cell, populate it with specific content and calculate its width & height. Trying to do that will result in an indefinite recursion calls or some other type of app crash (at least in iOS 8.3).
The closest way to fix this situation seems to copy definition of the cell into view hierarchy to let Auto-layout resize "cell" automatically (like cell to have the same width as parent collection view), so app can configure cell with specific content and calculate its size. This should definitely not be the only way to fix it because of duplicated resources.
All of that is connected with setting UILabel.preferredMaxLayoutWidth to some value that should be Auto-Layout controllable (not hardcoded) that could depend on screen width & height or at least setup by Auto-layout constraint definition, so app can get multiline UILabel intrinsic size calculated.
I would not like to instantiate cells from XIB file since Storyboards should be today's industry standard and I would like to have as less intervention in code.
EDIT:
The basic code that cannot be run is listed bellow. So only instantiating variable cellProbe (not used) crashes the app. Without that call, app runs smoothly.
var onceToken: dispatch_once_t = 0
class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
dispatch_once(&onceToken) {
let cellProbe = collectionView.dequeueReusableCellWithReuseIdentifier("first", forIndexPath: NSIndexPath(forRow: 0, inSection: 0)) as! UICollectionViewCell
}
return CGSize(width: 200,height: 50)
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("first", forIndexPath: NSIndexPath(forRow: 0, inSection: 0)) as! UICollectionViewCell
return cell
}
}
If you are using constraints, you don't need to set a size per cell. So you should remove your delegate method func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
Instead you need to set a size in estimatedItemSize by setting this to a value other than CGSizeZero you are telling the layout that you don't know the exact size yet. The layout will then ask each cell for it's size and it should be calculated when it's required.
let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.estimatedItemSize = someReasonableEstimatedSize()
Just like when dynamically size Table View Cell height using Auto Layout you don't need call
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
in
optional func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
the proper way is create a local static cell for height calculation like a class method of the custom cell
+ (CGFloat)cellHeightForContent:(id)content tableView:(UITableView *)tableView {
static CustomCell *cell;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cell = [tableView dequeueReusableCellWithIdentifier:[CustomCell cellIdentifier]];
});
Item *item = (Item *)content;
configureBasicCell(cell, item);
[cell setNeedsLayout];
[cell layoutIfNeeded];
CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height + 1.0f; // Add 1.0f for the cell separator height
}
and I seriously doubt storyboard is some industry standard. xib is the best way to create custom cell like view including tableViewCell and CollectionViewCell
To calculate the CollectionView cell's size dynamically, just set the flow layout property to any arbitrary value:
let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
flowLayout.estimatedItemSize = CGSize(width: 0, height: 0)

Resources