Resizable TableView Cell - ios

So I have been trying to create a generic chat bubble-ish look with a resizable view and label inside a UITableView cell. Things were working well until I tried to add in the resizable feature. It cuts off just a bit at the bottom (or does not give any margin), and I have not worked with completely dynamic cells like this before so I am not sure how to fix this. I tried adding a 20px buffer but it did not help. I appreciate the help!
(Code below)
import QuartzCore
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var listOfStrings = [String] ()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// When I Uncomment the 2 lines below, the cell cuts off and only displays a little bit of the blue view.
// self.tableView.rowHeight = UITableViewAutomaticDimension
// self.tableView.estimatedRowHeight = 75
listOfStrings.append("Switch, Button, Segmented Control, Slider, Textfield")
listOfStrings.append("Switch, Button, Segmented Control, Slider, Textfield")
listOfStrings.append("Switch, Button, Segmented Control, Slider, Textfield")
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print(indexPath.row)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("someCell") as! SomeTableViewCell
cell.contentView.viewWithTag(0)!.backgroundColor = UIColor.clearColor()
let size = cell.layer.bounds
let tableSize = self.tableView.layer.bounds
let viewCGR = CGRect(x: size.minX, y: size.height/2, width: tableSize.width, height: size.height/2)
let view: UIView = UIView(frame: viewCGR)
let labelCGR = CGRect(x: 0, y: 0, width: viewCGR.width, height: viewCGR.height)
let label: UILabel = UILabel(frame: labelCGR)
label.numberOfLines = 0
label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)
label.textAlignment = NSTextAlignment.Center
label.text = listOfStrings[indexPath.row]
label.sizeToFit()
self.tableView.updateConstraints()
let newViewCGR = CGRect(x: viewCGR.minX, y: viewCGR.minY, width: label.frame.width+20, height: label.frame.height+20)
view.frame = newViewCGR
view.sizeToFit()
label.textColor = UIColor.blackColor()
label.center.x = view.center.x
// self.tableView.updateConstraints()
view.addSubview(label)
self.tableView.updateConstraints()
view.backgroundColor = UIColor.blueColor()
view.layer.cornerRadius = 6
cell.addSubview(view)
self.tableView.updateConstraints()
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listOfStrings.count
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Btw I am not using autolayout + constraints to get the functionality of having the bubble be able to appear on the left or right (depending on incoming or outgoing).

After the new API by apple you don't calculate height. You just use autolayout to do the work. I think this link could help you a lot. By the way there is a nice tutorial Self-sizing Table View Cells

you don't have to calculate height row for height cell .
you add this code in viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 50

