I have to create a screen displaying various activity in the user's account such as picture like, uploading images and videos. Just like this-
I thought of using custom TableViewCell for which I need to make two different custom cells in a same table view. I had no problem while making a custom cell for the 2nd activity but am facing problem for the first activity as I have to add an UIImageView for the picture and video. The height of view is not increasing along with UIImage added. Can anyone tell me how can I make two different shapes of custom cells or should I use some other way to show the activities?
You should calculate cell height for different cells. Here is how I do with this:
+(CGFloat)heightForBubbleWithObject:(MessageModel *)object
{
CGSize retSize = object.size;
if (retSize.width == 0 || retSize.height == 0) {
retSize.width = MAX_SIZE;
retSize.height = MAX_SIZE;
}else if (retSize.width > retSize.height) {
CGFloat height = MAX_SIZE / retSize.width * retSize.height;
retSize.height = height;
retSize.width = MAX_SIZE;
}else {
CGFloat width = MAX_SIZE / retSize.height * retSize.width;
retSize.width = width;
retSize.height = MAX_SIZE;
}
return 2 * BUBBLE_VIEW_PADDING + retSize.height;
}
in ios 8 and above put this magical lines and make sure the constraints are properly given
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0;
there is a wonderful answer about this please refer it for the details Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
Related
I have a table view, and within that table view I have a cell that contains text and it can also contain an image if the user chooses to. However, I'm trying to make the cell smaller if the user chooses not to include an image. When I do this, its actually stretching my image to make the cell even larger. Does anyone know why this is happening? My code is:
// get main queue to this block of code to communicate back
DispatchQueue.main.async {
cell.textLbl.sizeToFit()
// if no image on the cell
if image.size.width == 0 && image.size.height == 0 {
// move left textLabel if no picture
cell.textLbl.frame.origin.x = self.view.frame.size.width / 50
cell.textLbl.frame.size.width = self.view.frame.size.width - self.view.frame.size.width / 8
tableView.estimatedRowHeight = 120
tableView.rowHeight = UITableViewAutomaticDimension
cell.updateConstraintsIfNeeded()
}
}
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;
}
I have a feed that gets populated with 15 posts from the server. When I scroll down to 3 before the end of the list, I ping the server for the next 15 posts. This functionality works great. However, when I start scrolling up, the UITableViewCells frequently jump up, as though Cell 5 is now populating Cell 4, and Cell 4 is now populating Cell 3, etc. Either that, or the UITableView scroll is just jumping up.
When I get to the very top of the UITableView and then proceed to scroll down through all my data then back up, it works perfectly though. Is there a drawing issue with my table?
Edit: So, I've come across the understanding that this is happening because the heights of all my cells are dynamic. I'm pretty sure as I'm scrolling up, my UITableView is calculating and setting the appropriate heights, which is causing the jumpy action. I'm not sure how to mitigate that.
I never used the new funcionality of dynamic cell size in iOS8, but I can give you few suggestion for improve performance on table views. It should be a comment but it doesn't fit.
Cache the height of cells already displayed if you can. It's easy an dictionary paired with a sort of id would do the trick
Pay attention that you do not have complex layout between subviews of you cells
Check if you are drawing something that requires offscreen rendering, such as corner radius, clipping etc
I don't know how dynamic cell works on ios8 but I share piece of my code. It's pretty straightforward. I have a cell that I use as prototype, each times I need to calculate a cell height I feed it with my data, that I force it's layout to get me the correct height. Once I've got the height I saved it in a NSDictionary using the postID(it's a twitter like app) as a key.
This happens only when the cell height is not cached. If it is cached the height is returned.
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGSize size = CGSizeZero;
NSDictionary * data = self.timelineData[indexPath.row];
if (data[KEY_CELL_IDENTIFIER] == CellIdentifierPost) {
NSNumber * cachedHeight = [self.heightCaches objectForKey:#([(AFTimelinePostObject*)data[KEY_CELL_DATA] hash])];//[(AFTimelinePostObject*)data[KEY_CELL_DATA] timelinePostObjectId]];
if (cachedHeight) {
return (CGFloat)[cachedHeight doubleValue];
}
[_heightCell configureCellWith:data[KEY_CELL_DATA]];
size = [_heightCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
[self.heightCaches setObject:#(size.height) forKey:#([(AFTimelinePostObject*)data[KEY_CELL_DATA] hash])];//[(AFTimelinePostObject*)data[KEY_CELL_DATA] timelinePostObjectId]];
}
else if (data[KEY_CELL_IDENTIFIER] == CellIdentifierComment){
NSNumber * cachedHeight = [self.heightCaches objectForKey:#([(AFTimelinePostComments*)data[KEY_CELL_DATA] hash])];//[(AFTimelinePostObject*)data[KEY_CELL_DATA] timelinePostObjectId]];
if (cachedHeight) {
return (CGFloat)[cachedHeight doubleValue];
}
[_heightCommentCell configureCellWith:data[KEY_CELL_DATA]];
size = [_heightCommentCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
if (size.height < 80.0f) {
size = (CGSize) {
.width = NSIntegerMax,
.height = 115.f
};
}
else if (size.height > 180.0f) {
size = (CGSize) {
.width = NSIntegerMax,
.height = 180.f
};
}
[self.heightCaches setObject:#(size.height) forKey:#([(AFTimelinePostComments*)data[KEY_CELL_DATA] hash])];//[(AFTimelinePostObject*)data[KEY_CELL_DATA] timelinePostObjectId]];
}
else {
size = (CGSize) {
.width = NSIntegerMax,
.height = 50.f
};
}
return size.height;
}
Unfortunately, there does not seem to be a simple answer to this. I have struggled with it on multiple iOS apps.
The only solution I have found is to programmatically scroll to the top of your UITableView once it appears again.
[self.tableView setContentOffset:CGPointMake(0, 0 - self.tableView.contentInset.top) animated:YES];
OR
self.tableView.contentOffset = CGPointMake(0, 0 - self.tableView.contentInset.top);
Hope this an acceptable work around while still being able to use dynamic cell heights =)
I'm having a fight with UICollectionView to adopt it to my needs.
I'm trying to build a collapsing tag cloud. All elements can dynamically change size based on text inside.
When collection is displayed, I would like to display only first row and hide rest of content.
If there is more content than just for one row I want to show a button as a last item in first row - after selecting it, I will change collection size to fit it's content.
First step:
After click:
I was able to achieve desired effect with this code:
- (void) updateFrame
{
if(self.showFull)
{
self.showMoreButton.hidden = YES;
self.bottomConstrain.constant = 0;
self.frame = CGRectMake(0, 0, self.collectionView.frame.size.width, self.collectionView.contentSize.height);
}
else
{
self.showMoreButton.hidden = NO;
self.bottomConstrain.constant = self.originalConstrainValue;
self.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
}
}
But button in my case is placed completely outside the collection:
I'm hitting my head against a brick wall how to make this button to be a part of collection view.
It's the first time I'm playing with UICollectionView and UICollectionViewFlowLayout and it's too complex for me still to understand where would be best spot to put it.
One idea I had was to play a button over UICollectionView and position it at the end of first row - but I don't know how to get first row size from collection. With this approach it will be problematic also to cover last item in a row.
I think the best will be to put this element as a UICollectionViewCell, but I have no idea how to approach that - how to predict where it will placed and how to hide it later.
Any ideas will be highly appreciated.
Current template project is here:
https://www.zipshare.com/download/eyJhcmNoaXZlSWQiOiJjNTg2MGFkNC1mYWYxLTRlMzItOTA1YS1hMWFjOGFkMjMzYjUiLCJlbWFpbCI6Imdya3J1a293c2tpQGdtYWlsLmNvbSJ9
I'd suggest putting this button as a different cell INSIDE the collection view. Then, in collectionView:didSelectItemAtIndexPath: you would check to see if the special item was selected and react accordingly. I would let this button always be the last item in the collection view so you know where it is. When the items have expanded and everything is displayed, make sure you flip your logic and compress the collection view when this special cell is pressed.
You'll need to save some state to do this:
1.The array of tags
whether or not the collection view is expanded or not
Once you have this info, you can just flip the bits and reload the collection view section.
I've managed to achieve what I wanted with this code (thanks for atreat for suggestion).
I'm estimating which element won't fit in first row, and pushing "More" tag into tags array.
There is some logic also to make sure that More button will fit after last element in this row, if not it's pushed instead of this element. Maybe some will find it usefull.
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)self.collectionView.collectionViewLayout;
CGFloat collectionWidth = self.frame.size.width;
CGFloat lastFittingItemRight = 0.0f;
CGFloat spacingBetweenElements = flowLayout.minimumInteritemSpacing;
CGFloat sectionInsetLeft = flowLayout.sectionInset.left;
CGFloat sectionInsetRight = flowLayout.sectionInset.right;
CGFloat rightEdge = sectionInsetLeft;
for (int i = 0; i < [self collectionView:self.collectionView numberOfItemsInSection:0]; i++)
{
CGSize elementSize = [self collectionView:self.collectionView layout:flowLayout sizeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
CGFloat elementWidth = elementSize.width;
rightEdge += elementWidth;
if(rightEdge > collectionWidth - sectionInsetRight)
{
self.moreButtonIndexPath = [NSIndexPath indexPathForItem:i inSection:0];
break;
}
lastFittingItemRight = rightEdge;
rightEdge += spacingBetweenElements;
}
if(self.moreButtonIndexPath)
{
NSString* showMoreText = #"More";
CGSize moreButtonSize = [self sizeForTagWithText:showMoreText];
CGFloat rightEdgeMoreButton = lastFittingItemRight + spacingBetweenElements + moreButtonSize.width;
NSInteger moreButtonIndex = self.moreButtonIndexPath.row;
if(rightEdgeMoreButton > collectionWidth - sectionInsetRight)
{
moreButtonIndex = moreButtonIndex-1;
}
[self.tags insertObject:showMoreText atIndex:moreButtonIndex];
}
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;
}