Update dynamic height of UICollectionViewCells - ios

What's a recommended approach to determine the height of UICollectionViewCell which contains several text views whose height depends on the text content set in them?
Since UICollectionView's sizeForItemAt is called before cellForItemAt the definitive text content isn't known at the time the cell size should be calculated.
One possible way would be to calculate the text height with something like
func calculateEstimatedCellFrame(_ text:String) -> CGRect
{
let size = CGSize(width: 210, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
return NSString(string: text).boundingRect(with: size, options: options, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14)], context: nil)
}
But that seems impractical in my current case, especially since the texts for the cells is at least in part assembled from various other sources (in part from JSON data, and from localization strings).
Using constraints seems very complex and I'm not sure how I would apply them to have several text views that are placed between other text views to set their proper constraint values.
Is there any better way to figure out the required height for each cell?

Related

TableView Cell with Minor Lag (Async Proper Use?) Swift

I have an array full of Data loading images in a tableView Cell. However I am getting minor lag(more of a glitch) when the table view scrolls on image index.
Array Contains Data(Seems to lag more bigger the bytes)
Data in Bytes(624230 bytes)
Data in Bytes(1619677 bytes)
Data in Bytes(2257181 bytes)
Data in Bytes(1120275bytes)
Not Sure How to properly use Async When the data is loaded.
struct messageCellStruct {
let message: String!
let photoData: Data!
}
var messageArray = [messageCellStruct]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
let message2 = cell?.viewWithTag(2) as! UITextView
var photoUploadData = messageArray[indexPath.row].photoData
let main = DispatchQueue.main
let background = DispatchQueue.global()
let helper = DispatchQueue(label: "another_thread")
if(photoUploadData != nil){
print("Data in Bytes\(String(describing: photoUploadData))")
let fullString = NSMutableAttributedString(string: "")
let image1Attachment = NSTextAttachment()
let newImageWidth = (self.message.bounds.size.width - 20 )
let messageDisplayString = self.messageArray[indexPath.row].message
background.async {
image1Attachment.image = UIImage(data: photoUploadData!)
}
image1Attachment.bounds = CGRect.init(x: 0, y: 0, width: newImageWidth, height: 200)
let image1String = NSAttributedString(attachment: image1Attachment)
fullString.append(image1String)
fullString.append(NSAttributedString(string: messageDisplayString!))
message2.attributedText = fullString
message2.textColor = .white
message2.font = UIFont.systemFont(ofSize: 17.0)
}else {
message2.text = self.messageArray[indexPath.row].message
}
return cell!
}
The reason I introduced the Async in the first place was because of the lag. It lags with and without the Async.
You should never perform any UI operations on background thread.
remove background.async from
background.async {
image1Attachment.image = UIImage(data: photoUploadData!)
}
simply use
image1Attachment.image = UIImage(data: photoUploadData!)
EDIT:
The lag is not because of UIImage(data: photoUploadData!) I agree that it is a synchronous call but that wont create a lag the real culprit is let image1String = NSAttributedString(attachment: image1Attachment) NSAttributedString is known to be notorious.
To test the hypothesis you can comment out
let image1String = NSAttributedString(attachment: image1Attachment)
fullString.append(image1String)
fullString.append(NSAttributedString(string: messageDisplayString!))
message2.attributedText = fullString
message2.textColor = .white
message2.font = UIFont.systemFont(ofSize: 17.0)
and you should not see a lag.
Unfortunately I don't know the way to fix the issue with NSAttributedString we had same issue and it would often add up lag on scrolling huge pile of rows. Hence we decided to opt for DTCoreText
Somehow this performs better than NSAttributedString
EDIT:
We concluded that the delay/lag is probably because of conversion of huge data to NSAttributedString. As all that OP wanna do is show image and text below it n he did not know how to handle multiple components in cell I am updating the answer to same.
I am not claiming that this is the only way to do it this way might help the OP is assumption here
Step1:
Create UITableViewCell xib and drag UIImageView to it.
Now imageViews can take implicit size. What does that mean is UIImageView's can grow based on the image shows. If the images you are loading happen to be in your control (Server sending images is yours) and if your backend team can assure that they wont send crazy big images you dont need height constraint to ImageView.
But more often than not, server team claims that its a client team job. Because you would like to show the image best possible way and showing image with aspect fit and let the bigger part of image being chopped off or if image happens to be small leaving the huge space around image ask the backend team to send aspect ratio as a part of the response.
So in that case create a height constraint to imageView
Create an IBOutlet to the height constraint lets assume you call it as imageHeightConstraint
Now when you load the image in cell, you know that imageView's width will be equal to the width of the cell and you know the aspect ratio of the image to be shown so you can calculate the height of supposed image as
either in cellFroRowAtIndexPath
cell.imageHeightConstraint.constant = cell.bounds.size.width * aspectRatioOfImage
or better if you have configure method in a cell where you would expect cell to configure its subviews then
self.imageHeightConstraint.constant = self.bounds.size.width * aspectRatioOfImage
Obviously you might need to convert it to Float from CGFloat am sure u can do it :)
Now if you dont have any control over image and you are downloading it from some random website and hence dont have aspect ratio info then rather than adding height constraint add aspect ratio constraint to imageView and change imageView content mode to .aspectFit this might have side effects I mentioned above but thats the best you get without any support :)
Now add TextView below imageView
add vertical height constraint between imageView and TextView
Select textView and set scroll enabled as false
Select TextView and change vertical content hugging priority and compression resistance to 999
Thats it :)
Now what have you achieved with all these circus is that now you have a TableViewCell which can take implicit size based on content it has without any ambiguity :)
Now that makes the life easy. Implement tableView delegates and dont forget to use
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0;
Thats it :) Now run your code and enjoy self expanding tableView cells based on the content they show :) And you have the layout u wanted
Hope it helps