#Ryan find the height of the cell with the text using following method,
CGRect textRect =
[attributedText boundingRectWithSize:CGSizeMake(cellWidth, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
context:nil];
call this method from
heightForRowAtIndexPath:
and set the height of the cell here as textRect.size.height

Related

Swift: Continuous resizing of tableViewCell while user types in a UITextView

Introduction
Context:
I'm creating one of my first apps but I've ran into an issue I cannot figure out.
I have tableView with cells packed with quite a few UIElements. All constraints are done using the EasyPeasy library which basically just sets auto layout constraints ( I have tried setting them manually also). The UITextView in question is constrained by various numbers to the left, right, top and bottom, I have no constraints on it for height or width.
in cellForRowAt indexPath: I set the textView delegate for each cells textView to self, using a delegate property declared within the cells custom class. I also tag every textView with its cells indexPath.row (gives textView.tag integer in textViewDidChange method).
Issue/acknowledgments:
After browsing SO a lot I've found a few questions alike this but I have not been able to make them work for me, I have implemented parts of them that felt logic to my case. I believe the problem differencing my situation from those questions lies in that for my cell design to work the cells has to have a height of itemHeight or higher.
I have noticed that as I type into the textview the textview itself increases in height (even below the cells border but its not visible as it reaches that point), however the cell itself doesn't resize.
I've tried with a cell that only contains a textView so the problem must lie in the textViewDidchange or heightForRowAt indexPath methods.
Question:
What am I doing wrong here? Why doesn't the cells height change dynamically as I type in the textView?
Code:
func textViewDidChange(_ textView: UITextView) {
var newframe = textView.frame
newframe.size.height = textView.contentSize.height - textView.frame.size.height + itemHeight[textView.tag]
textView.frame = newframe
let ndxPath = IndexPath(row: textView.tag, section: 0)
let cell = tableView.cellForRow(at: ndxPath) as! EventsCell
cell.frame = CGRect(x: cell.frame.origin.x, y: cell.frame.origin.y, width: cell.frame.width, height: textView.frame.height)
tableView.beginUpdates()
tableView.setNeedsLayout() //have tried without this line
tableView.layoutIfNeeded() //have tried without this line
tableView.endUpdates()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if UITableViewAutomaticDimension > itemHeight[indexPath.row] {
return UITableViewAutomaticDimension
} else {
return itemHeight[indexPath.row]
}
}
TextView constraints:
let containerView : UIView = {
let cv = UIView(frame: .zero)
cv.backgroundColor = .white
cv.translatesAutoresizingMaskIntoConstraints = false
cv.layer.cornerRadius = 7
return cv
}()
let eventText : GrowingTextView = { // GrowingTextView is a extension to a regular UITextView
let tv = GrowingTextView()
tv.allowsEditingTextAttributes = true
tv.isScrollEnabled = false
var delegate: UITextViewDelegate?
tv.textContainerInset = UIEdgeInsetsMake(1, 1, 0, 1)
tv.autoresizingMask = .flexibleHeight
tv.translatesAutoresizingMaskIntoConstraints = false
return tv
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
containerView.addSubview(eventText)
contentView.addSubview(containerView)
containerView .easy.layout([Height(CGFloat(95 * itemCount)), Left(8), Right(8)])
eventText .easy.layout([Left(77), Right(5), Top(90), Bottom(4)])
}
Thanks for reading my post.
The constraints that determine the height should be laid out in such a way that the textView is attached directly to the top and bottom of the contentView or to views which are connected to the top and bottom of the contentView, so that autolayout can make out the height by connecting the constraints.
Make sure that you do not mention a height for the textView and disable scrolling. Let automatic dimension take care of all that.
Now all you need to do is call tableView.beginUpdates() and tableView.endUpdates() on textViewDidChange
Here is my repo which demonstrates the same.
OP Edit:
You should store the additional height that you add in a variable in the cell class so the cells can reload an appropriate height when the tableVIew is reloaded.
You should also change textViewDidChange method
cell.frame = CGRect(x: cell.frame.origin.x, y: cell.frame.origin.y, width: cell.frame.width, height: textView.frame.height)
to
let newFrame = ”originalCellHeight” - ”originalTextViewHeight” + textView.contentSize.height
cell.frame = CGRect(x: cell.frame.origin.x, y: cell.frame.origin.y, width: cell.frame.width, height: newFrame )`

Set width and height of UITextView based on the dynamic text

I'm building a chat application where each message are inserted into a row inside a table. Each row contains an avatar and a message. I want to set the width and height of the UITextArea as per the length of the text to put inside.
Below is the code I've used. But here, both height and width are constant (200x50)
PS: I'm a newbie to Swift and ios and I'm using Swift 3. Every code I got after searching is in objective-c or swift 2
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("ChatBox1", owner: self, options: nil)?.first as! ChatBox1
let myTextField: UITextView = UITextView(frame: CGRect(x: 30, y: 20, width: 200.00, height: 50.00));
cell.addSubview(myTextField)
myTextField.isScrollEnabled = false
myTextField.isUserInteractionEnabled = false
myTextField.backgroundColor = UIColor.lightGray
myTextField.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
myTextField.layer.cornerRadius = 5
print(myTextField.intrinsicContentSize)
let image = UIImage(named: "agent")
let imageView = UIImageView(image: image)
imageView.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
cell.addSubview(imageView)
return cell
}
This two methods are done to set Height and width of textview according to the text on chat.
You can use these. (swift 2.3)
var screenrect = UIScreen.mainScreen().bounds
func GET_WIDTH(textView : UITextView , text : String)-> CGFloat
{
textView.text = text;
let size = textView.bounds.size
let newSizeWidth = textView.sizeThatFits(CGSize(width: CGFloat.max, height: size.height))
// Resize the cell only when cell's size is changed
if size.width != newSizeWidth.width
{
if( newSizeWidth.width > screenrect.width-120 )
{
textView.layer.frame.size.width = screenrect.width-120;
}
else if ( newSizeWidth.width < 40 )
{
textView.layer.frame.size.width = 40
}
else
{
textView.layer.frame.size.width = newSizeWidth.width;
}
}
return textView.layer.frame.size.width + 40;
}
func GET_HEIGHT(textView : UITextView , text : String)-> CGFloat
{
textView.text = text;
let size2 = textView.bounds.size
let newSize = textView.sizeThatFits(CGSize(width: size2.width, height: CGFloat.max))
if size2.height != newSize.height
{
if( newSize.height < 40 )
{
textView.layer.frame.size.height = 40
}
else
{
textView.layer.frame.size.height = newSize.height
}
}
return textView.layer.frame.size.height + 15;
}
You have to call the functions of the textview like this to set height and width.
let textViewRight=UITextView(frame: CGRectMake(0, 4, 40, 40));
textViewRight.layer.frame.size.width = GET_WIDTH(textViewRight, text: currentObject.chat_userMessage)
textViewRight.layer.frame.size.height = GET_HEIGHT(textViewRight, text: currentObject.chat_userMessage)
Try this code:
Answer 1:
func tableView(tableView:UITableView!, numberOfRowsInSection section: Int) -> Int {
yourTableViewName.estimatedRowHeight = 44.0 // Standard tableViewCell size
yourTableViewName.rowHeight = UITableViewAutomaticDimension
return yourArrayName.count }
And also put this code inside your Cell for incase...
yourCell.sizeToFit()
Answer 2:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
in viewDidLoad Add this
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
Just add these two lines and your problem will b solved
Take textView height constraint and set it equal to the content size of the textView.
Alternatively, if you use autolayout you can set the content hugging and compression property to 1000. It should expand your cell according to the content.

UITableView Section Header Change Style of Current Header

Does anyone know of a built in method or custom way to access and change styles of the CURRENT section header in a UITableView (style plain) as the UITableView is scrolled in Swift.
My preset style for the header is:
override func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView //recast your view as a UITableViewHeaderFooterView
header.textLabel.font = UIFont(name: "HelveticaNeue-CondensedBold", size: 14)
header.contentView.backgroundColor = UIColor.groupTableViewBackgroundColor()
header.textLabel.textColor = UIColor.grayColor()
}
Specifically I would like to change the header background color to black and the text color to white only for the current section header as the view scrolls. The style for other headers remain in the preset style.
In a current Application of mine:
override public func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let view: UIView
if let _view: UIView = tableView.headerViewForSection(section)
{
view = _view
} else {
let dxOffset: CGFloat = 16.0
view = UIView(frame: CGRectMake(dxOffset, 0, tableView.frame.size.width - dxOffset, TableViewViewsHeight.sectionHeight))
}
// create our label
let label: UILabel = UILabel(frame: view.frame)
label.textColor = UIColor.appEmptyTextColor()
label.text = "\(self.letters[section])"
label.font = UIFont.systemFontOfSize(UIFont.smallSystemFontSize() + 4.0)
// create the separator frame
var separatorFrame: CGRect = view.frame
separatorFrame.size = CGSizeMake(separatorFrame.size.width, 1.0)
separatorFrame.offset(dx: 0.0, dy: view.frame.size.height - 1.0)
// create the separator
let imageView: UIImageView = UIImageView(frame: separatorFrame)
imageView.backgroundColor = UIColor.appEmptyGolfTrainingTextColor()
imageView.alpha = 0.4
// add subviews
view.addSubview(label)
view.addSubview(imageView)
// setup the view
view.backgroundColor = UIColor.whiteColor()
return view
}
This creates a header with a white background, a separator and a label containing a letter.
You should be using func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? to change the appearance of your section header.

Set TableView height by the number or rows

I have got TableView in the MainstoryBoard and the number of rows is random every time.
I want the height of the whole TableView to be flexible - by that I mean, for example: if I got 4 rows in the TableView and each TableView row height is 22 so the TableView height will be 88.
Another example:
number of rows: 2
row height = 22
TableView will be 44.
How can I make it?
You can change the UITableView height as per the contentSize as below:
Swift 2.2
tableView.frame = CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y, tableView.frame.size.width, tableView.contentSize.height)
Swift 3 or 4+
tableView.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: tableView.contentSize.height)
and make sure you write the above line in viewDidAppear method
You need to write the above line in viewDidLayoutSubviews also.
Swift 2.2
func viewDidLayoutSubviews(){
tableView.frame = CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y, tableView.frame.size.width, tableView.contentSize.height)
tableView.reloadData()
}
Swift 3 or 4+
func viewDidLayoutSubviews(){
tableView.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: tableView.contentSize.height)
tableView.reloadData()
}
Simply take outlet for tableview height constraint and update it in willDisplay cell: UITableViewCell method of UITableView
#IBOutlet weak var tblHConstraint: NSLayoutConstraint!
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.tblHConstraint.constant = self.tblView.contentSize.height
}
nothing worked here is the solution i ended up using which gives accurate height.
extension UITableView {
var contentSizeHeight: CGFloat {
var height = CGFloat(0)
for section in 0..<numberOfSections {
height = height + rectForHeader(inSection: section).height
let rows = numberOfRows(inSection: section)
for row in 0..<rows {
height = height + rectForRow(at: IndexPath(row: row, section: section)).height
}
}
return height
}
}
Usage:
tableView.contentSizeHeight
will give you the actual calculated height of your table view content.
Take outlet of the Height Constraint of the parent view and assign it the height of table view's content + constant(extra height of other contents in the view)
heightConstraintView.constant = tableView.contentSize.height + constantValue //use the value of constant as required.
and write this code in the cellForRowAt method.
To use with with autolayout:
Somewhere in viewDidLoad()
tableView.anchor(top: view.topAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor)
and then in your viewDidLayoutSubViews()
tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true
DispatchQueue.main.async {
var frame = tableView.frame
frame.size.height = tableView.contentSize.height
tableView.frame = frame
}
OR
DispatchQueue.main.async {
self.TblViewHeightConstraint.constant = CGFloat((self.array.count) * 30)//Here 30 is my cell height
self.TblView.reloadData()
}
Please find the below code.
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UIScreen.main.bounds.height
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let tableViewHeight = self.tableView.frame.height
let contentHeight = self.tableView.contentSize.height
let centeringInset = (tableViewHeight - contentHeight) / 2.0
let topInset = max(centeringInset, 0.0)
self.tableView.contentInset = UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0)
}
Use this for Swift 3
override func viewDidLayoutSubviews(){
tableView.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: tableView.contentSize.height)
tableView.reloadData()
}
Note: when your are increasing the tableview height it will goes out side the view. and you will get a problem with scrolling.
Ex: take a view inside that keep tableview and give constraints to left,right,bottom,top to Zero(0).
now reload the tableview assume 2 rows each row height is 20 now total rows height is 40. its fine and gave those height to table view, Table view will show only 40 height (you can assign tableview height by taking constraints (or) table view content size both are give same height according to rows height).
But if you reload 50 rows you will get scrolling problem because those rows height given to table view height, here table view height expands more than your screen height.
To solve this problem you should use scroll view outside the tableview.
Take outlet of tableview height and set it with your tableview row height in cellForRowAt like this,
tableviewHeight.constant = (tableView.contentSize.height * no_of_rows) + bottom_space

View for Header in section with different height in different UITableViews

This is my Split View Controller with two controllers. In both I use UITableView. In both I implement tableView(viewForHeaderInSection:) method.
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
var title = "Some Title"
return UIView.viewForHeaderInSectionWithTitle(title)
}
My UIView extesion:
extension UIView {
class func viewForHeaderInSectionWithTitle(title: String) -> UIView? {
var headerView: UIView?
headerView = UIView(frame: CGRectMake(0, 0, CGRectGetWidth(UIScreen.mainScreen().bounds), 25))
headerView!.backgroundColor = UIColor.catskillWhite()
let frame = CGRectMake(15, 3, CGRectGetWidth(headerView!.frame) - 15, 19)
let label = UILabel(frame: frame)
label.text = title
label.numberOfLines = 0
label.textColor = UIColor.curiousBlue()
label.font = UIFont.latoLightFontWithSize(15)
label.sizeToFit()
headerView!.addSubview(label)
return headerView
}
}
So same source, but different result. Why? The height is different, and label is not centered in the second one. Any ideas?

Resources