Incorrect layout with dynamic cell height - ios

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()
}
}

Related

How to create horizontally dynamic UICollectionView cells? Swift

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);

Activity shown in custom cell view

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

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;
}

Infinite Scrolling UITableView is glitchy when scrolling up after retrieving data from server

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 =)

fading scrollMenu with labels

i've created a paged UIScrollView which contains labels on every page. How can i lower the margin between the labels like in the picture and then still being able to scroll?
At the moment my navigationBar look like following. Where u can swipe to next page to the right and get the next label.
What i want is something like this with a small margin and still being able to swipe.
viewDidLoad code
//Category ScrollView
categoryScrollView = UIScrollView(frame: CGRectMake(0, self.navigationController!.navigationBar.frame.height-38, self.view.frame.width, 38))
categoryScrollView?.contentSize = CGSizeMake(self.view.frame.width, 38)
categoryScrollView?.delegate = self
categoryScrollView?.pagingEnabled = true
self.navigationController?.navigationBar.addSubview(categoryScrollView!)
categoryArray = NSArray(objects: "Book", "Elektronik")
var textWidth = 0
for val in categoryArray!
{
var textLabel: UILabel = UILabel()
textLabel.textAlignment = NSTextAlignment.Center
textLabel.text = val as NSString
textLabel.frame = CGRectMake(CGFloat(textWidth), 0, categoryScrollView!.frame.width, categoryScrollView!.frame.height)
categoryScrollView?.addSubview(textLabel)
textWidth = textWidth + Int(textLabel.frame.size.width)
if textWidth > Int(self.view.frame.width) {
categoryScrollView?.contentSize = CGSizeMake(CGFloat(textWidth), categoryScrollView!.frame.height);
}
}
The default width of the "page" when pagingEnabled is switched on is the width of the scroll view. You could reduce the width and let the contained views be visible beyond the edges of the view by setting clipsToBounds to false.
If this does not work for you there also the option to adjust the paging width by intercepting the end of the drag via the UIScrollViewDelegate method scrollViewWillEndDragging(...) where you can adjust the targetContentOffset to fit your needs (see an example here).

Resources