How to create horizontally dynamic UICollectionView cells? Swift - ios

Hey I'm trying display a set of "tags" in a view controller using collection view cells but I'm having trouble finding a way to make them be able to dynamically resizable depending on the length of the string.
Right now the individual cells are statically sized so whenever a String that populates the cell with characters exceeding the size of the cell, it goes into the second line. I want it so that the cell can change length depending on the length of the String. So if it's the tag "#Vegan", it will automatically resize so that the tag isn't that big. Likewise, if it's a longer string like "#LaptopFriendly", it will become horizontally longer to accommodate the string and not use the second line. The vertical length can stay fixed. Thank you!
UPDATE (interface builder settings when I run into errors using Rob's code):
Simulator screenshot:

You need unambiguous constraints between your label and the cell (e.g. leading, trailing, top, and bottom constraints):
Then you can use UICollectionViewFlowLayoutAutomaticSize for the itemSize of your collectionViewLayout. Don't forget to set estimatedItemSize, too, which enables automatically resizing cells:
override func viewDidLoad() {
super.viewDidLoad()
let layout = collectionView?.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = UICollectionViewFlowLayoutAutomaticSize
layout.estimatedItemSize = CGSize(width: 100, height: 40)
}
That yields:

You can calculate the lengths of the texts ahead of time, feed them into an array accessible by your collectionView and use them them to construct the size of the cell.
//Create vars
NSArray * texts = #[#"Short",#"Something Long",#"Something Really Long"];
NSMutableArray * lengths = [NSMutableArray new];
float padding = 30.0f;
//Create dummy label
UILabel * label = [UILabel new];
label.frame = CGRectZero;
label.font = [UIFont systemFontOfSize:20.0f weight:UIFontWeightBold];
//loop through the texts
for (NSString * string in texts){
//set text
label.text = string;
//calculate length + add padding
float length = [label.text boundingRectWithSize:label.frame.size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:label.font}
context:nil].size.width + padding;
//save value into array as NSNumber
[lengths addObject:#(length)];
}
//drop label
label = nil;
Create the size of the cell using some code like this:
return CGSizeMake(lengths[indexPath.row], 100.0f);

Related

Managing autolayout constraints for a chat view

I'm creating a chat view, and currently trying to design the cell based on autolayout. My main problem is that, there is an image view and just below there's a label for chat text which go multiple line, and if there's no image only text should display and if there's no text only image should display. Image should be 60% of cell width and 40% in height. Below is my cell design, I've added background color to each view for clarity.
But by giving proportional height, I'm not able to set its height programatically. So currently I tried giving a fixed height for image view and manage it programmatically in the code. Below is my code where I adjust imageview and chat text height.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell : ChatViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("chatCell", forIndexPath: indexPath) as! ChatViewCell
cell.fullNameLabel.text = tempArray[indexPath.row].0
cell.fullDateLabel.text = tempArray[indexPath.row].1
cell.chatSentStatusLabel.text = tempArray[indexPath.row].2
cell.chatTimeLabel.text = tempArray[indexPath.row].3
let chatBubbleData1 = tempArray[indexPath.row].4
if (chatBubbleData1.image == nil)
{
cell.chatImageHeightConstraint.constant = 0
}
else
{
cell.chatImageHeightConstraint.constant = 150
}
cell.chatTextContainerView?.layer.cornerRadius = 10.0
cell.chatTextContainerView?.layer.masksToBounds = true
cell.chatBubbleView?.layer.cornerRadius = 10.0
cell.chatBubbleView?.layer.masksToBounds = true
cell.chatImageView.image = chatBubbleData1.image
cell.userPointerView.colors = (0.0, 0.0, 1.0, 1.0)
cell.userPointerView.backgroundColor = .clearColor()
cell.chatImageView?.layer.cornerRadius = 10.0
cell.chatImageView?.layer.masksToBounds = true
if (chatBubbleData1.text?.characters.count > 0)
{
cell.chatText?.numberOfLines = 0 // Making it multiline
cell.chatText?.text = chatBubbleData1.text
cell.chatText?.sizeToFit()
cell.chatTextContainerHeightConstraint.constant = CGRectGetHeight(cell.chatText.frame)+20
}
else
{
cell.chatText?.text = ""
cell.chatTextContainerHeightConstraint.constant = 0
}
cell.setNeedsDisplay()
return cell
}
This is the result now
The multiline doesn't seems to work. I'm not sure if I'm following the best approach. If anybody has any solutions or any suggestions, please let me know.
PS: Please don't suggest any third parties like JSQMessagesViewController. I'm already aware of those. I'm doing this as part of learning.
You just put a bottom constraint for all the components in the cell.
and call delegate methods of table view (EstimatedRowHight and HightForRow).
it will return the exact hight of the cell.and you can also customise whether it display or not..
remember that text labels should had the bottom constrain property with respect the other components.Hope you get what i meant.
If UIImageView and UILabel will never be shown both in the same cell, you could remove all constraints relations between them and make each in relation only with superview. Then with some code you can manage UIImageView or UILabel visibility and calculate proper height for each cell in CollectionView/TableView delegate method.
I think problem is in your line
cell.chatTextContainerHeightConstraint.constant = CGRectGetHeight(cell.chatText.frame)+20
Instead of getting chatText.frame height your should manually calculate the height. I used following function to calculate cell height:
+ (CGFloat)calculateCellHeightForText:(NSString *)text cellWidth:(CGFloat)cellWidth {
CGFloat maxWidth = cellWidth;
CGSize textSize = [text boundingRectWithSize:CGSizeMake(maxWidth, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName : [UIFont fontWithName:#"CALIBRI" size:17]}
context:nil].size;
return textSize.height;
}

UILabel with background view + padding that follows text layout

I am trying to achieve a pretty complicated layout in my UICollectionViewCells where I have a UILabel that should have a white background with padding + 2px corner radius, that should follow the text.
If the text is too long it should be two lines, and the background should contain its padding. It's pretty hard to explain, but I think the picture will tell more.
Let me know if you have any solutions or suggestions.
Put the label inside uiview and give it a white background and play with its constraints and background colour.
You could proceed as follow :
Check the size of your string
Compare it with the size of your box
If the text size is bigger than the size of the box, you increase its size
//Get your textfield size
var frameRect = textField.frame;
let myString: NSString = textField.text! as NSString
//Get the size of your string
let size: CGSize = myString.sizeWithAttributes([NSFontAttributeName: UIFont.systemFontOfSize(14.0)])
//If your text doesn't fit, increase the size of your box
if size.width > frameRect.width {
frameRect.size.height = 50; // <-- Specify the height you want here.
textField.frame = frameRect;
}
Tell me if this works

Resizing the height of UILabel to fit text

I have UILabel in my Cell Subclass which is suppose to hold a title. The size of the title can be various lengths and therefor I need to resize the UILabel to fit the text and to prevent that the text is not to long I also need to be able to set maxHeight. The width should be the same. How can I create such code in swift in a tableViewCell subclass?
So far I have this in awakeFromNib
theTitleLabel?.font = UIFont(name: "HelveticaNeue", size: 13)
theTitleLabel?.textColor = UIColor(rgba: "#4F4E4F")
theTitleLabel?.numberOfLines = 0
theTitleLabel?.lineBreakMode = NSLineBreakMode.ByTruncatingTail
CGSize maximumLabelSize = CGSizeMake(MAX_WIDTH, MAX_HEIGHT);
CGSize expectedSize = [lbl sizeThatFits:maximumLabelSize];
CGSize s = CGSizeMake(STATIC_WIDTH, expectedSize.height);
yourLabel.frame = CGRectMake(yourLabel.frame.origin.x, nameLbl.frame.origin.y, s.width, s.height);
The easiest way to do that is using autolayout constraints. You are using awakeFromNib, so I assume you have that cell somewhere in your Interface Builder (xib or storyboard file).
If you can avoid it, never set up your views in your code. It's far easier to do it in the Interface Builder.
Find your label and set up its attributes (font, color, line break mode etc.) in the Interface Builder.
Add a width constraint (or constraints to left and right margins, depending on what you want).
Add a height constraint, change its relation from = (equals) to < (less than).
You are done, no code is needed.
Swift 2 version, from Zigglzworth's answer :
let maximumLabelSize = CGSizeMake(maxWidth, maxHeight);
let expectedSize = theLabel.sizeThatFits(maximumLabelSize)
theLabel.frame = CGRectMake(theLabel.frame.origin.x, theLabel.frame.origin.y, expectedSize.width, expectedSize.height)
let lblMassage = UILable()
lblMassage.text ="Resizing the height of UILabel to fit text.................."
lblMassage.numberOfLines = 0
lblMassage.lineBreakMode = NSLineBreakMode.byTruncatingTail
lblMassage.preferredMaxLayoutWidth = 190
lblMassage.sizeToFit()

Set UILabel Align top is not working in the cell

I have a problem to implement the text vertical alignment inside a table cell.
What I want to do is based on the length of the text I want to display a message top aligned in side one UILabel inside a cell.
For example if the message is only one line
The text should align top:
And if there are two rows then it should look like this:
At the beginning what I can see is like this
So I have searched the web and what I found is to
use the
[label1 sizeToFit];
But the problem with that is within the table view cell it is not always necessarily called especially when I switched to and from another tab view.
Then I tried to generate the label on the fly by code, but the problem is that let alone the complicated process of setting up the font format I want. I have to manage whether the label has been inserted or not then reuse it every time cellForRowAtIndexpath is called.
And more weirdly, once I select the row. The alignment is switched from the one you see in the first picture to the third one. It also happens when I switched to a different tab view and switch back to the view.
I was wondering if anybody has encountered such issue and have a solution to the problem.
Thank you for your reply in advance.
Edit:
#βḧäṙℊặṿῗ, what you said I have tried. It successfully align the label text if there is only one line. My situation is that, since I have multiple tab views. Once I switch back and forth between tabs view. The alignment just restored to centre-vertical alignment again. It also happens when I selected the row. Any idea?
Try this
// label will use the number of lines as per content
[myLabel setNumberOfLines:0]; // VERY IMP
[myLabel sizeToFit];
EDIT:
As you have one extra condition that maximumly display two lines then you need to set setNumberOfLines: to 2
[myLabel setNumberOfLines:2];
Create UILabel+Extras and add following methods to this class.
- (void)alignTop{
CGSize fontSize = [self.text sizeWithAttributes:#{NSFontAttributeName:self.font}];
double finalHeight = fontSize.height * self.numberOfLines;
double finalWidth = self.frame.size.width; //expected width of label
CGRect rect = [self.text boundingRectWithSize:CGSizeMake(finalWidth, finalHeight) options:NSStringDrawingTruncatesLastVisibleLine attributes:#{NSFontAttributeName:self.font} context:nil];
CGSize theStringSize = rect.size;
int newLinesToPad = (finalHeight - theStringSize.height) / fontSize.height;
for(int i=0; i< newLinesToPad; i++)
self.text = [self.text stringByAppendingString:#" \n"];
}
Call this method like this..
[YOUR_LABEL alignTop];
Either You set no of Lines to be 0 like
[yourLabelObject setNumberOfLines:0];
[yourLabelObject sizeToFit];
Or
You can find the height of label at run time depending upon textString length.
Following method will return you the size(height & width) of label for length of text string.
here width is fixed and only height will change :
- (CGSize) calculateLabelHeightWith:(CGFloat)width text:(NSString*)textString andFont:(UIFont *)txtFont
{
CGSize maximumSize = CGSizeMake(width, 9999);
CGSize size = [textString sizeWithFont:txtFont
constrainedToSize:maximumSize
lineBreakMode:UILineBreakModeWordWrap];
return size;
}
Yo need to calculate frame of label each time when u r doing to set text and set frame of label
I hope this will helps you.
Might sound a bit silly but this is my approach: For any field that needs to be top aligned I fill the text up with multiple "\n"s. This causes the text to be automatically top aligned. Pretty much the same as Mehul's method above.
http://i.stack.imgur.com/8u5q4.png

iOS UITableView AutoSizing Cell with Section Index

I have an app where I load a large plist file in a tableview. This plist file is as a book. It contains chapters and lines. Each row has different length depending on the line length and therefore I need to resize the cell automatically.
I am using storyboard and standard tableview and cell. Cell style=basic and the cell label is set to text=plain lines=0 linebreaks=wordwrap
Up to there, no problem resizing the cell height to the proper size. As the cell height is defined before the text is inserted in the label we have to do it by the well known method of using CGsize and I do it like that (it's working fine)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:
(NSIndexPath*)indexPath
{
NSUInteger section = [indexPath section];
NSString *key = [chapters objectAtIndex:section];
NSArray *linesSection = [lines objectForKey:key];
NSString* theText = [linesSection objectAtIndex:indexPath.row];
int labelWidth = LW_CHAPTERINDEX_10;
if (chapters.count < 100){
if (chapters.count < NB_MIN_CHAP){
labelWidth = LABELWIDTH;
} else {
labelWidth = LW_CHAPTERINDEX_10;
}
}
CGSize textSize = [theText sizeWithFont:[UIFont systemFontOfSize:14]
constrainedToSize:CGSizeMake(labelWidth, MAXFLOAT)
lineBreakMode:UILineBreakModeWordWrap];
return textSize.height;
}
The problem is the hardcoding depending on the index. For now I have 3 possibilites.
No index
Index with numbers below 10
Index with numbers below 100
and in the code this part
int labelWidth = LW_CHAPTERINDEX_10;
if (chapters.count < 100){
if (chapters.count < NB_MIN_CHAP){
labelWidth = LABELWIDTH;
} else {
labelWidth = LW_CHAPTERINDEX_10;
}
}
is the hardcoding depending on the 3 possibilities.
I find this was of doing weird, especially if apple will start to deliver more different screen sizes.
QUESTION
How can I get the index width at runtime to determine my label width ?
For example i would like to program something like screen.width - index-width to get the label width.
Or any other that should allow it to be dynamical and no more statically hardcoded.
Unfortunately there is no (standard) way you can directly access the section index subview. However, you can use the methods for calculating the CGSize of text to determine dynamically the width of the section index.
You could determine all possible strings to be returned by sectionIndexTitlesForTableView: beforehand and calculate the necessary size, perhaps padding with some extra pixels left and right. Maybe it is necessary to make some experiments to determine the correct font and size.
Now your approach with something like screenSize.width - textSize.width - 2*PADDING should be viable. The only hardcoded thing is now the padding, which should not break things when new screen sizes are introduced.
Ok, to save other people a few hours of work....I laboriously tested various fonts and sizes and margins, comparing them to a UIView hierarchy dump of an actual table view with an index, to arrive at this method which will return the width of a table index, so that the bounds of the table view cell content can be calculated as table_width - index_width. I will point out that there is often another 20 pixel right-side amount reserved for an accessory view.
I also discovered that the cell.contentView.bounds is NOT correctly set until AFTER cellForRowAtIndexPath and willDisplayCell methods are called, so trying to grab the value during those calls is doomed to fail. It is set correctly by the time viewDidAppear is called.
I have no idea how to calculate the index width if the device language is set for a non-English alphabet.
- (CGFloat) getMaxIndexViewWidth
{
CGFloat indexMargin = 21.0;
NSArray *sectionTitles = [self sectionIndexTitlesForTableView:nil]; //NOTE -- if multiple tables, pass real one in
CGFloat maxWidth11 = CGFLOAT_MIN;
for(NSString *title in sectionTitles)
{
CGFloat titleWidth11 = [title sizeWithFont:[UIFont fontWithName:#"Helvetica-Bold" size:11.0f]].width;
maxWidth11 = MAX(maxWidth11, titleWidth11);
}
return maxWidth11+indexMargin;
}

Resources