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
Related
I have a problem with some TextField inside a UITableViewCell; i got two textfield inside of the table cell, when I tap on the the textField everything works fine as you can see in this two screenshots
1.
The number in red squares are my UITextField, when I tap on one of them it works fine
But when i click on the other textField the entire cell disappear like this
I have an empty space after the click on the other textfield
I have no function implemented, only a function that change font and textColor
func setPickers() {
self.hourPicker.delegate = self
self.minutePicker.delegate = self
hourPicker.textColor = theme.grey
minutePicker.textColor = theme.grey
hourPicker.background = UIImage()
minutePicker.background = UIImage()
hourPicker.textAlignment = .center
minutePicker.textAlignment = .center
hourPicker.font = UIFont(name: "Roboto-Regular", size: 48)
minutePicker.font = UIFont(name: "Roboto-Regular", size: 48)
}
This the cell in my storyboard
EDIT 2
Look my graphic debug what shows before the bug
And after it
The cell is called EventDetailFooterTableViewCell
EDIT 3
Here is where I initialize the cellView for the footer
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "footerCell") as! EventDetailFooterTableViewCell
cell.event = self.event
cell.delegate = self
cell.setView()
cell.backgroundColor = theme.mainColor
return cell
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
var height: CGFloat = 180.0
var calendar = NSCalendar.current
calendar.timeZone = TimeZone(abbreviation: "UTC")! //OR NSTimeZone.localTimeZone()
let dateAtMidnight = calendar.startOfDay(for: Date())
let todayLong = dateAtMidnight.millisecondsSince1970
if let eventDay = event.dateTime?.millisecondsSince1970 {
if eventDay >= todayLong {
height = 280
}
}
return height
}
I see a problem. You are using regular cell as section footer and there is why you see unpredictable behaviour. You should use UITableViewHeaderFooterView instead. It is if you really need such design. Better solution will be to remove footer and make it cell instead.
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've made an UITableView and filled it with JSON data I get inside my API. I get and place all correctly but when I scroll or delete a row everything gets messed up!
Labels and images interfere; this is my code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
var dict = productsArrayResult[indexPath.row]
let cellImage = UIImageView(frame: CGRect(x: 5, y: 5, width: view.frame.size.width / 3, height: 90))
cellImage.contentMode = .scaleAspectFit
let productMainImageString = dict["id"] as! Int
let url = "https://example.com/api/DigitalCatalog/v1/getImage?id=\(productMainImageString)&name=primary"
self.downloadImage(url, inView: cellImage)
cell.addSubview(cellImage)
let cellTitle = UILabel(frame: CGRect(x: view.frame.size.width / 3, y: 5, width: (view.frame.size.width / 3) * 1.9, height: 40))
cellTitle.textColor = UIColor.darkGray
cellTitle.textAlignment = .right
cellTitle.text = dict["title"] as? String
cellTitle.font = cellTitle.font.withSize(self.view.frame.height * self.relativeFontConstantT)
cell.addSubview(cellTitle)
let cellDescription = UILabel(frame: CGRect(x: view.frame.size.width / 3, y: 55, width: (view.frame.size.width / 3) * 1.9, height: 40))
cellDescription.textColor = UIColor.darkGray
cellDescription.textAlignment = .right
cellDescription.text = dict["description"] as? String
cellDescription.font = cellDescription.font.withSize(self.view.frame.height * self.relativeFontConstant)
cell.addSubview(cellDescription)
return cell
}
You are adding subviews multiple times while dequeuing reusable cells. What you can do is make a prototype cell either in storyboard or as xib file and then dequeue that cell at cellForRowAtIndexPath.
Your custom class for cell will look similar to this where outlets are drawn from prototype cell.
Note: You need to assign Reusable Identifier for that prototype cell.
class DemoProtoTypeCell: UITableViewCell {
#IBOutlet var titleLabel: UILabel!
#IBOutlet var descriptionLabel: UILabel!
#IBOutlet var titleImageView: UIImageView!
}
Now you can deque DemoProtoTypeCell and use accordingly.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: DemoProtoTypeCell.self), for: indexPath) as! DemoProtoTypeCell
cell.titleImageView.image = UIImage(named: "demoImage")
cell.titleLabel.text = "demoTitle"
cell.descriptionLabel.text = "Your description will go here."
return cell
}
That's because you are adding subviews to reused (so that it may already have subviews added previously) cells.
Try to check if the cell has subviews and fill in information you need, if there're no subviews then you add them to the cell.
Option 1
if let imageView = cell.viewWithTag(1) {
imageView.image = //your image
} else {
let imageView = UIImageView(//with your settings)
imageView.tag = 1
cell.addSubview(imageView)
}
Option 2
Crete UITableViewCell subclass that already has all the subviews you need.
I have used below method to remove all subviews from cell:
override func prepareForReuse() {
for views in self.subviews {
views.removeFromSuperview()
}
}
But I have created UITableViewCell subclass and declared this method in it.
you can also do one thing as #sCha has suggested. Add tags to the subviews and then use the same method to remove subview from cell:
override func prepareForReuse() {
for view in self.subviews {
if view.tag == 1 {
view.removeFromSuperview()
}
}
}
Hope this helps.
I think the other answers already mentioned a solution. You should subclass the tableview cell and just change the values of your layout elements for each row.
But I want to explain why you get this strange behaviour.
When you call
tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
it tries to reuse an already created cell with the passed identifier #"cell". This saves memory and optimises the performance. If not possible it creates a new one.
So now we got a cell with layout elements already in place and filled with your data. Your code then adds new elements on top of the old ones. Thats why your layout is messed up. And it only shows if you scroll, because the first cells got no previous cells to load.
When you subclass the cell try to create the layout only once on first initialisation. Now you can pass all values to the respective layout element and let the tableview do its thing.
Try this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cell")
if cell == nil
{
cell = UITableViewCell.init(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
}
for subView in cell.subviews
{
subView.removeFromSuperview()
}
// Your Code here
return cell
}
I am creating a UITableViewCell object and returning it inside cellForRowAtIndexPath function in tableView. I have a UIView in the cell which I want to make circular. Using the code from this link, I had written following code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let containerDP = UIView()
containerDP.backgroundColor = UIColor.redColor()
// some code
let firstLetter = UILabel()
firstLetter.backgroundColor = UIColor.yellowColor()
firstLetter.text = (data[indexPath.row].UserObject.FirstName! as String).uppercaseString[0]
firstLetter.font = UIFont(name: firstLetter.font.fontName, size: 50)
firstLetter.adjustsFontSizeToFitWidth = true
firstLetter.textAlignment = NSTextAlignment.Center
firstLetter.layer.cornerRadius = firstLetter.frame.size.width / 2;
firstLetter.clipsToBounds = true
containerDP.addSubview(firstLetter)
firstLetter.snp_makeConstraints { (make) -> Void in
make.center.equalTo(containerDP)
make.edges.equalTo(containerDP).inset(UIEdgeInsetsMake(10, 10, 10, 10))
}
// some code
return cell
}
But there is still no circular mask:
Try this :
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let containerDP = UIView()
containerDP.backgroundColor = UIColor.redColor()
// some code
let firstLetter = UILabel()
firstLetter.backgroundColor = UIColor.yellowColor()
firstLetter.text = (data[indexPath.row].UserObject.FirstName! as String).uppercaseString[0]
firstLetter.font = UIFont(name: firstLetter.font.fontName, size: 50)
firstLetter.adjustsFontSizeToFitWidth = true
firstLetter.textAlignment = NSTextAlignment.Center
firstLetter.layer.cornerRadius = firstLetter.frame.size.width / 2;
firstLetter.layer.masksToBounds = true
firstLetter.clipsToBounds = true
containerDP.addSubview(firstLetter)
firstLetter.snp_makeConstraints { (make) -> Void in
make.center.equalTo(containerDP)
make.edges.equalTo(containerDP).inset(UIEdgeInsetsMake(10, 10, 10, 10))
}
// some code
return cell
}
Use dequeueReusableCellWithIdentifier for tablewView cell creating and caching
Make subview setups for tableViewCell in awakeFromNib method
With clipsToBounds = true it should work
first you should create a customtableviewCell class for your tableview.
then make IBOutlet connection to your cutomtablviewCell
then #import your customtablviewCell to your tableviewController
then inside the cellForRowAtIndexPath method(I'm well in objectiveC, not in swift) call to your cutomTableviewcell like below
customTableviewCell *cell = [your code here];
after that do your styles like
cell.fisrtLetter.layer.cornerRadius = cell.firstletter.frame.size.width/2;
cell.firstLetter.layer.masksToBounds = true;
please get the idea.
I am trying number cell from 0-X for a uitableview. Currently I am using a label to do it and I just want the labels text to be 0,1,2,3,4,...X.
//label for the cell
let cellFrame: CGRect = CGRectMake(0, 0, 90, 32)
var cellLabel = UILabel(frame: cellFrame)
cellLabel.textAlignment = .Center
cellLabel.font = UIFont.MDFont.regularFourteen
cellLabel.text = ("\(indexPath.row)")
I have tried this approach and it just does 0,1,2,3,4 but when I scroll it prints 0,1,2,3,4 on top of the other numbers as well.
This is the whole code for the function
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
if cell == null {
//label for the cell
let cellFrame: CGRect = CGRectMake(0, 0, 90, 32)
var cellLabel = UILabel(frame: cellFrame)
cellLabel.textAlignment = .Center
cellLabel.font = UIFont.MDFont.regularFourteen
cellLabel.text = ("\(indexPath.row)")
cell.contentView.addSubview(cellLabel)
}
//print("Table data is \(tableData[5])")
return cell
}
I think there is issue related reusable cell. kindly try below code. may it help you.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
if cell == null {
//label for the cell
let cellFrame: CGRect = CGRectMake(0, 0, 90, 32)
var cellLabel = UILabel(frame: cellFrame)
cellLabel.textAlignment = .Center
cellLabel.font = UIFont.MDFont.regularFourteen
cellLabel.text = ("\(indexPath.row)")
cell.contentView.addSubview(cellLabel)
}
else {
for obj : AnyObject in cell.contentView.subviews {
if let label = obj as? UILabel {
label.text = ("\(indexPath.row)")
}
}
}
//print("Table data is \(tableData[5])")
return cell
}
Try this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
if let cellLabel = cell.contentView.viewWithTag(10) as? UILabel {
cellLabel.text = ("\(indexPath.row)")
}
else {
let cellLabel = UILabel(frame: CGRectMake(0, 0, 90, 32))
cellLabel.tag = 10
cellLabel.textAlignment = .Center
cellLabel.font = UIFont.MDFont.regularFourteen
cellLabel.text = ("\(indexPath.row)")
cell.contentView.addSubview(cellLabel)
}
//print("Table data is \(tableData[5])")
return cell
}
When you compare variables you have to use nil.
Also in your case if the cell was nil, you should have created one. Because as you have it you try to add a label to a cell that doesn't exist.
See, when your table is loaded, your cells are nil for the visible region of screen... Here, its 4 cells, so your cell is nil condition will be satisfied for all 4 cells and you will get 4 cells having 0,1,2,3 text. But now when you scroll down, your cell will be reused and it wont go inside the condition so, it will show the old value that you've added in one of the above cells and hence will get wrong values.. You can use a simple approach:
Give tag (say, 1234) to your label when you allocate it when your cell is nil and access it outside the if condition, using that tag get that label and set text to it outside the if condition.. So, it will be called everytime and you will get 0,1,2,3,....X as you wanted...
Replace cellForRowAtIndexPath with this.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell : UITableViewCell = UITableViewCell(frame: CGRectMake(0, 0, tableView.frame.size.width, 44))
let cellLabel = UILabel(frame: CGRectMake(0, 0, 90, 32))
cellLabel.tag = 10
cellLabel.textAlignment = .Center
cellLabel.text = ("\(indexPath.row)")
cell.addSubview(cellLabel)
return cell
}
Hope this helps you. :)
I think the problem is getting your row index using indexPath.row. Just create a local variable with the index thats added to the cell in the function cellForRowAtIndexPath() before the cell is returned. Then just reset the cellIndex=0 before calling reloadData(). I believe that would work
i.e
var cellIndex = 0
func ..cellForRowAtIndexPath ...()
{
//set the index to the cell label.
cell.label = cellIndex++
return cell
}