iOS message cell width/height using auto layout - ios

The Goal
I'm trying to create a dynamic message cell using auto-layout.
What I've Tried
The cell is positioning correctly, for the most part, with auto-layout given the following constraints:
The Problem
My first problem was the message label (Copyable Label) width was constrained. That seems to be resolved by using setPreferredMaxLayoutWidth: as described in this question.
Height is still a problem. As you can see, the message bubble is still cutting off. In addition, I'm not sure how to determine the message cell height for the table view.
I expected auto-layout to somehow just work. I've read the answer here, but it seems like a lot to of steps.
The Question
First, is a case where auto-layout is more complex than traditional frame arithmetic?
Second, using auto-layout, how can I determine the height of the resulting cell?

I fully use Auto Layout and what you speak about is kinda a problem.
I didn't want to modify the way intrinsic size is calculated for performance purpose of UITable.
So I used a very simple way that is correct in the end. It's ok if your cell is simple, can become such hard if your cell contains more than one variable text.
I defined my cells normally, where you can put a UILabel that fits the insets (no problem about it).
Then, in your table datasource, you define directly the height of the cell:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [TEXTOFYOURCELL sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(300, 1000)].height + 31; // Here it's defined for 15 of top and bottom insets, define +1 than the size of the cell is important.
}
EDIT :
Here some code about the UILabel in the cell (in init method).
__titleLabel = [UILabel new];
__titleLabel.numberOfLines = 0;
[self.contentView addSubview:__titleLabel]; // adding to contentView rather than self is very important !
[__titleLabel keepInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
I use this API : https://github.com/iMartinKiss/KeepLayout to manage auto layout simpler.

This is possible on iOS 8 as can be read on AppCoda
Basically:
Set the label lines to 0.
Set the row height UITableViewAutomaticDimension

Related

iOS 8 self-sizing cells - wrongly calculated height for labels and Infinite scrolling

I am trying to get my cells to resize dynamically and I wanted to take advantage of new self-sizing cells in iOS 8. Now, I set constraints to edges of superview and the cells really resizes itself. The problem is that the cell has sometimes wrong height and the text is not displayed all. If the text is longer also the mistake in height of label is greater. I was experimenting if the problem is not caused by newly added margins but it doesn't work either.
When I was trying do the resizing the old way, I discovered that cell.contentView.systemLayoutSizeFittingSize gives also wrong height. There is probably something I am missing in sizing of content view in table view cell, but I can't figure out what it is.
I prepared working example with resizing text and image, for you to test out:
https://dl.dropboxusercontent.com/u/35953801/Cell%20test.zip
EDIT:
There is additional problem, when I try to implement infinite scrolling the reloadTable always scrolls it to the top. When I try to use insertRowsAtIndexPaths instead, it starts to jump to wrong position and glitch very badly.
Here is updated code to test out:
https://dl.dropboxusercontent.com/u/35953801/Cell%20test2.zip
EDIT 2:
So as #gabbler suggested, the best way to make reliable infinite scrolling is to do it the old way. I haven't found way how to use the storyboard prototype cells to measure them in heightForRow method. If you try something like this, you get stuck in infinite loop, because dequeReusableCell actually calls heightForRow:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var cell = tableView.dequeueReusableCellWithIdentifier("CellProgrammatical", forIndexPath: indexPath) as TableViewCell
It might be possible to set it up with Nib resource for cells, but best will be probably to save yourself a headache, follow https://github.com/smileyborg/TableViewCellWithAutoLayout and set up constraints programatically.
There is one thing that needs to be done for iOS8. It adds extra constraint UIView-Encapsulated-Layout-Height to 44 initially, which breaks the cell if it is taller than that. It's necessary to set all the vertical constraints to lower priority (999).
Here is the example once again, with working infinite scrolling:
https://dl.dropboxusercontent.com/u/35953801/Cell%20test3.zip
Thanks for help
FWIW I had this problem when I set constraints between my label and the cell margins instead of the cell itself. Margins are weird and don't appear to be correctly taken into account when calculating preferredMaxLayoutWidth.
Changing my constraints to be attached to the cell itself fixed the problem.
There is a label in your cell which has preferred width being set, remove the setting seems to fix the problem.
Since you have already make the label width to be equal with the cell width, there is no need to set the Preferred Width.
And try to remove these code.
self.tableView.estimatedRowHeight = 600;
tableView.reloadData()
which seems to be redundant here.
I use the following code to calculate height
Here 20 is my font size
UIFont *font = [UIFont systemFontOfSize:20];
NSDictionary *userAttributes = #{NSFontAttributeName: font,
NSForegroundColorAttributeName: [UIColor blackColor]};
NSString *text = self.userModel.quote;
CGSize textSize = [text sizeWithAttributes: userAttributes];
ht = 20;
if (textSize.width > [FunctionUtils getViewWidth]) {
ht = (textSize.width/([FunctionUtils getViewWidth]));
ht = (ceil(ht))*20+35;
}

