Can you solve the following problem: I have a UICollectionView with a fixed number of CollectionView Cells - but the hight of the devices change and I have to dynamically calculate the last(buttom) cell.
------
I: Cell 1
------
I: Cell 2
------
I: Cell 3, This cell has a dynamic hight
I
------
I have tried the following:
note: let view = self.cells[indexPath.row] is a fixed list of UIView that I add to the UICollectionView contextView in cellForItemAt
Also the code is in a class that implement UICollectionViewDelegateFlowLayout
self.collectionView?.isScrollEnabled = false
var totalHight: CGFloat = 0
public func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let view = self.cells[indexPath.row]
self.totalHight = self.totalHight + view.frame.size.height
if (self.totalHight > self.frame.size.height){
let diff = self.totalHight - self.frame.size.height
let height = (view.frame.size.height - diff)
view.frame.size = CGSize(width: self.frame.size.width,
height: height)
self.totalHight = 0
return view.frame.size
}else{
return CGSize(width: self.frame.size.width, height: view.frame.size.height)
}
}
I can't tell if that code you've tried is in your view controller or a custom layout. What was the result?
This can be done with a custom UICollectionViewLayout instance. You can probably get by with subclassing UICollectionViewFlowLayout and overriding
collectionView:layout:sizeForItemAtIndexPath:
Then you can look at the remaining space on the screen. It will probably pose problems if the content ends up exceeding the available space.
One approach you might look at is getting the layout attributes for the previous cell, and that will give you its frame, something like this:
let previousIndexPath = IndexPath(row: indexPath.row - 1, section: indexPath.section)
let attribs = layoutAttributesForItem(at: previousIndexPath)
let previousFrame = attribs.frame
let availableHeight = totalHeight - previousFrame.size.height
return CGSize(width: previousFrame.width, height: availableHeight)
See more here:
https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html
https://developer.apple.com/documentation/uikit/uicollectionviewlayout/1617797-layoutattributesforitem
Edit: as an aside, it sounds like maybe using UIStackView and autolayout constraints would make this thing much easier, since your content is static and doesn't scroll.
Related
I need to make UICollectionView cells in oval shape where height is fixed but width is dynamic and it has a limit also, if text longer than that, then text should scroll. Any third party option available for this or need to create own using UICollectionView. Please guide.
Below is the image what i am trying to achieve. I want to know before starting should i look for third parties or use UICollectionView to make own. I have short time to complete that's why to avoid time on searching asking in starting itself which direction to follow.Please guide.
You can use a UICollectionViewFlowLayout and Auto Layout to achieve this.
Create a UICollectionViewCell with a container view.
Pin this container view the edges of the cell with auto layout
Add a UILabel to this container view and pin it to all edges of the container view (give it a background color to distinguish from the cell background)
In the UICollectionViewCell subclass you'll want to round the corners of the container view, e.g. self.containerView.layer.cornerRadius = self.containerView.height / 2
In the UICollectionViewFlowLayoutDelegate method, estimatedSizeForItem return an approximate size for the cell (auto layout will calculate the actual size.)
The important thing to remember is your cell needs to have enough constraints so that the auto layout engine can calculate the actual height and width based on the content.
Edit: If you want a fixed height, ensure your label can only have a single line. Or add a height constraint.
Finally, i found a library TagListView that can be installed through cocoapods with lots of customisation and swift 4 support also.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let nw = intersts[indexPath.row]
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let size = CGSize(width: 250, height: 1500)
let estimatedFrame = NSString(string: nw).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font : UIFont.systemFont(ofSize: 17)], context: nil)
let attributes = [NSAttributedStringKey.font : UIFont.systemFont(ofSize: 17)]
let yourLabelSize: CGSize = nw.size(withAttributes:attributes )
var width1 = yourLabelSize.width + 30
if width1 < 30 {
width1 = 30
}
return CGSize(width: estimatedFrame.width+20, height: estimatedFrame.height+20)
}
I just extend or implement #Tim answer. So after you build the cell as described in his answer, then specify the cell width and height to be flexible using the sample code below i.e similar to what he described
let collectionViewLayout = UICollectionViewFlowLayout()
collectionViewLayout.scrollDirection = .horizontal
let itemWidth = Constants.myCollectionViewItemWidth
let itemHeight = Constants.myCollectionViewItemHeight
collectionViewLayout.estimatedItemSize = CGSize(width: itemWidth, height: itemHeight)
myCollectionView.collectionViewLayout = collectionViewLayout
I'm trying to create an UI like below.
For this purpose I'm using UICollectionView and FlowLayout.
For showing the first cell with full width and remaining cell as 3 column, I've implemented the sizeForItemAtIndexPath: and minimumInteritemSpacingForSectionAtIndex methods:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
var cellSize = CGSizeZero
if indexPath.item == 0
{
let width = self.cvMedia.frame.width - 20
let height = (width * 9)/16
cellSize = CGSize(width: width, height: height)
}
else
{
let width = self.cvMedia.frame.width / 3 - 20
let height = (width * 16)/9
cellSize = CGSize(width: width, height: height)
}
return cellSize
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat
{
return 20.0
}
But I'm getting the following output in iPhone 6SPlus and iPhone 6Plus:
I tried by changing the item spacing from 20 to 19 and it worked on simulator, but on actual device it still shows the same behaviour. If I change the spacing values I'm getting the correct output on some device versions, and not working on some versions. I can add a device version check and based on that I can return the value. But it is not an elegant solution and it will break on future versions. Can anyone help me to solve this issue ? Thanks in advance.
You should use the collection view's width as your parameter when making this layout.
For instance,
You need 3 cells to fit the view.
The cells must have 20pt spacing in between them.
Solution:
Calculate the size of the cell at runtime either on viewWillAppear() or cellForRowAtIndexPath()
Scenario:
Width of device is 320pt. Collectionview's width is 300pt( spacing 10pt L & R) Spacing : 20pt between cells. Cell's needed 3!!
Cell Size: ?
Now..Start calculating in either method.
Cell Spacing = (3-1) * 20 = 2 * 20 = 40pt
Remaining collection view width = 300pt - 40pt = 260pt
Cell size = 260pt/3 = 86.667 pt. (86pt apprx).
In Code:
cellWidth = ( collectionView?.bounds.width - minimumSpacing * (numberOfCellsInARow - 1 ) ) / numberOfCellsInARow
Also, this only gives the cellWidth. You need cellHeight as well.
You might already have an aspect ratio for the cell. Using that, calculate the cellHeight as well.
Kind Regards,
Suman Adhikari
Check out this and change the values for min spacing to 0 for both cells and lines
In my app I am using using UICollectionView. I want to set fix space between UICollectionViewCell. so how can I do this?
here is my code:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
return 20
}
by this line I can set space between cell?
here is my screenshot please see this. and let me know how can i set fix distance in both landscape or portrait mode
Maybe,you need implementation follow method.
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
You can adjust the spacing between UICollectionCell by using the storyboard Min. spacing property of UICollectionView.
Here you have to set Min spacing value for cells and lines.
Hope it would help you.
You can also manage the spacing using the size inspector of Collection View
http://i.stack.imgur.com/Nvp3g.png
If you want exact spacing between the cells you need to calculate the size for the cells that will best fit to allow for the spacing you need without wrapping while considering the sectionInsets and CollectionView bounds. eg.
let desiredSpacing: CGFloat = 20.0
if let layout = self.collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
layout.minimumLineSpacing = desiredSpacing
let totalCellsContentWidth = self.collectionView!.bounds.width - layout.sectionInset.left - layout.sectionInset.right
let numberOfCellsPerRow: CGFloat = 10
let numberOfSpacesPerRow = numberOfCellsPerRow - 1
let cellWidth = (totalCellsContentWidth - (numberOfSpacesPerRow * desiredSpacing) / numberOfCellsPerRow)
layout.itemSize = CGSize(width: cellWidth, height: cellWidth)
}
Assuming your cells are the same size the minimumLineSpacing is simple but would otherwise expand to fit the largest cell on the row before wrapping to the next line. As you can see it's the cell spacing that is a bit more complicated.
I am trying to achieve a custom layout like this one :
I am trying to implement it via a UICollectionView. First I use this code to have the desired size :
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize{
return CGSizeMake((collectionView.frame.size.width / 2) - 2 , (collectionView.frame.size.height / 3) - 2)
}
It's working fine.
The problem is that my picture is not properly centered. I did it this way :
Let me explain :
One constraint to align the center of the uiimage on X
One constraint to do the same thing on Y
One constraint to keep the image ratio
One constrain to say that the height of the image is 70% of the cell height
And the result is very not the one expected :
i did it using custom layout.
let collLayout:UICollectionViewFlowLayout = layout as! UICollectionViewFlowLayout
collLayout.scrollDirection = .Vertical
collLayout.minimumInteritemSpacing = 10
let width = (frame.size.width - 3*collLayout.minimumInteritemSpacing)*0.5
collLayout.itemSize = CGSizeMake(width, width)
collLayout.sectionInset = UIEdgeInsetsMake(0, 10, 0, 10)
And then initialize your collection view with this custom layout.
I would like to populate UICollectionView in reverse order so that the last item of the UICollectionView fills first and then the second last and so on. Actually I'm applying animation and items are showing up one by one. Therefore, I want the last item to show up first.
Swift 4.2
I found a simple solution and worked for me to show last item first of a collection view:
Inside viewDidLoad() method:
collectionView.transform = CGAffineTransform.init(rotationAngle: (-(CGFloat)(Double.pi)))
and inside collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) method before returning the cell:
cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
(optional) Below lines will be necessary to auto scroll and show new item with smooth scroll.
Add below lines after loading new data:
if self.dataCollection.count > 0 {
self.collectionView.scrollToItem(at: //scroll collection view to indexpath
NSIndexPath.init(row:(self.collectionView?.numberOfItems(inSection: 0))!-1, //get last item of self collectionview (number of items -1)
section: 0) as IndexPath //scroll to bottom of current section
, at: UICollectionView.ScrollPosition.bottom, //right, left, top, bottom, centeredHorizontally, centeredVertically
animated: true)
}
I'm surprised that Apple scares people away from writing their own UICollectionViewLayout in the documentation. It's really very straightforward. Here's an implementation that I just used in an app that will do exactly what are asking. New items appear at the bottom, and the while there is not enough content to fill up the screen the the items are bottom justified, like you see in message apps. In other words item zero in your data source is the lowest item in the stack.
This code assumes that you have multiple sections, each with items of a fixed height and no spaces between items, and the full width of the collection view. If your layout is more complicated, such as different spacing between sections and items, or variable height items, Apple's intention is that you use the prepare() callback to do the heavy lifting and cache size information for later use.
This code uses Swift 3.0.
//
// Created by John Lyon-Smith on 1/7/17.
// Copyright © 2017 John Lyon-Smith. All rights reserved.
//
import Foundation
import UIKit
class InvertedStackLayout: UICollectionViewLayout {
let cellHeight: CGFloat = 100.00 // Your cell height here...
override func prepare() {
super.prepare()
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttrs = [UICollectionViewLayoutAttributes]()
if let collectionView = self.collectionView {
for section in 0 ..< collectionView.numberOfSections {
if let numberOfSectionItems = numberOfItemsInSection(section) {
for item in 0 ..< numberOfSectionItems {
let indexPath = IndexPath(item: item, section: section)
let layoutAttr = layoutAttributesForItem(at: indexPath)
if let layoutAttr = layoutAttr, layoutAttr.frame.intersects(rect) {
layoutAttrs.append(layoutAttr)
}
}
}
}
}
return layoutAttrs
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let layoutAttr = UICollectionViewLayoutAttributes(forCellWith: indexPath)
let contentSize = self.collectionViewContentSize
layoutAttr.frame = CGRect(
x: 0, y: contentSize.height - CGFloat(indexPath.item + 1) * cellHeight,
width: contentSize.width, height: cellHeight)
return layoutAttr
}
func numberOfItemsInSection(_ section: Int) -> Int? {
if let collectionView = self.collectionView,
let numSectionItems = collectionView.dataSource?.collectionView(collectionView, numberOfItemsInSection: section)
{
return numSectionItems
}
return 0
}
override var collectionViewContentSize: CGSize {
get {
var height: CGFloat = 0.0
var bounds = CGRect.zero
if let collectionView = self.collectionView {
for section in 0 ..< collectionView.numberOfSections {
if let numItems = numberOfItemsInSection(section) {
height += CGFloat(numItems) * cellHeight
}
}
bounds = collectionView.bounds
}
return CGSize(width: bounds.width, height: max(height, bounds.height))
}
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
if let oldBounds = self.collectionView?.bounds,
oldBounds.width != newBounds.width || oldBounds.height != newBounds.height
{
return true
}
return false
}
}
Just click on UICollectionView in storyboard,
in inspector menu under view section change semantic to Force Right-to-Left
I have attach an image to show how to do it in the inspector menu:
I'm assuming you are using UICollectionViewFlawLayout, and this doesn't have logic to do that, it only works in a TOP-LEFT BOTTOM-RIGHT order. To do that you have to build your own layout, which you can do creating a new object that inherits from UICollectionViewLayout.
It seems like a lot of work but is not really that much, you have to implement 4 methods, and since your layout is just bottom-up should be easy to know the frames of each cell.
Check the apple tutorial here: https://developer.apple.com/library/prerelease/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/CreatingCustomLayouts/CreatingCustomLayouts.html
The data collection does not actually have to be modified but that will produce the expected result. Since you control the following method:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
Simply return cells created from inverting the requested index. The index path is the cell's index in the collection, not necessarily the index in the source data set. I used this for a reversed display from a CoreData set.
let desiredIndex = dataProfile!.itemEntries!.count - indexPath[1] - 1;
Don't know if this still would be useful but I guess it might be quite useful for others.
If your collection view's cells are of the same height there is actually a much less complicated solution for your problem than building a custom UICollectionViewLayout.
Firstly, just make an outlet of your collection view's top constraint and add this code to the view controller:
-(void)viewWillAppear:(BOOL)animated {
[self.view layoutIfNeeded]; //for letting the compiler know the actual height and width of your collection view before we start to operate with it
if (self.collectionView.frame.size.height > self.collectionView.collectionViewLayout.collectionViewContentSize.height) {
self.collectionViewTopConstraint.constant = self.collectionView.frame.size.height - self.collectionView.collectionViewLayout.collectionViewContentSize.height;
}
So basically you calculate the difference between collection view's height and its content only if the view's height is bigger. Then you adjust it to the constraint's constant. Pretty simple. But if you need to implement cell resizing as well, this code won't be enough. But I guess this approach may be quite useful. Hope this helps.
A simple working solution is here!
// Change the collection view layer transform.
collectionView.transform3D = CATransform3DMakeScale(1, -1, 1)
// Change the cell layer transform.
cell.transform3D = CATransform3DMakeScale(1, -1, 1)
It is as simple as:
yourCollectionView.inverted = true
PS : Same for Texture/IGListKit..