GrowingTextView or expandable textview inside UItableview cell - ios

I wish to implement an expandable textview inside the table cell, I found the GrowingTextView but I still failed to implement.I need to get input from user and the cell will auto resize when the users typing. Is there any easier implementation or guide on this? Thanks all

Actually you dont need 3rd party library to do this.
Change your GrowingTextView to UILabel
Config you UILabel to Top, Left, Right, and Bottom to the cell, and make "Title" label dependent on the UILabel
this is important because the cell size is dependant of the content of UILabel.
Set numberOfLines to 0 and
Set lineBreakMode to word wrapping or character warpping
Set tableView.rowHeight = UITableViewAutomaticDimension and tableView.estimatedRowHeight = 150 // or wtever you found reasonable
For task like these i suggest you can do more research on Internet instead of using a 3rd party library so fast, the keyword here is dynamic table view cell which by searching on Google there are lots of tutorials helping you out without using 3rd party library.

You can achieve it with native UITextView.
Implement this in the text view's delegate
- (void)textViewDidChange:(UITextView *)textView{
CGRect oldFrame = textView.frame;
CGSize constraint = CGSizeMake(yourTextViewWidth, MAXFLOAT);
CGRect rect = [textView.text boundingRectWithSize:constraint options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:textView.font} context:nil];
CGSize size = CGSizeMake(rect.size.width, rect.size.height);
[textView setFrame:CGRectMake(oldFrame.origin.x, oldFrame.origin.y, oldFrame.size.width, size.height + 5)];
// These 2 lines to force UITableView to redo the height calculation.
[self.yourTableView beginUpdates];
[self.yourTableView endUpdates];
}
Then, implement this in the table view's delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *yourTextViewText;
// You get the text either from the same way you did in cellForRowAtIndexPath dataSource
// or if you already referenced UITextView as your property you can easily retrieve the text by calling self.yourTextView.text
CGSize constraint = CGSizeMake(yourTextViewWidth, MAXFLOAT);
CGRect rect = [text boundingRectWithSize:constraint options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:yourFontSize]} context:nil];
CGSize size = CGSizeMake(rect.size.width, rect.size.height);
CGFloat height = MAX(size.height, yourDefaultCellHeight);
return height + 5;
}
UPDATE Swift version converted by Swiftify v4.1.6738 - https://objectivec2swift.com/
func textViewDidChange(_ textView: UITextView) {
let oldFrame: CGRect = textView.frame
let constraint = CGSize(width: yourTextViewWidth, height: MAXFLOAT)
let rect: CGRect = textView.text.boundingRect(with: constraint, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: textView.font], context: nil)
let size = CGSize(width: rect.size.width, height: rect.size.height)
textView.frame = CGRect(x: oldFrame.origin.x, y: oldFrame.origin.y, width: oldFrame.size.width, height: size.height + 5)
// These 2 lines to force UITableView to redo the height calculation.
yourTableView.beginUpdates()
yourTableView.endUpdates()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let yourTextViewText: String
// You get the text either from the same way you did in cellForRowAtIndexPath dataSource
// or if you already referenced UITextView as your property you can easily retrieve the text by calling self.yourTextView.text
let constraint = CGSize(width: yourTextViewWidth, height: MAXFLOAT)
let rect: CGRect = text.boundingRect(with: constraint, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: yourFontSize)], context: nil)
let size = CGSize(width: rect.size.width, height: rect.size.height)
let height: CGFloat = max(size.height, yourDefaultCellHeight)
return height + 5
}

Related

Calculating height of UICollectionViewCell with text only

