I am supporting iOS 7 and I am not using autolayout. Is there a way I can have dynamic height for cell labels doing it this way?
Thanks
EDIT:
Here is the code I am using to define a dynamic height in iOS 7, it seems I can get it kinda working with auto layout but it cuts off the last cell at the bottom, it is weird.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var cell = offScreenCells.objectForKey("gcc") as? GalleryCommentCell
if cell == nil {
cell = commentsTable.dequeueReusableCellWithIdentifier("GalleryCommentCustomCell") as? GalleryCommentCell
offScreenCells.setObject(cell!, forKey: "gcc")
}
let comment :GalleryCommentInfo = commentResults[indexPath.row]
setCellCommentInfo(cell!, data: comment)
cell!.bounds = CGRectMake(0, 0, CGRectGetWidth(commentsTable.bounds), CGRectGetHeight(cell!.bounds))
cell!.setNeedsLayout()
cell!.layoutIfNeeded()
let height = cell!.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
return height + 1
}
func setCellCommentInfo(cell :GalleryCommentCell, data :GalleryCommentInfo) {
cell.commentDate.text = data.galleryCommentDate
cell.comment.text = data.galleryComment
}
In your custom cell implement method like this:
+ (CGFloat)heightForContactName:(NSString *)name
{
CGFloat height = 0.0f;
if (name) {
CGFloat heightForText;
// Calculate text height with `textBoundingRect`...
height += heightForText;
}
return height;
}
Related
I've a vertical listing with 7 types of UITableViewCells. One of them consist a UITableView inside the cell.
My requirement is the main tableview should autoresize the cell according to the cell's inner tableview contentSize. That os the inner tableview will show its full length and the scrolling will be off.
This approach working fine, but for cell with tableview only. When I introduce a UIImageView (with async loading image) above inner tableview, the total height of cell is somewhat smaller than the actual height of its contents. And so the inner tableview is getting cut from bottom.
Here is a representation of the bug.
I'm setting the height of UImageView according to the width to scale properly:
if let media = communityPost.media, media != "" {
postImageView.sd_setImage(with: URL(string: media), placeholderImage: UIImage(named: "placeholder"), options: .highPriority) { (image, error, cache, url) in
if let image = image {
let newWidth = self.postImageView.frame.width
let scale = newWidth/image.size.width
let newHeight = image.size.height * scale
if newHeight.isFinite && !newHeight.isNaN && newHeight != 0 {
self.postViewHeightConstraint.constant = newHeight
} else {
self.postViewHeightConstraint.constant = 0
}
if let choices = communityPost.choices {
self.datasource = choices
}
self.tableView.reloadData()
}
}
} else {
self.postViewHeightConstraint.constant = 0
if let choices = communityPost.choices {
datasource = choices
}
tableView.reloadData()
}
And the inner table view is a subclass of UITableView :
class PollTableView: UITableView {
override var intrinsicContentSize: CGSize {
self.layoutIfNeeded()
return self.contentSize
}
override var contentSize: CGSize {
didSet{
self.invalidateIntrinsicContentSize()
}
}
override func reloadData() {
super.reloadData()
self.invalidateIntrinsicContentSize()
}
}
The main table view is set to resize with automaticDimension :
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights[indexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath] ?? UITableView.automaticDimension
}
Can't seem to understand what is going wrong. Any help is appreciated.
Thanks.
When the table view is asking you to estimate the row height, you are calling back the table view. Thus you are not providing it with any information it doesn't already have.
The problem is probably with your async loading image, so you should predict the image size and provide the table view with properly estimated row height when the image hasn't loaded yet.
I have a TableViewController, inside the TableViewCell, I have a UIWebView. I want the UIWebView to display some content from the internet, but I don't want the scroll effect, I want the WebView to have a dynamic height based on the length of the content. In addition, I want the TableViewCell to be able to adjust its cell height dynamically based on the dynamic height of WebView. Is this possible?
This is how I implemented my TableViewController:
class DetailTableViewController: UITableViewController {
var passPost: Posts = Posts()
var author: Author = Author()
override func viewDidLoad() {
super.viewDidLoad()
getAuthor()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("detailCell")
let postImageUrlString = passPost.postThumbnailUrlString
let postImageUrl = NSURL(string: postImageUrlString)
let size = CGSize(width: 414.0, height:212.0 )
let filter = AspectScaledToFillSizeFilter(size: size)
(cell?.contentView.viewWithTag(1) as! UIImageView).af_setImageWithURL(postImageUrl!, filter: filter)
//Set Author Avatar
let authorAvatarUrlString = author.authorAvatarUrlString
let authorAvatarUrl = NSURL(string: authorAvatarUrlString)
//Mark - Give Author Avatar a Round Corner
let filter2 = AspectScaledToFillSizeWithRoundedCornersFilter(size: (cell?.contentView.viewWithTag(2) as! UIImageView).frame.size, radius: 20.0)
(cell?.contentView.viewWithTag(2) as! UIImageView).af_setImageWithURL(authorAvatarUrl!, filter: filter2)
//Set Post Title and Content and so on
(cell?.contentView.viewWithTag(4) as! UILabel).text = passPost.postTitle
(cell?.contentView.viewWithTag(6) as! UIWebView).loadHTMLString("\(passPost.postContent)", baseURL: nil)
for the heightForCellAtIndexPath I did
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! tableViewCell
tableView.rowHeight = cell.theWebView.scrollView.contentSize.height
return tableView.rowHeight
}
This is working fine, except the WebView has a scroll effect, and the height is limited due to the limitation of the TableViewCell. So, how to achieve what I need?
You need to set the height of your cell in the UITableViewDelegate method
tableView(_:heightForRowAt:)
You would do all your calculations on each individual cell height in this method and return it. If you want to display the UIWebView in it's whole without the need to scroll, you should return the height of the UIWebView's scroll view contentView – plus any height for anything else you might want to display in this cell.
Follow the steps:
1) set leading, trailing, top, bottom constraint 0(zero) from webview to tableviewCell
2) Now no need to call HeightForRow method of tableView.
3) in webview delegate method
func webViewDidFinishLoad(webView: UIWebView)
{
var frame = cell.webView.frame
frame.size.height = 1
let fittingSize = cell.webView.sizeThatFits(CGSizeZero)
frame.size = fittingSize
}
4) webview scrollenabled = false
You can return, in heightForRowAtIndexPath:
tableView.rowHeight = UITableViewAutomaticDimension
You also need to have a value setted in estimatedRowHeight, like
tableView.estimatedRowHeight = 85.0
If you have all the constraints defined correctly in the Storyboard (Or programmatically), you shouldn't get any error.
Reference: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html
This is my first question so please do excuse me if I have broken guidelines.
I will post code down below and would appreciate any input. Easy technique is preferred.
So, to the question.
I have a custom cell and a label inside of it. I want the size of the label to change. I have already got a way for the table cell height to change but I haven't worked out how to change the size of the label.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("messageCell") as! MessageTableViewCell
let receiveTextSize: CGFloat = cell.receivedMessage.intrinsicContentSize().width
let sendTextSize: CGFloat = cell.sentMessage.intrinsicContentSize().width
cell.receivedMessage.alpha = 0
cell.sentMessage.alpha = 0
if messageReceived[indexPath.row] {
if receiveTextSize >= 220 {
cell.sentMessage.frame.size = CGSizeMake(cell.sentMessage.frame.width, 100)
}
cell.receivedMessage.text = messageContent[indexPath.row] as String
cell.receivedMessage.alpha = 1
} else {
if sendTextSize >= 220 {
cell.sentMessage.frame.size = CGSizeMake(cell.sentMessage.frame.width, 100)
cell.sentMessage.numberOfLines = 0
}
cell.sentMessage.text = messageContent[indexPath.row] as String
cell.sentMessage.alpha = 1
}
cell.receivedMessage.sizeToFit()
cell.receivedMessage.frame.size = CGSizeMake(100, 100)
return cell
}
Edit Solution was to set auto-constraints and then change the heightForRowAtIndexPath function for the tableView
The basic setup is like this: I have a UITableView with two proto cells that I am working with (think of a messaging app, where one cell type shows messages you send and the other, messages you recieve). Now obviously the message length can vary from one line to even 100+ lines thus I need variable cell heights.
First attempt:
tableView.estimatedRowHeight = 75
tableView.rowHeight = UITableViewAutomaticDimension
I used estimatedRowHeight in my viewDidLoad(). This works perfectly and computes cell heights very nicely. But because this is a messaging app I need to scroll the tableView all the way to bottom on viewDidLoad() and whenever a new message is recieved / sent. But the estimatedRowHeight messes with the tableView scrolling all the way to bottom. Some say it's a bug, some say it's to be expected. Nonetheless, this way won't work for me at all.
Second Attempt:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
I thought to compute heights manually as so:
let measuerWidth = UIScreen.mainScreen().bounds
var w = measuerWidth.size.width // this is so that we can limit label width to screen width so the text is forced to go to multiple lines
// I probably should use my custom cell width here, but if I try to `dequeue` that cell it's frame contents are always `zero`. Is that a problem?
let lbl = UILabel(frame: CGRect.zeroRect)
lbl.text = chatMessages[indexPath.row][ChatRoomKeys.MESSAGE_TEXT] as? String
lbl.font = UIFont.systemFontOfSize(20)
lbl.numberOfLines = 0
lbl.lineBreakMode = NSLineBreakMode.ByWordWrapping
lbl.frame = CGRectMake(0, 0, w, 0)
lbl.sizeToFit()
return lbl.frame.height + 20
This way works almost perfectly however at times cell height isn't what it should be. Meaning sometimes if the text is one line plus one word, that one word won't show because the cell height was only for one line.
Is there some better way to calculate the cell height?
UPDATE:
Here's a screenshot of kinda what happens
as you can see the label ends at x but the original text goes upto y.
UPDATE 2:
These are the proto cells I am using, the bottom cell is simply a mirror of the top one.
I use this one:
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 2)
{
NSString *cellText = #"init some text";
CGSize labelSize = [self calculateTextSize:cellText];
return labelSize.height + 20.0f;
}
return 45;
}
- (CGSize) calculateTextSize: (NSString*) text
{
UIFont *cellFont = [UIFont fontWithName:#"HelveticaNeue" size:12.0];
CGFloat width = CGRectGetWidth(self.tableView.frame) - 40.0f;
CGSize constraintSize = CGSizeMake(width, MAXFLOAT);
CGRect labelRect = [cellText boundingRectWithSize:constraintSize options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:cellFont} context:NSLineBreakByWordWrapping];
CGSize labelSize = labelRect.size;
return labelSize;
}
Edit:
CGFloat width = CGRectGetWidth(self.tableView.frame) - 40.0f;
Calculation of the maximum available width of the text, you can use self.view.frame or etc. -40.0f - because i have indentation from the edge = 20.0f
You can do this with the automatic height as you were doing. See the code below, when your view is about to appear, ask the UITableView for the number of rows in the section (0) in this case. Then you can create an NSIndexPath for the last row in that section then ask the UITableView to scroll that index path into view. Using the code below you can still have the UITableView calculate the heights for you.
import UIKit
final class ViewController: UIViewController {
// MARK: - Constants
let kFromCellIdentifier = "FromCell"
let kToCellIdentifier = "ToCell"
// MARK: - IBOutlets
#IBOutlet private weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 75.0
tableView.rowHeight = UITableViewAutomaticDimension
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let numRows = tableView.numberOfRowsInSection(0) - 1 // -1 because numbering in the array starts from 0 not 1
let indexPath = NSIndexPath(forRow: numRows, inSection: 0)
tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: false)
}
}
extension ViewController: UITableViewDataSource {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 50
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row % 2 == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier(kFromCellIdentifier, forIndexPath: indexPath) as! CustomTableViewCell
cell.configureCell(UIImage(named: "Talk")!, text: "From Some text \(indexPath.row)")
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier(kToCellIdentifier, forIndexPath: indexPath) as! CustomTableViewCell
cell.configureCell(UIImage(named: "Email")!, text: "To Some text \(indexPath.row)")
return cell
}
}
}
I have a vertically scrolling UICollectionView that uses a subclass of UICollectionViewFlowLayout to try and eliminate inter-item spacing. This would result in something that looks similar to a UITableView, but I need the CollectionView for other purposes. There is a problem in my implementation of the FlowLayout subclass that causes cells to disappear when scrolling fast. Here is the code for my FlowLayout subclass:
EDIT: See Comments For Update
class ListLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
if var answer = super.layoutAttributesForElementsInRect(rect) {
for attr in (answer as [UICollectionViewLayoutAttributes]) {
let ip = attr.indexPath
attr.frame = self.layoutAttributesForItemAtIndexPath(ip).frame
}
return answer;
}
return nil
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let currentItemAtts = super.layoutAttributesForItemAtIndexPath(indexPath) as UICollectionViewLayoutAttributes
if indexPath.item == 0 {
var frame = currentItemAtts.frame
frame.origin.y = 0
currentItemAtts.frame = frame
return currentItemAtts
}
let prevIP = NSIndexPath(forItem: indexPath.item - 1, inSection: indexPath.section)
let prevFrame = self.layoutAttributesForItemAtIndexPath(prevIP).frame
let prevFrameTopPoint = prevFrame.origin.y + prevFrame.size.height
var frame = currentItemAtts.frame
frame.origin.y = prevFrameTopPoint
currentItemAtts.frame = frame
return currentItemAtts
}
}
One other thing to note: My cells are variable height. Their height is set by overriding preferredLayoutAttributesFittingAttributes in the subclass of the custom cell:
override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes! {
let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as UICollectionViewLayoutAttributes
attr.frame.size = CGSizeMake(self.frame.size.width, myHeight)
return attr
}
And I set the layout's estimated size on initialization:
flowLayout.estimatedItemSize = CGSize(width: UIScreen.mainScreen().bounds.size.width, height: 60)
Here is a GIF that demonstrates this problem:
Does anybody have an idea as to what's going on? Your help is much appreciated.
Thanks!