I have a UICollectionView with a button to create cells, which should appear in the order of creation (1,2,3,4 across and down as space permits). The text views are constrained with flexible widths to fill the cell. The size of the cell depends on the device and rotation to allow 1, 2, 3 or 4 cells per row as follows:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
var size = collectionView.bounds.size // size of collection view
switch size.width
{
case 0...450:
println(size.width)
return CGSize(width: (size.width - 10), height: 145)
case 451..<768:
println(size.width)
return CGSize(width: ((size.width - 10) / 2), height: 145)
case 768:
println(size.width)
return CGSize(width: ((size.width - 10) / 3), height: 145)
default:
println(size.width)
return CGSize(width: 248, height: 150) // in case there is no size
}
}
With left and right insets set to 0, this sort of works - but only if I rotate the device and add a cell. In detail:
Simulating an iPhone 5, if cells are added in portrait they appear below each other, as intended, but when the device is rotated, the cells remain in a vertical column instead of two cells per row. If I add a cell while in landscape, the cells then arrange themselves two cells per row, which is correct (except for having to add a cell to make it happen).
Simulating an iPad, if cells are added in portrait, instead of three, only two cells are added per row with space for a third cell between them.
Continuing on iPad, if the device is rotated, instead of four, only three cells appear in each row with space between them. Similar to the iPhone, if another cell is added while in landscape mode, four cells appear per row as expected, AND if the device is rotated back to portrait, three cells are positioned per row, which is correct (except for having to rotate the device, add a cell and rotate it back).
I am calling reloadData when adding the cells, and if I understand correctly, I should not have to call reload upon device rotation (called automatically). Why do I have to rotate the device to landscape and add a cell for the correct layouts to appear in both orientations?
As requested, here is a thinned out version of the source code for the VC. With a simple cell (244*150 with text field 228) and button in the footer, this should repeat the problem:
import UIKit
class CountCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout
{
private var reuseIdentifier = "CountCell"
var cells = 1
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
}
#IBAction func addCount(sender: AnyObject)
{
cells += 1
self.collectionView?.reloadData()
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int
{
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return cells
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as UICollectionViewCell
return cell
}
override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView
{
switch kind // the kind of supplimentary view provided by layout
{
case UICollectionElementKindSectionFooter: // section header was selected in storyboard
let footerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "CountFooterView", forIndexPath: indexPath) as UICollectionReusableView // deque and select correct header
return footerView
default:
assert(false, "Unexpected element kind")
}
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
var size = collectionView.bounds.size
switch size.width
{
case 0...450:
return CGSize(width: (size.width - 20), height: 145)
case 451..<768:
return CGSize(width: ((size.width - 20) / 2), height: 145)
case 768:
return CGSize(width: ((size.width - 20) / 3), height: 145)
default:
return CGSize(width: 248, height: 150)
}
}
private let sectionInsets = UIEdgeInsets(top: 20.0, left: 0, bottom: 50.0, right: 0.0)
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets
{
return sectionInsets
}
}
Number 33 = items count!
You can tweak it independently from your total items count to fit more or less items in screen! For example / (Double(33)) on iPhone 4 will fit 33 items in 4 columns and 4 items per row! in iPhone 6 Plus you will see same picture cells will scale up respecting 4 columns!
// ...........returning cell size based on device screen size by calculating square roo
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let deviceSize = UIScreen.mainScreen().bounds.size
let cellSize = sqrt(Double(deviceSize.width * deviceSize.height) / (Double(33)))
return CGSize(width: cellSize , height: cellSize)
}
Are you setting the no of sections as 1, If not set it as 1 and try again.
Related
I have a NSCollectionView which presents items in a list.
my layout is defined by
private func configureCollectionView() {
let flowLayout = NSCollectionViewFlowLayout()
flowLayout.itemSize.width = self.view.frame.width
//flowLayout.itemSize.height = 50
flowLayout.minimumLineSpacing = 8
myCollectionView.collectionViewLayout = flowLayout
view.wantsLayer = true
}
When a long peice of text is entered as an item, I am trying to get the item height for that 'cell' to grow/expand with the content of the text.
I have setup my contsraints using AutoLayout and I fairly certain that it is setup correctly for this, but my item height remains fixed at 50 (I can't tell where this is getting set).
I am trying to take advantage of
func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize
But I'm not having much luck with this either as I don't know how to best figure out what the Size should be (the height in this case)
Any suggestions on this problem?
Thanks!
You can use sizeThatFits with your desired width and a big enough height
func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
let string = strings[indexPath.item]
let size = NSTextField(labelWithString: string).sizeThatFits(NSSize(width: 400, height: 800))
return NSSize(width: 400, height: size.height > 80 ? size.height : 80)
}
I have a label and an image.
if image != nil {
cell height = 445
} else {
//how do i set the cell height according to the label?
}
You should set the height of tableView cells in heightForRowAt. This function returns the height of the cell for a given indexPath (as you probably have guessed). Inside there you can use return label.frame.height
In Swift 3:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if image != nil {
return 445
} else {
return label.frame.height
}
}
Edit: You can't use cellForRowAt until cells are initialized in cellForRowAt. So Try this instead. It assumes the image and label are #IBOutlets
I have created a UICollectionView in which I have one prototype cell. I want 2 cells per row and running it on smaller screen gives me on one cell per row.
The distance between cells gets increased on bigger screen. I am talking only with respect to iPhones.
I think I have to set constrains programmatically by taking screen width and divide it by 2. I know how to take screen width and divide it by 2 but I don't know how to give width and heights to cell programmatically.
UICollectionViewDelegateFlowLayout defines a method for this purpose.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
// return a CGSize
}
Example:
class MyViewController : UIViewController, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.delegate = self
self.collectionView.dataSource = self
}
// ...
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSize(width: CGFloat(self.view.frame.size.width / 2), height: self.view.frame.size.height)
}
// ...
}
I'm trying to figure out how to make a table of images with varying sizes. Where the images get resized to fit the width of the device, and the cells fit the height of the image. Currently the images are getting sized to the table cells.
Xcode project
ViewController
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
var images = ["abstract","city","city2","nightlife"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! ImageTableViewCell
cell.configureCellWith(images[indexPath.item])
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return images.count
}
}
ImageTableViewCell
import UIKit
class ImageTableViewCell: UITableViewCell {
#IBOutlet weak var tableImage: UIImageView!
func configureCellWith(image: String) {
tableImage.image = UIImage(named: image)
}
}
Implement heightForRowAtIndexPath to set the cell height to the correct height for that cell's image.
(You can determine that height by examining the image's width and height, and divide to give the aspect ratio. Now use that aspect ratio to determine the height based on the cell width, which is the table width.)
This is actually fairly simple. You just have to implement heightForRowAtIndexPath function and return height of image for each cell.
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return image[indexPath.row].size.height
}
I am trying to set the height of each row in the tableView to the height of the corresponding cell with this code:
override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
var cell = tableView.cellForRowAtIndexPath(indexPath)
return cell.frame.height
}
I get this error when initialising var cell :
Thread 1:EXC_BAD_ACCESS(code=2,address=0x306d2c)
For setting row height there is separate method:
For Swift 3
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0;//Choose your custom row height
}
Older Swift uses
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100.0;//Choose your custom row height
}
Otherwise you can set row height using:
self.tableView.rowHeight = 44.0
In ViewDidLoad.
Put the default rowHeight in viewDidLoad or awakeFromNib. As pointed out by Martin R., you cannot call cellForRowAtIndexPath from heightForRowAtIndexPath
self.tableView.rowHeight = 44.0
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var height:CGFloat = CGFloat()
if indexPath.row == 1 {
height = 150
}
else {
height = 50
}
return height
}
yourTableView.rowHeight = UITableViewAutomaticDimension
Try this.
As pointed out in comments, you cannot call cellForRowAtIndexPath inside heightForRowAtIndexPath.
What you can do is creating a template cell used to populate with your data and then compute its height.
This cell doesn't participate to the table rendering, and it can be reused to calculate the height of each table cell.
Briefly, it consists of configuring the template cell with the data you want to display, make it resize accordingly to the content, and then read its height.
I have taken this code from a project I am working on - unfortunately it's in Objective C, I don't think you will have problems translating to swift
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
static PostCommentCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [self.tblComments dequeueReusableCellWithIdentifier:POST_COMMENT_CELL_IDENTIFIER];
});
sizingCell.comment = self.comments[indexPath.row];
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height;
}
Problem Cause:
The problem is that the cell has not been created yet. TableView first calculates the height for row and then populates the data for each row, so the rows array has not been created when heightForRow method gets called. So your app is trying to access a memory location which it does not have the permission to and therefor you get the EXC_BAD_ACCESS message.
How to achieve self sizing TableViewCell in UITableView:
Just set proper constraints for your views contained in TableViewCell's view in StoryBoard. Remember you shouldn't set height constraints to TableViewCell's root view, its height should be properly computable by the height of its subviews -- This is like what you do to set proper constraints for UIScrollView. This way your cells will get different heights according to their subviews. No additional action needed
Make sure Your TableView Delegate are working as well.
if not then
in your story board or in .xib
press and hold Control + right click on tableView drag and Drop to your Current ViewController.
swift 2.0
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 60.0;
}
There is no way to call tableView.dequeueReusableCell from within tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath).
So you have to compute the height from the data.
In my case that was not possible because I have a textView in the cell, which size I did not know.
So I come around with the following strategies:
Do not use tableView.dequeueReusableCell. Just use an array of cells yo have under full control. If your cell is not too large in memory and you don't have too much rows, this is the simplest strategy.
Use a dummy cell, configure it with your data and compute the size.
Try code like this copy and paste in the class
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}