trying to calculate height of a cell with specified width and cannot make it right. Here is a snippet. There are two columns specified by the custom layout which knows the column width.
let cell = TextNoteCell2.loadFromNib()
var frame = cell.frame
frame.size.width = columnWidth // 187.5
frame.size.height = 0 // it does not work either without this line.
cell.frame = frame
cell.update(text: note.text)
cell.contentView.layoutIfNeeded()
let size = cell.contentView.systemLayoutSizeFitting(CGSize(width: columnWidth, height: 0)) // 251.5 x 52.5
print(cell) // 187.5 x 0
return size.height
Both size and cell.frame are incorrect.
Cell has a text label inside with 16px margins on each label edge.
Thank you in advance.
To calculate the size for a UILabel to fully display the given text, i would add a helper as below,
extension UILabel {
public static func estimatedSize(_ text: String, targetSize: CGSize = .zero) -> CGSize {
let label = UILabel(frame: .zero)
label.numberOfLines = 0
label.text = text
return label.sizeThatFits(targetSize)
}
}
Now that you know how much size is required for your text, you can calculate the cell size by adding the margins you specified in the cell i.e 16.0 on each side so, the calculation should be as below,
let intrinsicMargin: CGFloat = 16.0 + 16.0
let targetWidth: CGFloat = 187.0 - intrinsicMargin
let labelSize = UILabel.estimatedSize(note.text, targetSize: CGSize(width: targetWidth, height: 0))
let cellSize = CGSize(width: labelSize.width + intrinsicMargin, height: labelSize.height + intrinsicMargin)
Hope you will get the required results. One more improvement would be to calculate the width based on the screen size and number of columns instead of hard coded 187.0
That cell you are loading from a nib has no view to be placed in, so it has an incorrect frame.
You need to either manually add it to a view, then measure it, or you'll need to dequeu it from the collectionView so it's already within a container view
For Swift 4.2 updated answer is to handle height and width of uicollectionview Cell on the basis of uilabel text
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
let size = (self.FILTERTitles[indexPath.row] as NSString).size(withAttributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14.0)])
return CGSize(width: size.width + 38.0, height: size.height + 25.0)
}

how to find actual height of auto growable UILabel

I have given number of lines to zero for UIlabel so that its height will adjust according its content.
sometimes If I give a big text it grows unto 4 lines in UI but when I access its
frame.size.height
it returns 21 instead of actual height which is way more bigger than that.
Any ideas on how to fix this ?
swift
public static func requiredHeightForLabel(text : String, font : UIFont, width : CGFloat) -> CGFloat {
let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: .greatestFiniteMagnitude))//UILabel(frame: CGRectMake(0, 0, width, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
objective c
+(CGFloat)getLabelHeightForString:(NSString *)string font:(UIFont *)font
{
CGSize size = CGSizeMake(lablwidth, MAXFLOAT);
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
CGSize boundingBox = [string boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:font}
context:context].size;
size = CGSizeMake(ceil(boundingBox.width), ceil(boundingBox.height));
return size.width;
}
In your View Controller you can use method which notify you about layout finishing:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("frame = \(label.frame)")
}

How to resize table cell based on textview?

