UICollectionView horizontal columns with vertical scroll - ios

I'm trying to get a UICollectionView to display horizontal columns when on iPad.
This is my current layout when on iPhone (Collection view scrolls vertically):
Each UICollectionViewCell has a dynamic height depending on the content but the width is set to 100% of the screen width.
The cell height is set like this:
if let flowLayout = collectionView?.collectionViewLayout
as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = CGSize(width: 1, height: 1)
}
It's a standard UICollectionView using the usual dataSource/delegate methods (sizeForItemAtIndexPath, cellForItemAtIndexPath etc.)
When on iPad, I'm adjusting the width depending on the number of columns required:
var numberOfColumns = 1
if screenWidth > 760 {
numberOfColumns = 2
}
let cellWidth = screenWidth/numberOfColumns
Adjusting the width of the columns gives me this:
But i'm trying to get something like this:
Is there a way to achieve this using UICollectionView?
A few notes about the collection view:
Layout is set to Flow
Scroll direction is vertical
Headers are a fixed height
Cell height is dynamic based on content
I need the collection view to scroll vertically if the content extends outside of the screen

Related

how to make scrollview grow based on collection view number of rows in ios?

i have a scrollview which has a picture, text, button, label and then a collectionView whose number of rows is dynamic in nature. Its like collectionview will grow in height and hence the scrollview should assume collectionView height as well as other elements height to get a smooth scroll. How to achieve this using auto Layout in ios?
So my doubt is how to make a dynamic scrollview height based on a growing colelctionview or tableview height.
You can increase the height of CollectionView Height Constraint , connect Height contains to your ViewController and than .
how to connect constraint to outlet ?
let height = self.collectionView.contentSize.height;
self.heightConstraints = height;
self.scrollView.contentSize = CGSize.init(widht:self.scrollView.frame.size.width,height:height)
FOr Swift 4.2
override func viewDidLayoutSubviews() {
let heightFilterCOll = self.filterCollectionView.contentSize.height
self.filterCOllectionViewCOnstraintHeight.constant = heightFilterCOll
self.ScrollView.contentSize = CGSize(width:self.ScrollView.frame.size.width,height:heightFilterCOll + 77)
}
These methods didn't work for me. Try,
self.collectionView.reloadData()
DispatchQueue.main.async {
self.heightCollectionViewConstraint.constant = self.collectionViews.collectionViewLayout.collectionViewContentSize.height
}

UITableViewCell dynamic row height + custom UIView + Auto Layout

