I'm having a problem with my TableViewCell
I have two type of cell in my storyboard.
when i scroll, the text overlaps in some cells. I Try everything but I do not know how else to do. thank you very much for the help
public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var storeNew = systemsBlog.getStore(listNews[indexPath.row].getIdStore())
var newNotice = listNews[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("TimelineCell", forIndexPath: indexPath) as? TimelineCell
cell!.nameLabel.text = storeNew.getName()
cell!.postLabel?.text = newNotice.getText()
cell!.postLabel?.numberOfLines = 0
cell!.dateLabel.text = newNotice.getDate()
cell!.typeImageView?.tag = indexPath.row;
return cell!
}
class TimelineCell : UITableViewCell {
#IBOutlet var nameLabel : UILabel!
#IBOutlet var postLabel : UILabel?
#IBOutlet var dateLabel : UILabel!
override func awakeFromNib() {
postLabel?.font = UIFont(name: "Roboto-Thin", size: 14)
}
override func layoutSubviews() {
super.layoutSubviews()
}
I can fix the problem. In the storyboard, the label have unchacked "Clears Graphics Context". I checked and for now it solved! Thanks for the help!
I had a similar issue with one of my UITableViews in the past. There are a bunch of things that could cause this, but maybe it is the same thing that happened to me.
I see that you are using a custom tableViewCell. What could be happening is when you set the text of the cell, it adds a label view with that text. Now say you scroll through the tableview and that cell disappears. If you were to reuse that cell and you did not remove the label from the subview, or set the text of that label again to the desired text, you will be reusing the tableviewcell with a previous label on it and adding a new label with new text to it, overlapping the text.
My suggestion would be to make sure you do not keep adding UIlabels as subviews in the TimelineCell class unless no label exists. if a label exists edit the text of that label not of the cell.
As per apple documentation:
The table view’s data source implementation of
tableView:cellForRowAtIndexPath: should always reset all content when
reusing a cell.
It seems that your problem is that you not always setting postLabel, causing it to write on top of the other cells, try this:
//reuse postLabel and set to blank it no value is returned by the function
let cell = tableView.dequeueReusableCellWithIdentifier("TimelineCell", forIndexPath: indexPath) as? TimelineCell
cell!.nameLabel.text = storeNew.getName()
if newNotice.getText() != nil{
cell!.postLabel.text = newNotice.getText()
} else {cell!.postLabel.text = ''}
cell!.postLabel.numberOfLines = 0
cell!.dateLabel.text = newNotice.getDate()
cell!.typeImageView?.tag = indexPath.row;
return cell!
}
//Make postLabel mandatory and set the font details in the xcode
class TimelineCell : UITableViewCell {
#IBOutlet var nameLabel : UILabel!
#IBOutlet var postLabel : UILabel!
#IBOutlet var dateLabel : UILabel!
override func awakeFromNib() {
//set this in xcode
}
override func layoutSubviews() {
super.layoutSubviews()
}
Also be sure that you are not creating any UI element and appending to the cell, as if you are you need to dispose it before you recycle the cell.
You can try setting:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 44.0 // Set this value as a good estimation according to your cells
}
In the View Controller containing your tableView.
Make sure the layout constraints in your TimelineCell define a clear line of height constraints
Another option is responding to:
tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 44.0 // Your height depending on the indexPath
}
Always in the ViewController that contains your tableView and, I assume, is the tableView's UITableViewDelegate
Hope this helps
Set cell to nil that will fix some error.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? ImageCellTableViewCell
cell = nil
if cell == nil {
cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for:indexPath) as? ImageCellTableViewCell
}
cell.yourcoustomtextTextLabel.text = "this is text"
cell.yourcoustomtextImageView.image = image
return cell
}
Related
I am using a tableview in an app in which I have used pagination. The request is sent to the server and it returns items in batches of size 10. everything is working fine till now. Now I have an imageview in my tableview cells (custom). I want that when the image of that imageview toggles when user taps on it. I tried this thing in the following way:
TableviewController:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell : AdventureTableViewCell = tableView.dequeueReusableCell(withIdentifier: "adventureCell" , for: indexPath) as? AdventureTableViewCell else {
fatalError("The dequeued cell is not an instance of AdventureViewCell.")
}
cell.adventureName.text = adventureList[indexPath.row]
cell.amountLabel.text = "\(adventurePriceList[indexPath.row])$"
cell.favouriteButtonHandler = {()-> Void in
if(cell.favouriteButton.image(for: .normal) == #imageLiteral(resourceName: "UnselectedFavIcon"))
{
cell.favouriteButton.setImage(#imageLiteral(resourceName: "FavSelectedBtnTabBar"), for: .normal)
}
else
{
cell.favouriteButton.setImage(#imageLiteral(resourceName: "UnselectedFavIcon"), for: .normal)
}
}
}
CustomCell:
class AdventureTableViewCell: UITableViewCell {
#IBOutlet weak var adventureName: UILabel!
#IBOutlet weak var adventureImage: UIImageView!
#IBOutlet weak var amountLabel: UILabel!
#IBOutlet weak var favouriteButton: UIButton!
#IBOutlet weak var shareButton: UIButton!
var favouriteButtonHandler:(()-> Void)!
var shareButtonHandler:(()-> Void)!
override func awakeFromNib() {
super.awakeFromNib()
adventureName.lineBreakMode = .byWordWrapping
adventureName.numberOfLines = 0
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
override func prepareForReuse() {
adventureImage.af_cancelImageRequest()
adventureImage.layer.removeAllAnimations()
adventureImage.image = nil
}
#IBAction func favouriteButtonTapped(_ sender: Any) {
self.favouriteButtonHandler()
}
Now the problem which I am facing is that if user taps the first the imageview on any cell it changes its image, but along with that every 4th cell changes it image.
For example, if I have tapped imageview of first cell its image is changed but image of cell 5, 9, 13... also get changed.
What is wrong with my code? Did I miss anything? It is some problem with indexPath.row due to pagination, but i don't know what is it exactly and how to solve it. I found a similar question but its accepted solution didn't work for me, so any help would be appreciated.
if you need to toggle image and after scrolling also that should be in last toggle state means you need to use an array to store index position and toggle state by comparing index position and scroll state inside cellfoeRowAtIndex you can get the last toggle state that is one of the possible way to retain the last toggle index even when you scroll tableview otherwise you will lost your last toggle position
if self.toggleStatusArray[indexPath.row]["toggle"] as! String == "on"{
cell.favouriteButton.setImage(#imageLiteral(resourceName: "FavSelectedBtnTabBar"), for: .normal)
} else {
cell.favouriteButton.setImage(#imageLiteral(resourceName: "UnselectedFavIcon"), for: .normal)
}
cell.favouriteButtonHandler = {()-> Void in
if self.toggleStatusArray[indexPath.row]["toggle"] as! String == "on"{
//Assign Off status to particular index position in toggleStatusArray
} else {
//Assign on status to particular index position in toggleStatusArray
}
}
Hope this will help you
Your code looks OK, I see just one big error.
When u are setting dynamic data (names, images, stuff that changes all the time) use func tableView(UITableView, willDisplay: UITableViewCell, forRowAt: IndexPath) not cellForRowAt indexPath.
cellForRowAt indexPath should be used for static resources, and cell registration.
If u are on iOS 10 + take a look at prefetchDataSource gonna speed things up a loot, I love it.
https://developer.apple.com/documentation/uikit/uitableview/1771763-prefetchdatasource
Small example:
here u register the cell, and set up all the stuff that is common for all the cells in the table view
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "adventureCell" , for: indexPath)
cell.backgroundColor = .red
return cell
}
here adjust all the stuff that is object specific
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.nameLabel.text = model[indexPath.row].name
// also all the specific UI stuff goes here
if model[indexPath.row].age > 3 {
cell.nameLabel.textColor = .green
} else {
cell.nameLabel.textColor = .blue
}
}
You need this because cells get reused, and they have their own lifecycle, so you want to set specific data as late as possible, but you want to set the generic data as less as possible ( most of the stuff you can do once in cell init ).
Cell init is also a great place for generic data, but u can not put everything there
Also, great thing about cell willDisplay is the that u know actual size of the frame at that point
I have spent days on resolving this issue and after trying much I am asking a question here. I am using a custom UITableViewCell and that cell contains UITextFields. On adding new cells to the table view, the table view behaves abnormal like it duplicates the cell and when I try to edit the textfield of new cell, the textfield of previous cel gets edited too.
The behavior of duplication is as follows: 1st cell is duplicated for 3rd cell. I don't know this is due to reusability of cells but could anyone tell me about the efficient solution?
I am attaching the screenshot of UITableViewCell.
The code for cellForRow is as follows:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : Product_PriceTableViewCell = tableView.dequeueReusableCell(withIdentifier: "product_priceCell") as! Product_PriceTableViewCell
cell.dropDownViewProducts.index = indexPath.row
cell.txtDescription.index = indexPath.row
cell.tfPrice.index = indexPath.row
cell.dropDownQty.index = indexPath.row
cell.tfTotalPrice_Euro.index = indexPath.row
cell.tfTotalPrice_IDR.index = indexPath.row
cell.dropDownViewTotalDiscount.index = indexPath.row
cell.dropDownViewDeposit.index = indexPath.row
cell.tfTotalDeposit_Euro.index = indexPath.row
cell.tfRemaingAfterDeposit_IDR.index = indexPath.row
return cell
}
The issue is the cell is being reused by the UITableView, which is what you want to happen for good scrolling performance.
You should update the data source that supports each row in the table to hold the text the user inputs in the field.
Then have the text field's text property assigned from your data source in cellForRowAt.
In other words, the UITableViewCell is the same instance each time you see it on the screen, and so is the UITextField and therefore so is it's text property. Which means it needs to be assigned it's correct text value each time cellForRowAt is called.
I'm unsure of your code so I have provided an example of how I would do something like what you want:
class MyCell: UITableViewCell {
#IBOutlet weak var inputField: UITextField!
}
class ViewController: UIViewController {
#IBOutlet weak var table: UITableView!
var items = [String]()
fileprivate func setupItems() {
items = ["Duck",
"Cow",
"Deer",
"Potato"
]
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setupItems()
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// the # of rows will equal the # of items
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// we use the cell's indexPath.row to
// to get the item in the array's text
// and use it as the cell's input field text
guard let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as? MyCell else {
return UITableViewCell()
}
// now even if the cell is the same instance
// it's field's text is assigned each time
cell.inputField.text = items[indexPath.row]
// Use the tag on UITextField
// to track the indexPath.row that
// it's current being presented for
cell.inputField.tag = indexPath.row
// become the field's delegate
cell.inputField.delegate = self
return cell
}
}
extension ViewController: UITextFieldDelegate {
// or whatever method(s) matches the app's
// input style for this view
func textFieldDidEndEditing(_ textField: UITextField) {
guard let text = textField.text else {
return // nothing to update
}
// use the field's tag
// to update the correct element
items[textField.tag] = text
}
}
I suggest to do the following
class Product_PriceTableViewCell: UITableViewCell {
var indexRow: Int = -1
func configureCell(index: Int) {
cell.dropDownViewProducts.clean()
...
cell.tfRemaingAfterDeposit_IDR.clean()
}
}
where clean is the function to empty de view (depend on the type)
Then in the delegate:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : Product_PriceTableViewCell = tableView.dequeueReusableCell(withIdentifier: "product_priceCell") as! Product_PriceTableViewCell
cell.configureCell(row: indexPath.row)
return cell
}
As #thefredelement pointed out when the cell is not in the view frame, it is not created. Only when the view is going to appear, it tries to reuse an instance of the cell and as the first is available, the table view uses it but does not reinitialize it. So you have to make sure to clean the data
The rest of the answer is for better coding.
This is my requirement:
I want my tableView's cell to be like the last cell, its border is margin the tableView some pix, not contradict the tableview's edge.(I want this is because when I click down the cell, there is gray effect on the cell)
How to do with that?
u can't resize the cell's, instead u can set the views's layer properties to achieve the similar effect, for example, (u are not mentioning which language u are using, i assume u are using swift).
i will assume your custom cell contains a UIView and some other view components, like below,
and also add outlet for imageHolderView in the above image,
out let name will be holderView as shown in below image,
in the custom cell class, define two methods for selection management, and your custom cell class would look like below,
class CustomCell: UITableViewCell {
#IBOutlet weak var circleNameTextField: UILabel!
#IBOutlet weak var holderView: UIView!
var cellindexPath:IndexPath?
var selectedIndexPath:IndexPath?
func selectTheCell() {
if self.selectedIndexPath?.row == self.cellindexPath?.row {
self.holderView.layer.cornerRadius = 6.0
self.holderView.layer.masksToBounds = true
self.holderView.layer.borderWidth = 4.0
self.holderView.layer.borderColor = UIColor.red.cgColor
self.backgroundColor = UIColor.gray
} else {
self.resetCellWith(animate: false)
}
}
func resetCellWith(animate:Bool) {
self.holderView.layer.cornerRadius = 0.0
self.holderView.layer.masksToBounds = false
self.holderView.layer.borderWidth = 0.0
self.holderView.layer.borderColor = UIColor.clear.cgColor
self.backgroundColor = UIColor.orange
}
}
now all u have to do is call the above methods, from controller and update the cell behaviour, for example,
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selIndexPath = indexPath
self.aTableView.reloadSections(IndexSet(integer: 0), with: .none)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : CustomCell? = tableView.dequeueReusableCell(withIdentifier: "CUSTOM_CELL", for: indexPath) as? CustomCell//tableView.dequeueReusableCell(withIdentifier: "CUSTOM_CELL") as? CustomCell
cell?.cellindexPath = indexPath
if let selectedIndexPath = self.selIndexPath {
cell?.selectedIndexPath = selectedIndexPath
cell?.selectTheCell()
} else {
cell?.resetCellWith(animate:false)
}
cell?.selectionStyle = .none
return cell!
}
with the above arrangement, u can get the table cell and selection like below,
NOTE: well, above is one way achieve this effect. and method names i simply used the sample project that i created for different purpose. :)
I'm building an iOS application with Swift 2 that uses custom table view cells, with additional labels, image views, etc. (let's call the class CustomTableViewCell). I've made the class-storyboard connections to every subview and assigned an identifier to the cell. I've mocked the data and tried to run the application to check that the cell is properly mapped, and it looks ok.
The problem is that I cannot treat a dequeued cell as a CustomTableViewCell to test the value of its properties. When I downcast the cell returned from tableView(tableView, cellForRowAtIndexPath: indexPath) all custom property values turns into nil and my tests fail.
Here's my code:
MyViewControllerTests.swift
func testShouldConfigureTableViewCellToDisplayNotification() {
// Given
sut.tableView = TableViewSpy()
let items = [ <some items to display> ]
sut.displayedItems = items
// When
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
let cell = viewController.tableView(viewController.tableView, cellForRowAtIndexPath: indexPath) as! CustomTableViewCell
// Then
XCTAssertEqual(cell.detailLabel?.text, "foo", "A properly configured table view cell should display the notification detail")
XCTAssertEqual(cell.titleLabel?.text, "Bar", "A properly configured table view cell should display the notification title")
XCTAssertEqual(cell.dateLabel?.text, "15/04/2016", "A properly configured table view cell should display the notification date")
}
MyViewController.swift
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "CustomTableViewCell"
let displayedItem = displayedItems[indexPath.row]
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? CustomTableViewCell
if cell == nil {
cell = CustomTableViewCell(style: .Value1, reuseIdentifier: cellIdentifier)
}
cell!.dateLabel?.text = displayedItem.date
cell!.detailLabel?.text = displayedItem.detail
cell!.titleLabel?.text = displayedItem.title
return cell!
}
CustomTableViewCell.swift
class CustomTableViewCell: UITableViewCell {
// MARK: Properties
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var detailLabel: UILabel!
#IBOutlet weak var titleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Try replacing
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "CustomTableViewCell"
let displayedItem = displayedItems[indexPath.row]
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? CustomTableViewCell
if cell == nil {
cell = CustomTableViewCell(style: .Value1, reuseIdentifier: cellIdentifier)
}
cell!.dateLabel?.text = displayedItem.date
cell!.detailLabel?.text = displayedItem.detail
cell!.titleLabel?.text = displayedItem.title
return cell!
}
with
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "CustomTableViewCell"
let displayedItem = displayedItems[indexPath.row]
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! CustomTableViewCell
cell.dateLabel.text = displayedItem.date
cell.detailLabel.text = displayedItem.detail
cell.titleLabel.text = displayedItem.title
return cell
}
If you have connected your cell correctly ins storyboard then this should work. Otherwise check that you have correctly assigned all IBOutlets for your cell.
If something doesn't work please check the following:
1) Select your cell in the storyboard.
2) In the right column open Identity Inspector (3rd tab at the top). Make sure that the class of your cell is set to CustomTableViewCell.
3) In the Attributes Inspector (4th tab) make sure that cell identifier is correctly spelled.
4) In Connections inspector (last tab) assign all of the IBOutlets of your cell which you have defined.
From your code sample looks like you didn't registered your cell for reusing.
tableView.registerClass(CustomTableViewCell.self, forCellReuseIdentifier: "CustomTableViewCell")
So, I created a custom table view cell with a label on the left and a UIImageView on the right.
The label has a tag of 100 and the UIImageView a tag of 110.
My code is the following:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("ThemeCell") as UITableViewCell
let theme = themes[indexPath.row]
var themeName = cell.viewWithTag(100) as? UILabel
themeName?.text = theme.themeName
let themeImage = cell.viewWithTag(110) as? UIImageView
themeImage?.image = UIImage(named: "test.png")
//1
//cell.imageView?.image = UIImage(named: "test.png")
println("The loaded image: \(themeImage?.image)")
return cell;
}
As is is, the themeName is displayed but the themeImage does not appear, although from println it seems that the image is loaded. If I uncomment the code in 1 the image appears in the custom cell but of course does not appear in the proper place as it is not added to the UIImageView that I created in IB.
Any ideas what I might be doing wrong? The Tags are all correct.
Thanks
Firstly, you need to pin your views with auto layout mechanism. Open interface builder, left click on label inside your custom cell, then for example do the following:
Editor->Pin->Width
Editor->Pin->Height
Editor->Pin->Leading Space to Superview
Editor->Pin->Top Space to Superview
the same for image inside your custom cell
Editor->Pin->Width
Editor->Pin->Height
Editor->Pin->Trailing Space to Superview
Editor->Pin->Top Space to Superview
Then create custom class for your cell. for example
MyCustomCell.swift
import Foundation
import UIKit
class MyCustomCell: UITableViewCell {
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var myImageView: UIImageView!
}
Then set custom class for your cell and create connections from elements.
And now in tableViewController you can set the values to your elements without tags:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: MyCustomCell = tableView.dequeueReusableCellWithIdentifier("ThemeCell") as MyCustomCell
let theme = themes[indexPath.row]
cell.myLabel.text = theme.themeName
cell.myImageView.image = UIImage(named: "test.png")
println("The loaded image: \(cell.myImageView.image)")
return cell;
}
Ok, so the fault was not in the UITable at all but in the fact that the AutoLayout was not set correctly and the image appeared outside the tableview...