I am creating a chat feature in my app, but the cells are getting cut off. I've reviewed the Ray Wenderlich tutorial and the SO questions, but I think my problem is that I combine code with auto layout and I'm missing something (for instance, I don't put a label for the message text on the storyboard, but I do put the imageView). Here's what it looks like when I run it:
Here is the code for the tableView setup:
In viewDidLoad:
tableView.estimatedRowHeight = 600
tableView.rowHeight = UITableView.automaticDimension
Other code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Get a preconfigured messageCell
let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Storyboard.messageCell) as! MessageCell
// Send the message to it for set up.
cell.setMessage(message: messages[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
And here is the setMessage function in the custom cell:
#IBOutlet var messageImage: UIImageView!
func setMessage(message: Message) {
if message.who == "me" {
// User wrote this message, show it on the right in blue as an outgoing message.
let text = message.txt
let label = UILabel()
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 18)
label.textColor = .white
label.text = text
let constrainRect = CGSize(width: 0.66 * self.frame.width,
height: .greatestFiniteMagnitude)
let boundingBox = text.boundingRect(with: constrainRect, options: .usesLineFragmentOrigin, attributes: [.font: label.font!], context: nil)
label.frame.size = CGSize(width: ceil(boundingBox.width), height: ceil(boundingBox.height))
let bubbleImageSize = CGSize(width: label.frame.width + 28, height: label.frame.height + 20)
messageImage = UIImageView(frame: CGRect(
x: self.frame.width - bubbleImageSize.width - 20,
y: self.frame.height - (bubbleImageSize.height),
width: bubbleImageSize.width,
height: bubbleImageSize.height))
let bubbleImage = UIImage(named: "outgoing-message-bubble")?
.resizableImage(withCapInsets: UIEdgeInsets(top: 17, left: 21, bottom: 17, right: 21), resizingMode: .stretch)
.withRenderingMode(UIImage.RenderingMode.alwaysTemplate)
messageImage.image = bubbleImage
self.addSubview(messageImage)
label.center = messageImage.center
self.addSubview(label)
} else {
// Other person wrote this message. Show it on the left in gray.
}
}
There are no constraints on messageImage, but I have tried it with constraints of zero around it and the result is the same. I've run the code with breaks and confirmed the sizes are all accurate for the text and the bubble image. But the content view of the cell is cutting off the height to a standard cell size. I've worked on this for over a day. Any help would be appreciated.
Related
I'm trying to create a calendar like as Google or iOS has. For calendar I'm using amazing FSCalendar. It work perfectly. But I have a problem with tableview for day events. I created uitableview with timeline (1 cell = 30 minutes). But how to add events to here that look like Google or iOS calendar, for example
I think I need to add buttons for each events and add it to uitableview.
This is my code:
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (hour_end - hours_begin) * step
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
cell.timeLabel.layer.zPosition = 1
cell.timeLabel.isHidden = true
if indexPath.row % step == 0 && indexPath.row != ((hour_end - hours_begin) * step) - 1 {
let hour = indexPath.row / step + hours_begin
cell.timeLabel.text = (hour < 10 ? "0": "") + String(hour) + ":00"
cell.timeLabel.layer.zPosition = 10
cell.timeLabel.isHidden = false
cell.separatorInset = UIEdgeInsets(top: 0, left: 50, bottom: 0, right: 0)
}
else {
cell.timeLabel.text = ""
cell.timeLabel.isHidden = true
cell.separatorInset = UIEdgeInsets(top: 0, left: 2000, bottom: 0, right: 0)
cell.timeLabel.layer.zPosition = 1
}
add_event(indexPath: indexPath, nRef: 5, offset: 0, height: 64.0)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
}
func add_event(indexPath: IndexPath, nRef: Int, offset: CGFloat, height: CGFloat)
{
let row = indexPath.row
let rectOfCellInTableViewCoordinates = tableView.rectForRow(at: indexPath)
let rectOfCellInSuperviewCoordinates = view.convert(rectOfCellInTableViewCoordinates, to: tableView.superview)
print("\(row) \(rectOfCellInSuperviewCoordinates.origin.x) \(rectOfCellInSuperviewCoordinates.origin.y)")
if row == nRef {
if let z = view.viewWithTag(nRef) as! UIButton?{
print("z found")
z.removeFromSuperview()
}
let btn_x = 50
let btn_width = tableView.frame.width - 50
let frame = CGRect(x: CGFloat(btn_x), y: rectOfCellInSuperviewCoordinates.origin.y + offset, width: btn_width, height: height)
let overlay_btn = UIButton(frame: frame)
overlay_btn.backgroundColor = UIColor(red: 0.5, green: 0.0, blue: 0.5, alpha: 0.3)
overlay_btn.setTitle("Press for more details", for: .normal)
overlay_btn.setTitleColor(UIColor.black, for: .normal)
overlay_btn.layer.cornerRadius = 8
overlay_btn.tag = 5
tableView.addSubview(overlay_btn)
}
}
But, I think it is not great solution. Any ideas?
I would recommend using a scroll view for what you are trying to accomplish. Instead of using cells and adding views... splitting them up between cells will be tricky. Instead, you can have your scroll view that has all of the dividers and hour marks.
Implement a custom UIView that will serve as an event. When the user creates an event, it will be a subview of your scrollview. Say for example your scrollview is 2400px long and each hour is 100px, If the event starts at 12pm, add the UIView to the 1200px mark...
That is a rough explanation of how I would approach this problem. Feel free to reach out if you have further questions.
How do I go about dynamically changing the UITableViewCell height? I've tried implementing the following, but for some reason, it isn't working. The code crashes as soon as I load the view controller displaying this table view
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cell = tableView.cellForRow(at: indexPath) as! AvailableRideCell
return cell.getHeight()
}
This is the getHeight function in my AvailableRideCell
func getHeight() -> CGFloat {
return self.destinationLabel.optimalHeight + 8 + self.originLabel.optimalHeight + 8 + self.priceLabel.optimalHeight
}
And this is the optimalHeight function
extension UILabel {
var optimalHeight : CGFloat {
get {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = self.font
label.text = self.text
label.sizeToFit()
return label.frame.height
}
}
}
Keep in mind that UITableViewCell is reused. So getting the height of the current cell can be unstable.
A better way is to have one fake/placeholder cell (I call the calculator cell) and use that to calculate the size of the cell.
So in the heightForRowAt method, you get the data instead of the cell.
Put that data inside the calculator cell and get the height from there.
You code crashes because of this line
let cell = tableView.cellForRow(at: indexPath) as! AvailableRideCell
From Apple Documentation we know that this method is used for optimization. The whole idea is to get cells heights without wasting time to create the cells itself. So this method called before initializing any cell, and tableView.cellForRow(at: indexPath) returns nil. Because there are no any cells yet. But you're making force unwrapping with as! AvailableRideCell and your code crashed.
So at first, you need to understand, why you should not use any cells inside the cellForRow(at ) method.
After that, you need to change the logic so you could compute content height without calling a cell.
For example, in my projects, I've used this solution
String implementation
extension String {
func height(for width: CGFloat, font: UIFont) -> CGFloat {
let maxSize = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
let actualSize = self.boundingRect(with: maxSize,
options: [.usesLineFragmentOrigin],
attributes: [NSAttributedStringKey.font: font],
context: nil)
return actualSize.height
}
}
UILabel implementation
extension String {
func height(for width: CGFloat, font: UIFont) -> CGFloat {
let labelFrame = CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude)
let label = UILabel(frame: labelFrame)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.font = font
label.text = self
label.sizeToFit()
return label.frame.height
}
}
With that, all you need to do is to compute your label and store its font.
var titles = ["dog", "cat", "cow"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// number of rows is equal to the count of elements in array
return titles.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cellTitle = titles[indexPath.row]
return cellTitle.height(forWidth: labelWidth, font: labelFont)
}
Dynamic rows height changing
If you'll need to update row height, all you need to do is to call this method when your content had been changed. indexPath is the index path of changed item.
tableView.reloadRows(at: [indexPath], with: .automatic)
Hope it helps you.
You don't mention if you are using Auto Layout, but if you are, you can let Auto Layout manage the height of each row. You don't need to implement heightForRow, instead set:
tableView.rowHeight = UITableViewAutomaticDimension
And configure your UILabel with constraints that pin it to the cell's content view:
let margins = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
cellLabel.leadingAnchor.constraint(equalTo: margins.leadingAnchor),
cellLabel.trailingAnchor.constraint(equalTo: margins.trailingAnchor),
cellLabel.topAnchor.constraint(equalTo: margins.topAnchor),
cellLabel.bottomAnchor.constraint(equalTo: margins.bottomAnchor)
])
Each row will expand or contract to fit the label's intrinsic content size. The height is automatically adjusted if the device landscape/portrait orientation changes, without re-loading the cell. If you want the row height to change automatically when the device's UIContentSizeCategory changes, set the following:
cellLabel.adjustsFontForContentSizeCategory = true
I am making UITableView custom cell because height also changed per cell.
This is my code for initialize cell and after that i want to add UITextView as Subview.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
let dictAd = self.arrAllQuestions[indexPath.row] as! NSDictionary
let fontNew = UIFont(name: "AvenirNext-Regular", size: 19.0)
let strA = "\(indexPath.row+1). \(dictAd.object(forKey: "Title")!)"
let heightTitle = self.heightForView(text: strA, font: fontNew!, width: tableView.frame.size.width-16)
return heightTitle+5;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let dictAd = self.arrAllQuestions[indexPath.row] as! NSDictionary
let cell = tableView.dequeueReusableCell(withIdentifier: "quesionCell", for: indexPath) as! QuestionCell
let fontNew = UIFont(name: "AvenirNext-Regular", size: 19.0)
let strA = "\(indexPath.row+1). \(dictAd.object(forKey: "Title")!)"
cell.lblName.font = fontNew
cell.lblName.text = strA
cell.lblName.numberOfLines = 0
let heightTitle = self.heightForView(text: strA, font: fontNew!, width: tableView.frame.size.width-16)
var frame = cell.lblName.frame as CGRect
frame.size.height = heightTitle; //you need to adjust this value
cell.lblName.frame = frame;
let txtView = AnimatableTextView(frame: CGRect(x: 8, y: cell.lblName.frame.origin.y+cell.lblName.frame.size.height+5, width: tableView.frame.size.width-16, height: 25))
txtView.borderWidth = 1.0
txtView.borderColor = UIColor.lightGray
txtView.cornerRadius = 4.0
cell.contentView.addSubview(txtView)
return cell
}
You can see output below.
It seems that the height calculation in your heightForRowAtIndexPath is not proper. Consider using self-sizing cells using UITableViewAutomaticDimension and Autolayout to solve your issues. Here is a great tutorial that can help you to get started. One more suggestion if you are using UITextView or subclass of the same in a cell and you want it to take the height of the content set its scrollable property to false.
Declare this in the viewDidLoad
Hope this will help you
tableView.estimatedRowHeight = 44
I am implementing a view that displays a lot of information. The view is scrollable and inside of the view I implemented a non-scrollable table view holding user comments. I have all the auto-layout constraints and it appears to layout correctly however touches are not received below a certain row. It appears that the table view or something is blocking the views below from receiving the events but I am unable to trace down the issue.
I want the main scroll view's content size to grow as the comment table view grows. Keeping the post comment view at the bottom of the table view. Right now I can't select the last cell or the text field.
Comment Cell View
Simulator screenshot
Here is the code from the table view implementation:
commentsTableView.delegate = self
commentsTableView.dataSource = self
commentsTableView.estimatedRowHeight = 82
commentsTableView.rowHeight = UITableViewAutomaticDimension
commentsTableView.sectionHeaderHeight = UITableViewAutomaticDimension
commentsTableView.estimatedSectionHeaderHeight = 54
commentsTableView.estimatedSectionFooterHeight = 0
commentsTableView.sectionHeaderHeight = UITableViewAutomaticDimension
commentsTableView.tableFooterView = UIView(frame: CGRectZero)
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Comments"
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return comments.count
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section != 0 { return nil }
let sectionTitle: String = self.tableView(tableView, titleForHeaderInSection: section)!
if sectionTitle == "" {
return nil
}
let view = UIView(frame: CGRectMake(0, 0, tableView.frame.width, 54))
let title = UILabel(frame: CGRectMake(60, 22, tableView.frame.width, 17))
view.addSubview(title)
title.text = sectionTitle
title.textColor = UIColor(red: (74 / 255), green: (74 / 255), blue: (74 / 255), alpha: 1.0)
title.backgroundColor = UIColor.clearColor()
view.backgroundColor = UIColor.clearColor()
title.font = UIFont(name: "ProximaNova-Semibold", size: 16.0)
view.layer.addBorder(.Bottom, color: UIColor.lightGrayColor().colorWithAlphaComponent(0.75), thickness: 0.5)
title.setNeedsDisplay()
view.setNeedsDisplay()
return view
}
You have to set the userInteractionEnabled to true to fire those events.
view.userInteractionEnabled = true
The Comment TextField is the last row of your comment TableView. So put comment TextField code in footer of the TableView as follows:
func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let commentTextFieldView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 50))
// Your Comment TextField code
return commentTextFieldView
}
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 50.0
}
There is an another way to assign Comment TextField view as tableView footer:
myTableView.tableFooterView = commentTextFieldView
This question already has answers here:
How to customise header section in static cell?
(3 answers)
Closed 6 years ago.
I have this UITableView with static cells:
I'd like to change the header field and center horizontally in the cell. How can I do using Swift?
Thanks in advance.
first of all, of course you can do this using swift, and now this is the code that you need to put in your viewController to make this work
you need to put your viewController as UITableViewDelegate and then implement this methods
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return X; //X is the value of height of your header
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRect(x: 0, y: 0, width: self.tableView.frame.size.width, height: X)) //X is the value of height of your header
let label = UILabel(frame: headerView.frame)
label.text = "TESTING"
label.textAlignment = NSTextAlignment.Center
headerView.addSubview(label)
return headerView;
}
I hope this help you
Sounds like you are wanting to use:
tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
}
Using this you could code the UI you are looking for, create the label and set the frame to what you need and set the text to what you would want it to be. Inside would either if check or use a switch statement for section, and set the text accordingly, then simply return the label or view, however you decide to implement your solution. I have done something like this in a tableView footer, I'll post the code and link to the documentation for reference:
func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footerView = UIView.init(frame: CGRectMake(0, 0, tableView.frame.size.width, 50))
//label
let label : UILabel = UILabel.init(frame: CGRectMake(30, 0, 150, 50))
label.text = "Calorie Total :"
//label to hold calorie total for the section
let totalLabel = UILabel.init(frame: CGRectMake(185, 0, 50, 50))
var total = Int()
//grab the total for the section
switch section {
case 0:
total = calculateCalories(breakfastFoodArray)
break
case 1:
total = calculateCalories(lunchFoodArray)
break
case 2:
total = calculateCalories(dinnerFoodArray)
break
case 3:
total = calculateCalories(snackFoodArray)
break
default:
break
}
totalLabel.text = String(total)
footerView.addSubview(label)
footerView.addSubview(totalLabel)
let footerExentsionView = UIView.init(frame: CGRectMake(0, 50, tableView.frame.size.width, 10))
footerExentsionView.backgroundColor = UIColor.whiteColor()
footerView.addSubview(footerExentsionView)
return footerView
}