UICollectionView stick to a cell even if it's content offset may change

This is a hard one, I hope I can explain it properly.
All right, so the thing is that I have a UICollectionView where the height of the cell is dynamically changing using collectionView.collectionViewLayout.invalidateLayout() which guides into the sizeForItemAtIndexPath() method of UICollectionVieFlowLayout.
This works perfectly. There is new content, it resizes, moves the other cells according to it, etc. Now, my collection view's content size is probably pretty big, that means when I'm stopping at cell number, say, 23, and cell number 15 is not even displayed but is changing it's size, it is going to make my cell number 23 move up or down. Can I kind of throw an "anchor" to where I currently am? Here's the code that's resizing the cell, triggered by Firebase:
cell.resizeToText(text: text!)
let size = CGSize(width: self.view.frame.width, height: cell.mainView.frame.height + (cell.mainView.frame.origin.y*2.5))
let invalidationIndex = indexPath.row + 1
self.invalidationSizeDictionary[invalidationIndex] = size
let context = UICollectionViewLayoutInvalidationContext()
context.invalidateItems(at: [indexPath])
self.feedCollectionView.collectionViewLayout.invalidateLayout()
Question: is there a possibility to stay at my current offset position in the collectionView?

UITableViewCell content being cut

I have created a UITableView with the height as
tableView.rowHeight = UITableViewAutomaticDimension
I found that characters, e.g. p, g, y, etc are being cut, which can't show correctly.
I've tried to set the height in larger number, however, the cell height will increase, but it is still didn't show the whole word.
Besides, I've tried something like setting a
label.sizeToFit()
cell.textlabel.numberOfLines = 0
But all doesn't work
sizeToFit is not meant to resize labels to match their font. From the UIView header:
open func sizeThatFits(_ size: CGSize) -> CGSize // return 'best' size to fit given size. does not actually resize view. Default is return existing view size
open func sizeToFit() // calls sizeThatFits: with current view bounds and changes bounds size.
It's a relatively arbitrary method that just returns some kind of size. If you want to find out how big to make a label, use
string.boundingRect(with: CGSize, options: NSStringDrawingOptions, attributes: [String : Any]?, context: NSStringDrawingContext?). However, I find that it doesn't respect heights very well, so you are better off getting the ascender/descender from the font and calculating the size yourself.

IOS Section Footer Cell Height based on Content

i need to create a custom footer for a section within a table view. The footer contains a text (label) and a button underneath it.
The problem is that the text has a different length in different languages. How can I create the footer with a dynamic height based on the content from the label?
Thx!
You can determine the height of the footer view by calculating the estimated size of the label according to the text inside of it, adding the height of the button and adding perhaps another small value for padding.
Here's a Swift example:
let theLabelFont = UIFont.systemFontOfSize(FONTSIZE)
let labelRect = (yourLabelText as NSString).boundingRectWithSize(CGSizeMake(LABEL_WIDTH, CGFloat.max), options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName:theLabelFont], context: nil)
let footerHeight: CGFloat = labelRect.height + (YOUR_BUTTON).frame.height + somePaddingValue
Then simply return it in the heightForFooterInSection: method.

Auto layout calculating same height for each collectionViewCell

So I have a collection view that uses a custom layout that I found on Github called CHTCollectionViewWaterfallLayout I like it so far, however I am looking to make the custom collection view cell I made dynamic in height, and I am having trouble connecting Auto Layout to calculate this. I shared a simple sample project on Github that generates random string sizes and displays them, only problem is that Auto Layout generates the same cell height for each Collection View Cell. The project can be found here.
To give you a run down of my thought process, I calculate the cell height by using the method CHTCollectionViewDelegateWaterfallLayout sizeForItemAtIndexPath. Since I also use the delegates method columnCountforSection my thought is since I provide a finite number of columns based on the orientation, I take the collectionView frame width and I divide by the number of columns to get me my width for the cell.
func collectionView (collectionView: UICollectionView,layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
var cell = dict["heightCell"] as? CollectionViewCell
let randomString = RadomStrings[indexPath.item]
let float = CGFloat(columnCount)
let width = collectionView.bounds.size.width / float
if cell == nil {
cell = NSBundle.mainBundle().loadNibNamed("CollectionViewCell", owner: self, options: nil)[0] as? CollectionViewCell
dict["heightCell"] = cell
}
cell?.RandomStringLabel.text = randomString
cell!.frame = CGRectMake(0, 0, CGRectGetWidth(collectionView.bounds), CGRectGetHeight(cell!.frame))
cell!.setNeedsLayout()
cell!.layoutIfNeeded()
var size = cell?.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
size?.width = width
return CGSize(width: width, height: size!.height)
}
My constraints are pretty basic. I have a constraint on each axis. One on the top of the content view, leading, trailing and bottom. I also have a height on the label that is greater than or equal to 45.
Using Auto Layout to calculate TableViewCell heights is easy to me and I like it because I like the DRY principle behind this height calculation approach. So I would like to keep this height calculation process the same throughout my app. CollectionViews are a relatively new layout process for me, so I would love to learn what I am doing wrong here. Hopefully I am clear for everyone, thanks!
I figured it out! What I didn't do is put a width constraint on the custom cell I created! Duh!

Resources