I am trying to get a custom photo container with multiple UIImageViews to fit in my tableview cell. The view contains a variable number of images (1 ~ 9), and its height would change correspondingly from 1x to 3x imageHeight.
I used AutoLayout to define the top/bottom/leading/trailing margins with the tableview and the custom UIView inside, and to enable self-sizing cells, I have set
tableView.estimatedRowHeight = X
tableView.rowHeight = UITableViewAutomaticDimension
I initialize these cells with
tableView.register(nib: forCellReuseIdentifier:)
and in tableView(_ tableView: cellForRowAt:) method, I setup the cell with:
let cell = tableView.dequeueReusableCell(
withIdentifier: "test9cell",
for: indexPath) as! SocialFeedTableViewCell
cell.photoContainer.setup(with: urls)
cell.photoContainer.loadImages()
return cell
where setup() hooks each imageView in the container with a URL
func setup(with urls: [URL]) {
self.imageUrls = urls
for i in 0 ..< urls.count {
let imageView = UIImageView(frame: CGRect.zero)
self.addSubview(imageView)
self.imageViews.append(imageView)
}
self.setNeedsLayout()
}
func loadImages() {
self.imageViews.forEach { imageView in
imageView.frame = // Calculate position for each subview
imageView.sd_setImage(...) // Load web image asynchronously
}
}
Defining intrinsicContentSize for the view:
override var intrinsicContentSize {
let frameWidth = self.frame.size.width
var frameHeight: CGFloat
switch self.imageUrls.count { // range from 1...9
case 1...3:
frameHeight = frameWidth / 3
case 4...6:
frameHeight = frameWidth / 3 * 2
default:
frameHeight = frameWidth
return CGSize(frameWidth, frameHeight)
}
override func layoutSubviews() {
super.layoutSubviews()
self.imageViews.forEach { imageView in
imageView.frame = // Calculate position for each subview
}
}
The problem here is: after I set the initial intrinsicContentSize, the container's frame size changes in layoutSubviews() afterwards. Although by then I can position the imageView subviews correctly, the cell height will not be changed anymore.
Hope I am not making this problem more confusing. Could someone point out how would I resize the cell height AFTER modifying the contents of its UIView subview? Thanks!
There are a few things here that will be causing you some issues with dynamically sized cells.
You are adding multiple items to the cell but are not defining any auto layout constraints on the image views, so it does not know how to properly place / stack the items.
from the code above you are adding UIImageView with a frame of CGRectZero, without autulayout rules they will either stay zero or try to adjust to the contentsize when you add an image, but they wont adjust the cells height.
If you are loading images from the network they will likely be added/rendered after the tableviewcell has done its initial rendering. So you will likely need to cache the loaded images and reload the cell so that they can load in at the right time.
Now this last point is alot more complicated.
Dynamic UITableViewCell's calculate their height based on the autolayout rules of the content within them. You MUST have enough constraints from your content to the UITableViewCell's contentView property (to all edges) that give the cell enough information to place each item, calculate its overall height and width and therefore it is able to calculate the new height.
Using just one image isn't too bad for dynamic sized cells. but placing multiple items dynamically without any rules after the cell has initially rendered will not work.
You need to decide how these images should be laid out in your cell. once you have this you can look at adding the required constraints as you add the image views. Personally I would only add the image views once i have received each image.
Previously when I have done this I have cached the images once received and re-loaded the cell so that the image can be placed in the cell as it is rendered, allowing the tableview cell to calculate its height based off the image dimensions and constraints.
You may want to also consider using a collection view or merging the images together into a single image and using that in your cell. You could do this on device at runtime or server side if you have that kind of access to the images.

UICollectionView AutoLayout max width?

I'm having a dilemma maybe someone can help. I followed this tutorial that showed me how to dynamically adjust the height of a collectionview to match the height of the text.
https://possiblemobile.com/2016/02/sizing-uicollectionviewcell-fit-multiline-uilabel/
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.estimatedItemSize = CGSizeMake(1, 1) }
cell.label.preferredMaxLayoutWidth = 50
But the issue I'm having is now the collection view wraps around the text instead of conforming to collectionView.bounds.width. So when i have text that is smaller than the collectionview width, the cell wraps around the text. I want the cell to match the height of the text but have a set width set to collectionview bounds. How could I achieve this?

Dynamic height TableView in a Scroll View