custom cell height based on objects inside the cell

I made a custom cell, placed a UITextView inside it and I want to change the height of the cell based on the length of UITextView's text length. I know how to statically change the cell height using heightForRowAtIndexPath, but I can't put my head around doing it dynamically, based on content.
I have read about a dozen topics on this forum and on several other, but I didn't find the answer I was looking for.
Any ideas?
in heightForRowAtIndexPath
float height = [cell.textView.text sizeWithFont:cell.textView.font constrainedToSize:CGSizeMake(cell.textView.frame.size.width, 10000)].height;
return height;
10000 it's max height of cell, actually you can set max integer value
In your textViewDidChange method, call
[tableView beginUpdates];
[tableView endUpdates];
This will trigger heightForRowAtIndexPath to recalculate all your cell sizes each time the user types a letter. Then in heightForRowAtIndexPath, you can then calculate the necessary size for your cell.
The sizeWithFont method will return the size needed to display the text in a UILabel, which is slightly different than that for a UITextView due to content insets, line spacing, etc. I've used a somewhat hacky solution in the past. If you create a temporary UITextView, set it's text, and use [UIView sizeThatFits:constraintSize] to get the size that will fit all its content within the constraints. (The documentation on this method is a little unclear - take a look at this answer for more info: question about UIView's sizeThatFits)
UITextView *temp = [UITextView alloc] initWithFrame:someArbitraryFrame];
temp.font = DISPLAY_FONT
temp.text = cell.textView.text;
//This gets the necessary size to fit the view's content within the specified constraints
CGSize correctSize = [self.temp sizeThatFits:CGSizeMake(CONSTRAINT_WIDTH, CONSTRAINT_HEIGHT)];
return correctSize.height
In interest of efficiency, you should probably store/lazy-load the temporary textView so that you're not creating a new UITextView for every cell, but rather re-using the same one to calculate height for different text.

Calling `[UIView -systemLayoutSizeFittingSize:]` on a UITableViewCell always fails