I have a UITextView in a custom UITableViewCell. The textview delegate is assigned in the tableviewcell custom class. Textview scrolling is disabled. Text loads into each textview and is multiline. But the text is always clipped because the cell height doesn't change.
I have the following in viewDidLoad of the tableview controller:
tableView.estimatedRowHeight = 56.0
tableView.rowHeight = UITableViewAutomaticDimension
Any idea why this isn't working?
Try my answer its work perfectly.
var record : NSArray = NSArray()
var hight: CGFloat = 0.0
Put this code in viewDidLoad()
record = ["I have a UITextView in a custom UITableViewCell. The textview delegate is assigned in the tableviewcell custom class." ,"Textview scrolling is disabled. Text loads into each textview and is multiline. But the text is always clipped because the cell height doesn't change.","I have the following in viewDidLoad of the tableview controller:"," have a UITextView in a custom UITableViewCell. The textview delegate is assigned in the tableviewcell custom class.","Textview scrolling is disabled. Text loads into each textview and is multiline. But the text is always clipped because the cell height doesn't change.","I have the following in viewDidLoad of the tableview controller:","i just give you one link at put place i use label and you can now use your textview and give same constrain that i give in that link and try it so your problem will be solve","I have the following in viewDidLoad of the tableview controller:"];
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return record.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Textviewcell", forIndexPath: indexPath)
let textview: UITextView = (cell.viewWithTag(5) as! UITextView)
textview.text = record.objectAtIndex(indexPath.row) as? String
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// 7.1>
hight = self.findHeightForText(self.record.objectAtIndex(indexPath.row) as! String, havingWidth: self.view.frame.size.width - 10, andFont: UIFont.systemFontOfSize(14.0)).height
return 44 + hight
}
func findHeightForText(text: String, havingWidth widthValue: CGFloat, andFont font: UIFont) -> CGSize {
var size = CGSizeZero
if text.isEmpty == false {
let frame = text.boundingRectWithSize(CGSizeMake(widthValue, CGFloat.max), options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
size = CGSizeMake(frame.size.width, ceil(frame.size.height))
}
return size
}
In Swift 3.0
func findHeightForText(text: String, havingWidth widthValue: CGFloat, andFont font: UIFont) -> CGSize {
var size = CGSize.zero
if text.isEmpty == false {
let frame = text.boundingRect(with: CGSize(width: widthValue, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
size = CGSize(width: frame.size.width, height: ceil(frame.size.height))//CGSizeMake(frame.size.width, ceil(frame.size.height))
}
return size
}
Here are some screen shot .Storyboard
Runtime tableview with UITextView
You should use the delegate method
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
to set the height for the row. Then, inside that method, return the height that the cell should be by calculating the height of the textview which will occupy the space. You should use the function:
-(CGFloat)heightForTextViewRectWithWidth:(CGFloat)width andText:(NSString *)text withBuffer:(float)buffer
{
UIFont * font = [UIFont fontWithName:#"YourFontName" size:15.0f]; // Replace with your font name and size
NSDictionary *attributes = #{NSFontAttributeName: font};
// this returns us the size of the text for a rect but assumes 0, 0 origin, width is the width of that your text box will occupy.
// Ex. If you text box has padding of 12 both trailing and leading to the cell, then width should be the cell's width minus 24.
CGRect rect = [text boundingRectWithSize:CGSizeMake(width, MAXFLOAT)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:attributes
context:nil];
// return height of rect
return rect.size.height;
}
This will determine the rect that your text view will occupy in the cell. The height that is returned is equal to the height of the text view, so if you want the cell to be taller than the text box, add that padding.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString * yourText = commentDict.text; // or however you are getting the text
float widthPadding = 24.0f;
float heightPadding = 16.0f;
float height = [self heightForTextViewRectWithWidth: (tableView.frame.size.width - widthPadding) andText:yourText];
return height + heightPadding;
}
Hope this helped!

Add read more to label and expanding UITableViewCell

I am populating a UITableView with labels. How can i add a "read more" button at the end of the label only if the number of characters in that label exceed 120 and make the UItableViewCell expand to fit the new big label? Please help. (SWIFT)
I think you will have to manually adjust the string as it goes into the label, turn it into a NSAttributedString with 120 characters (with the ....) then append a NSLinkAttributeName for the read more part with a dummy URL, override textView shouldInteractWithURL and put expand function there (which should be just reload the cell with full string)
Just a Swift 3 version of #VRAwesome answer:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var height: CGFloat = 333
// assign initial height
let lblFrame = CGRect(x: CGFloat(40), y: CGFloat(50), width: CGFloat(194), height: CGFloat(50))
//assign initial frame
let strText = currentChallenge.description as NSString
let rect = strText.boundingRect(with: CGSize(width: lblFrame.size.width, height: CGFloat(Float.greatestFiniteMagnitude)), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
if rect.size.height > lblFrame.size.height || rect.size.height < lblFrame.size.height {
let diff = rect.size.height - lblFrame.size.height
height = height + diff + 20
}
if isRowOpen[indexPath.row] == true {
let rect = strSharedText.boundingRect(with: CGSize(width: lblFrame.size.width, height: CGFloat(Float.greatestFiniteMagnitude)), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
if rect.size.height > lblFrame.size.height || rect.size.height < lblFrame.size.height {
let diff = rect.size.height - lblFrame.size.height;
height = height + diff + 20;
}
}
return height
}
First calculate chars of string which you are going to assign to label. If that is more than 120 chars then create and add button(Read More) to the bottom right corner of cell.
And when press Read More then calculate size of string using boundingRectWithSize then reload that particular cell it will update size of cell.
Create one global boolean array :
bool isRowOpen[];
Your methods should be like :
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat height = 420; // assign initial height
CGRect lblFrame = CGRectMake(40, 50, 194, 50); //assign initial frame
NSString *strText = [someArray objectAtIndex:indexPAth.row];
CGRect rect = [strText boundingRectWithSize:CGSizeMake(lblFrame.size.width, FLT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil];
if (rect.size.height > lblFrame.size.height || rect.size.height < lblFrame.size.height) {
float diff = rect.size.height - lblFrame.size.height;
height = height+diff+20;
}
if (isRowOpen[indexPath.row] == TRUE) {
CGRect rect = [strSharedText boundingRectWithSize:CGSizeMake(lblFrame.size.width, FLT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil];
if (rect.size.height > lblFrame.size.height || rect.size.height < lblFrame.size.height) {
float diff = rect.size.height - lblFrame.size.height;
height = height+diff+20;
}
}
return height;
}
And last on Button Read More click event reload that particular cell. when you want expand, update isRowOpen[selectedRow] = TRUE. when you want collapse then again update isRowOpen[selectedRow] = FALSE. So it will show effect what you want.

UITextView Changing Size Dynamically

I am using the following code to dynamically adjust the height of the containerView which contains my UITextView
func textViewDidChange(textView: UITextView) {
let amountOfLinesToBeShown:CGFloat = 6
let maxHeight:CGFloat = textView.font!.lineHeight * amountOfLinesToBeShown
if ((textView.contentSize.height / textView.font!.lineHeight) < 6) {
topConstraint?.constant = -textView.sizeThatFits(CGSizeMake(textView.frame.size.width, maxHeight)).height - keyboardFrameSize!.height
containerView.layoutIfNeeded()
containerView.updateConstraints()
}
}
The problem is, when I am typing, it appears like this:
and the desired effect is this:
The top answer from this post solves this issue. How do I size a UITextView to its content?
The code below is not mine, it is from the answer linked above (simply posting for other's convenience):
let fixedWidth = textView.frame.size.width
textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height:CGFloat.max))
var newFrame = textView.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
textView.frame = newFrame;
The code below might be the solution for this problem you got.
func textViewDidChange(textView: UITextView) {
var newFrame = textView.frame
var minHeight = 40 // ENTER THE MIN HEIGHT OR THE INITIAL HEIGHT OF THE TEXTVIEW
var maxHeight = 100 // ENTER THE MAX HEIGHT YOU WANT
newFrame.size = textView.sizeThatFits(CGSizeMake(newFrame.size.width, maxHeight))
if newFrame.size.height > minHeight && newFrame.size.height < maxHeight {
// Change the origin.y value which is 10 to your desired margin for top.
textView.frame = CGRectMake(0, 10, textView.frame.size.width, newFrame.size.height)
}
}
Create a function and add this in your viewDidLoad method, try with:
self.addSubview(containerView)
containerView.addSubview(textView)
containerView.bottomAnchor.constraintEqualToAnchor(self.bottomAnchor).active=true
containerView.leftAnchor.constraintEqualToAnchor(self.leftAnchor).active=true
textView.topAnchor.constraintEqualToAnchor(containerView.topAnchor, constant: 3).active=true
textView.leftAnchor.constraintEqualToAnchor(containerView.leftAnchor, constant: 3).active=true
textView.widthAnchor.constraintEqualToAnchor(containerView.widthAnchor).active=true
textView.heightAnchor.constraintEqualToAnchor(containerView.heightAnchor).active=true
use textview textContainerInset method for achieving what u want
//insets are top, left, bottom, top..
tv.textContainerInset = UIEdgeInsetsMake(0,0,0,0);
after setting this method,when textview line starts, increase height of textview for finding the textview next line start,
use method:- TextViewDidChange

Resources