I am designing a page having a scroll view and above it a table view(scroll disabled). For doing this I have referred answers in this question - Make UITableView not scrollable and adjust height to accommodate all cells ,but wasn't successful.
Hierarchy of views along with provided constraints-
-Main View
-Scroll view
pinned to all sides of main view(0,0,0,0), constraint to margins
-Content View
pinned to scroll view(0,0,0,0),equal width to main view,equal height to main view(priority - 250)
-Table view inside content view
scroll disabled,having 50 point spaces from all sides,Height(>=),bottom spacing 50(relation >=).I have put greater than equal so as to increase height dynamically.
Now when I populate my table view I use the code as
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableview.dequeueReusableCellWithIdentifier("cellreuse", forIndexPath: indexPath)
cell.textLabel?.text = name[indexPath.row]
tableview.frame.size = tableview.contentSize
return cell
}
So when I run my code, it increases the tableview frame but doesn't stretch the content size and it just becomes weird as my scroll view doesn't scroll to the end of the table view neither my table view obeys the auto layout constraints.
Just I needed to do this -
remove the line - tableView.frame.size = tableView.contentSize
Add a height constraint for table view.
Set priority to High
Create an outlet of the height constraint(Ctrl+Drag).
Wherever you need to reload data of your table, set the height constraint to tableview's content height.
tableHeightConstraint.constant = tableview.contentSize.height
Assign a table height. Let it be constant 0.
Just add below lines.
tableView.heightConstant.constant = CGFloat.greatestFiniteMagnitude
tableView.reloadData()
tableView.layoutIfNeeded()
tableView.heightConstant.constant = tableView.contentSize.height
With this, you can easily achieve dynamic table height. Working on iOS 13, Swift 5.
Had the same issue and resolved it by doing the following:
Create an outlet of the height constraint for the table view with a priority of 1000
#IBOutlet private weak var tableViewHeight: NSLayoutConstraint!
On viewDidLayoutSubview call layoutIfNeeded on the table view and then set the table view height constraint to the height of the content view
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.layoutIfNeeded()
tableViewHeight.constant = tableView.contentSize.height
}
Tested on iOS 14.1 and iOS 16.1

Autoresize UICollectionView height to adjust to its content size

I have a UICollectionView, a button that creates a new cell in collection view. And I want UICollectionView to adjust it's size according to it's content size (when there are one or two cells then UICollectionView is short, if there are a lot of cell UICollectionView is big enough).
I know how to get content size:
collectionView.collectionViewLayout.collectionViewContentSize
But I have no idea where to use this value. I would appreciate if somebody help me to figure out how to make UICollectionView auto adjust it's height.
UPD:
I published on GitHub a demo project that describes the problem: https://github.com/avokin/PostViewer
I don't think content size is what you're after. I think you're wanting to adjust the amount of screen real estate consumed by the collection view, right? That's going to require adjustment of the frame. The content size includes the off-screen (scrolling) area as well as the on screen view.
I don't know of anything that would prevent you from just changing the frame size on the fly:
collectionView.frame = CGRectMake (x,y,w,h);
[collectionView reloadData];
If I'm understanding you correctly.
Use a height constraint for the collection view and update its value with the content height when needed. See this answer: https://stackoverflow.com/a/20829728/3414722
Steps to change the UICollectionView frame:
Set the "translatesAutoresizingMaskIntoConstraints" property to YES for the collectioview's superview (If you are using AUTOLAYOUT)
Then update the collectioview's frame as :
collectionView.frame = CGRectMake (x,y,w,h);
[collectionView reloadData];
You need to constrain the collection view height to the height of your content:
I'm using SnapKit in the following code.
First constrain the collection view edges to its superview:
private func constrainViews() {
collectionView?.translatesAutoresizingMaskIntoConstraints = true
collectionView?.snp.makeConstraints { make in
make.edges.equalToSuperview()
heightConstraint = make.height.equalTo(0).constraint
}
}
Next calculate the height and set the height to the height constraint offset. I'm letting the flow layout do the work, and then calculating the height based on the bottom edge of the last layout attribute:
override func viewDidLayoutSubviews() {
guard
let collectionView = collectionView,
let layout = collectionViewLayout as? UICollectionViewFlowLayout
else {
return
}
let sectionInset = layout.sectionInset
let contentInset = collectionView.contentInset
let indexPath = IndexPath(item: tags.count, section: 0)
guard let attr = collectionViewLayout.layoutAttributesForItem(at: indexPath) else {
return
}
// Note sectionInset.top is already included in the frame's origin
let totalHeight = attr.frame.origin.y + attr.frame.size.height
+ contentInset.top + contentInset.bottom
+ sectionInset.bottom
heightConstraint?.update(offset: totalHeight)
}
Note that in the example, I always have one special tag not included in my items tags count, so the line:
let indexPath = IndexPath(item: tags.count, section: 0)
would need to be something like if items.count > 0 ... let indexPath = IndexPath(item: tags.count - 1, section: 0) in most other code.

Resources