I want to use auto-layout for UITableViewCells. These table cells have a dynamic height (depending on text length).
I'm using [UIView -systemLayoutSizeFittingSize:] to calculate the appropriate cell height (to return in [UITableView -heightForRowAtIndexPath:]) but I keep getting the following results:
If I pass UILayoutFittingCompressedSize, I get back a CGSize of (0,0).
If I pass UILayoutFittingExpandedSize, my app crashes with this error:
*** Assertion failure in -[NSISLinearExpression incrementConstant:], /SourceCache/Foundation_Sim/Foundation-1043.1/Layout.subproj/IncrementalSimplex/NSISLinearExpression.m:620
(My guess is that this means some number is infinite.)
My implementation is simple. I calculate the height for each object, and then cache it:
MessageCell *cell = // allocate a new cell...
// set up the cell (assign text, images, etc)
CGSize size = [cell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
self.cellHeight = size.height; // size always equals (0, 0)
I hypothesize that this is a problem with the constraints I set, but:
If I manually set cellHeight to a large value, the cells all look fine except the height is wrong.
Interface Builder gives me no warnings about ambiguous restraints
[cell hasAmbiguousLayout] returns NO.
My cell has, among other things, an image set at 48x48, so a size of (0, 0) shouldn't satisfy all the constraints.
Any ideas?
This is what works for me (note 'contentView')
CGSize size = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
EDIT: I answered a similar question and provided a complete example, here:
How to resize superview to fit all subviews with autolayout?
It is hard to say something concrete basing on your post because you didn't post constraints that you use.
Apple Documentation:
systemLayoutSizeFittingSize:
Returns the size of the view that satisfies the constraints it holds.
Maybe you created constraints that can be interpreted in the way the size of the cell is equal to (0,0).
There is another way you can check the height of the cell with the text. You can put your text in the UITextView and then:
UITextView textView;
textView.text = #"l";
textView.font = [UIFont fontWithName:FONT_NAME size:FONT_SIZE];
//some code here
//
CGFloat cellHeight = textView.contentSize.height;
It is important to set the text and font (and every other property that can cause the change of the height of the UITextView) of the UITextView before using contentSize property. Also you must first add UITextView to the view.
////// EDIT
The problem with your approach with using constraints can be that you want to measure the cell which ISN'T added to the view so the system don't have all the informations it needs. It doesn't know how much space that will be for the space etc because it doesn't know the surrounding area of the cell

How to properly add custom TableViewCell?

I have a grouped tableView in my iPad-app, and I've been trying to set cell.imageView.center = cell.center to center the image instead of putting it to the leftmost position. This is apparently not possible without a subclass of the UITableviewCell(If someone could explain why, that'd also be appreciated.. For now I just assume they are 'private' variables as a Java-developer would call them).
So, I created a custom tableViewCell, but I only want to use this cell in ONE of the rows in this tableView. So in cellForRowAtIndexPath I basically write
cell = [[UITableViewCell alloc]initWith//blahblah
if(indexPath.row == 0)
cell = [[CustomCell alloc]initWith//blahblah
This is of course not exactly what I'm writing, but that's the idea of it.
Now, when I do this, it works, but the first cell in this GROUPED tableView turns out wider than the rest of them without me doing anything in the custom cell. The customCell class hasn't been altered yet. It still has rounded corners though, so it seems it knows it's a grouped tableView.
Also, I've been struggling with programmatically getting the size of a cell, in cellForRowAtIndexPath, I've tried logging out cell.frame.size.width and cell.contentView.frame.size.width, both of them returning 320, when I know they are a lot wider.. Like, all the rows are about 400 wide, and the first cell is 420 or something. It still writes out 320 for all the cells..
This code will not work for a couple of reasons:
cell.imageView.center = cell.center;
Firstly, the center is relative to its superview. I believe the cells superview is the tableView. The imageView's superview will be the content view of the cell. Therefore the coordinate systems are different so the centens will be offset. E.g. the 3rd cell down will have a center of 0.5 widths + 3.5 heights. You should be able to ge around this issue by doing:
cell.imageView.center = CGPointMake( width / 2 , height / 2 );
The second issue is related to how the table view works. The table view manages its cells view's. The width of a cell is defined by the table view's width and the height is defined by the table view's row height property. This means the cell itself has no control over its size.
You can however size its subviews, but you must do this after the cells size has been set (otherwise you can get strange results). You can do this in layout subviews (of the custom UITableViewCell class). See this answer.
- (void)layoutSubviews {
[super layoutSubviews];
self.imageView.frame = ....
}
When layoutSubviews is called the cells frame has been set, so do your view logging here instead of cellForRowAtIndexpath.
As for the GROUPED style. Im not sure if this is designed to work with custom views. I suspect it sets the size of its cells to its own width minus a 20 pixel margin on each size, then applies a mask to the top and bottom cells in a section to get the rounded effect. If you are using custom view try to stick with a standard table view style.

How to create a multi-line UILabel, with a maximum width?

I'm writing a simple IRC client that I'm modeling after Twitter's iOS app appearance. I've taken a screenshot of the Twitter app, for reference:
It looks like a simple table view with a few labels inside of each cell. So, in my app, I am programmatically creating a table and the cell formatting. My custom cell has only two labels in it, which I have positioned one on top of the other. The top label is a simple 1-liner. The bottom label I would like to contain longer messages, and need it to word-wrap to multiple lines while staying within my specified width.
How do I achieve this?
So far, I've tried explicitly setting the frame of the label to the dimensions that I want, but it does not word-wrap, if this is all I do. It just flows out of the cell horizontally. I then tried calling sizeToFit, within the tableView:cellForRowAtIndexPath: function, for this label, but it appears to word-wrap at a very small width - the text wraps after like two or three letters and then flows out of the cell vertically.
I can't seem to figure out how to get the text within the label to wrap after a specified width. Any ideas?
My custom cell class: https://github.com/ryancole/pound-client/blob/master/pound-client/views/MessageListCell.m
The cellForRowAtIndexPath function: https://github.com/ryancole/pound-client/blob/master/pound-client/controllers/MessageListViewController.m#L62-L84
Edit 1:
To demonstrate what happened when I set numberOfLines to 0, for unlimited, I have attached a screenshots of that being called. It wraps after a few characters, instead of first taking up the specified width of the UILabel's frame. This is being set prior to called sizeToFit.
You need to set numberOfLines to the number of lines you want, or 0 which allows for an unlimited number of lines (the default is 1). You might also need to set the lineBreakMode to NSLineBreakByWordWrapping (although that might be the default).
After Edit: If you want the text to start at the top, then I think you'll have to use variable height cells, and not set an explicit size for your custom cell. I did it this way in one of my projects:
-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UILabel *label = [[UILabel alloc] init];
label.numberOfLines = 0; // allows label to have as many lines as needed
label.text = _objects[indexPath.row][#"detail2"];
CGSize labelSize = [label.text sizeWithFont:label.font constrainedToSize:CGSizeMake(300, 300000) lineBreakMode:NSLineBreakByWordWrapping];
CGFloat h = labelSize.height;
return h + 50;
}
The label I create here, is just for calculating the height of the row, it's discarded after this method ends. The width of the cell is determined by the 300 argument I have in the constrainedToSize: parameter. The +50 was just a fudge factor I added to get my cells looking right -- you'd probably want to mess with that number to get what you want. In my custom cell class, I used initWithStyle:reuseIdentifier, and didn't set any size.

Resources