The cell is initially created from a xib file. Ive registered a reuse identifier in viewDidLoad().
Whenever I scroll down, one specific label is redrawn partially. Cant seem to figure out whats going on with the cell, but I know that the cell is not nil. Looking for insight on why this label isn't being drawn correctly.
before dragging
after dragging
More Info:
I can't tell, but it seems as if new cells are being drawn directly on top of the old cells. I figured this out due to the custom line divider: (let lineView = self.createLineDivider(cell: cell)) I added to context view of the cell. To stop the line from drawing over itself, I change the tag of every new cell.
override func viewDidLoad()
{
super.viewDidLoad()
let nib = UINib(nibName: "customContactDetailTableViewCell", bundle: nil)
self.contactDetailsTableView.register(nib, forCellReuseIdentifier: "customDetailCell")
}
func configureCell(cell: customContactDetailTableViewCell, indexPath: IndexPath) -> customContactDetailTableViewCell
{
let sectionName = self.props[indexPath.section].keys.first!
let contactPropLabel = self.props[indexPath.section][sectionName]![indexPath.row].keys.first!
let contactProp = self.props[indexPath.section][sectionName]![indexPath.row][contactPropLabel]
cell.contactDetailInfo?.layer.borderWidth = 1
cell.contactDetailInfo?.layer.borderColor = UIColor.white.cgColor
cell.contactDetailInfoTitle?.layer.borderWidth = 1
cell.contactDetailInfoTitle?.layer.borderColor = UIColor.white.cgColor
cell.contactDetailInfo?.numberOfLines = 0
cell.contactDetailInfoTitle?.text = contactPropLabel
cell.contactDetailInfo?.text = contactProp
cell.contactDetailInfo?.lineBreakMode = .byWordWrapping
self.rowHeight = cell.bounds.height
return cell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{ var cell = tableView.dequeueReusableCell(withIdentifier: "customDetailCell") as? customContactDetailTableViewCell
if cell?.tag != 212
{
cell?.tag = 212
cell?.backgroundColor = UIColor.clear
let lineView = self.createLineDivider(cell: cell)
cell?.contentView.addSubview(lineView)
}
return self.configureCell(cell: cell!, indexPath: indexPath)
}
Update
Figured it out. Looks like the sporadic behavior came from the table view not updating the row height quick enough. Used this instead:
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Related
in my View:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TransactionTableCell", for: indexPath) as! TransactionTableCell
let newItem = getTransactionsInSection(section: sectionHeader[indexPath.section])[indexPath.row]
cell.configure(item: newItem)
}
in my TransactionTableCell
func configure(item: TransactionModel) {
guard let withdrawalBonuses = item.withdrawalBonuses,
withdrawalBonuses < 0,
let accruedBonuses = item.accruedBonuses,
accruedBonuses > 0 else {
configureWithOneOperation(item)//shows one line of operation
return
}
//show 2 lines of operations
firstOperationAmountLabel.text = "+\(Int(accruedBonuses))"
secondOperationAmountLabel.text = "\(Int(withdrawalBonuses))"
}
When I scroll the cell , second operation line is appears in wrong cells where its shouldn't be, even If I reload my table , that also has this problem.
You should use prepareForReuse() method
Simply just clear data of your labels:
override func prepareForReuse() {
super.prepareForReuse()
firstOperationAmountLabel.text = nil
secondOperationAmountLabel.text = nil
}
There are few things to check here.
Make sure you reset all fields before configure a new cell.
If you have created a cell using xib or storyboard, make sure you haven't filled labels with static text.
Is your guard statements passing for every item?
Else block for guard configures cell with a single operation, Is it handling all ui elements in cell?
I have a tableview and in each cell there is a checkbox. I also have a "select all" button.
My problem is that when I click select all I want to update all the checkboxes to checked state. So from a list of 100 cells, all get checked but every 13th cell does not. To make it clearer, on my simulators screen are 12 cells visible that all get checked. When I start scrolling, the first cell that comes up is unchecked, and is then followed by 12 checked ones :S
When I scroll a little and click "select all" again, the skipped ones become also checked..
Anyone have a clue what am I missing?
This is the cell code:
class ListTableViewCell: UITableViewCell {
#IBOutlet weak var checkbox: UIButton!
var buttonState = false{
didSet{
if buttonState{
checkbox.setImage(#imageLiteral(resourceName: "checked"), for: .normal)
}else{
checkbox.setImage(#imageLiteral(resourceName: "unchecked"), for: .normal)
}
}
}
#IBAction func checkboxAction(_ sender: UIButton) {
if buttonState {
buttonState = false
}else{
buttonState = true
}
}
func simulateCheck(){
buttonState = true
}
And here are some snipets from my controller:
private var articleValues: [ArticleValue] = []{
didSet{
tableView.reloadData()
}
}
func selectAll(){
for i in 0..<articleValues.count{
let cell = tableView.cellForRow(at: IndexPath(item: i, section: 0)) as? ListTableViewCell
cell?.simulateCheck()
tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "articleValueItem", for: indexPath)
// Cell Configuration
let articleValue = articleValues[indexPath.row]
if let articleValueCell = cell as? ListTableViewCell{
articleValueCell.articleValue = articleValue
}
return cell
}
Your UITableView is backed by a data source. This means that you shouldn't change cells directly like you do here:
cell?.simulateCheck()
tableView.reloadData()
Instead you should keep a list of all the checked positions, maybe another array that has bools for each corresponding articleValue (this is not the best design).
var checkedValues = Bool
In your
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method you would then set the state of the cell:
articleValueCell.buttonState = checkedValues[indexPath.row]
In your selectAll method fill this array with true values and then call tableView.reloadData()
private var checkedValues = [Bool]()
private var articleValues: [ArticleValue] = []{
didSet{
checkedValues = Array(repeating: false, count: articleValues.count)
tableView.reloadData()
}
}
func selectAll(){
checkedValues = Array(repeating: true, count: articleValues.count)
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "articleValueItem", for: indexPath)
// Cell Configuration
let articleValue = articleValues[indexPath.row]
if let articleValueCell = cell as? ListTableViewCell{
articleValueCell.articleValue = articleValue
articleValueCell.buttonState = checkedValues[indexPath.row]
}
return cell
}
Another mistake is that you should never iterate on all the cells in the table because they are reused, no point in going through your data source and getting a cell for each. It only makes sense to iterate through tableView.visibleCells. But like in your case, most of the time you don't need that either, you should just update your data source accordingly and reload the table or just the modified cell.
It's not recommended that you refer to cells directly within a table view. The reason is that UITableViews have an efficient method of only loading the cells as they are needed (and deallocating them when they are no longer needed, e.g. the cell scrolls off screen). Because of this the cell you are try to refer to may not be loaded.
Instead you should interact with it via the cellForRowAt method. If you want to "select all" cells, you should create a property that stores the value of checked or not checked via a Bool and then set all of the ArticleValue elements to true for that property and reload the data inside selectAll().
It could work something like this:
func selectAll() {
articleValues.forEach {
$0.checked = true
}
tableView.reloadData()
}
// ...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "articleValueItem", for: indexPath)
// Cell Configuration
let articleValue = articleValues[indexPath.row]
if let articleValueCell = cell as? ListTableViewCell{
articleValueCell.articleValue = articleValue
if articleValue.checked {
articleValueCell.simulateCheck()
}
}
return cell
}
I'm making a collection view that has 6 * 7 cells and even though I wrote code such that I could change each cells background colour after declaring the UICollectionView; the cell background colour stays white. Other cells are not returning back to white background colour. Can someone tell me the reason why only this cell doesn't keep it's background colour?
override func viewDidLoad() {
super.viewDidLoad()
let longPressRecognizer = UILongPressGestureRecognizer(target:self, action: #selector(ViewController.longPressAction(_:)))
longPressRecognizer.allowableMovement = 5
longPressRecognizer.minimumPressDuration = 0.5
self.collectionView.addGestureRecognizer(longPressRecognizer)
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let col = indexPath.section
let row = indexPath.row
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as! TimeTableCollectionViewCell
cell.backgroundColor = UIColor.whiteColor()
if !ViewController.colorDictionary.isEmpty {
for (indexPath, color) in ViewController.colorDictionary {
self.collectionView.cellForItemAtIndexPath(indexPath)?.backgroundColor = color
}
}
Please debug the dictionary if condition. It might be going in if statement everytime cell gets return from the collection view.
I'm creating a simple messenger app and using uitableview to display messages. I have created two XIB files for two types of cells i.e. incoming and outgoing. Also I have added constraints to each cell to make textViews to be not at full width of the screen (picture below)
The problem is, when i scroll table view up and down i see some jumps of content (i.e. bad performance). I made all views opaque, but it didn't help. As data storage i'm using Realm.io, to fetch messages from it. I get group_key, and list messages in this group.
Here's my code:
extension MessageChatViewController : UITableViewDataSource {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count = 0
if !groupKey.isEmpty {
let messagesForGroupInRealm = realm.objects(Messages).filter("group_key = '\(groupKey)'")
if messagesForGroupInRealm.count != 0 {
count += messagesForGroupInRealm.count
}
}
return count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let messagesForGroupInRealm = messagesForCurrentChat.sorted("created_at", ascending: true)
if messagesForGroupInRealm.count != 0 {
if indexPath.row < messagesForGroupInRealm.count {
let message = messagesForGroupInRealm[indexPath.row]
if message.owner_key == gCredentials["account_key"] {
let cell = tableView.dequeueReusableCellWithIdentifier("OutgoingChatMessageCell", forIndexPath: indexPath) as! OutgoingChatMessageCell
cell.outgoingTextView.text = message.content
cell.outgoingTextView.font = UIFont(name: "NotoSans", size: 16)
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("IncomingChatMessageCell", forIndexPath: indexPath) as! IncomingChatMessageCell
cell.incomingTextView.text = message.content
cell.incomingTextView.font = UIFont(name: "NotoSans", size: 16)
return cell
}
}
}
return UITableViewCell()
}
}
I also use dynamic height of cells:
override func viewDidLoad() {
super.viewDidLoad()
....
tableView.registerNib(UINib(nibName: "OutgoingChatMessageCell", bundle: nil), forCellReuseIdentifier: "OutgoingChatMessageCell")
tableView.registerNib(UINib(nibName: "IncomingChatMessageCell", bundle: nil), forCellReuseIdentifier: "IncomingChatMessageCell")
....
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44.0
....
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
So is there any way to get best performance for tableView? Maybe i need somehow cache something ?:) Or maybe i made some mistake... Thanks for any advices (for Obj-C also)!
I seems like you are re-querying and sorting the entire dataset every time you draw a cell (or get a count). That would be a lot of unneeded overhead and could easily slow things down.
Why don't you make messagesForGroupInRealm a class member? Then you only have to do the query once (on init), and can then just keep accessing it directly from there.
I have been struggling with this issue. I can scroll freely between the tag cells because it actually remembers them. But if I get the description cell out of my view it immediately removes it from memory and doesn't get it back. Instead I just get "fatal error: unexpectedly found nil while unwrapping an Optional value" when I scroll back to the description. So I have the following pieces of code:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44.0
tableView.reloadData()
}
I don't know if the viewWillAppear is of any importance in this case but if it is then tell me. Anyway, this is for filling in the cells in my table view:
func GetDescription(cell:descCell, indexPath: NSIndexPath) {
cell.descText.text = descriptTextTwo.htmlToString
}
func GetTagCell(cell:basicTag, indexPath: NSIndexPath) {
let item = tagResults[indexPath.row]!
cell.titleLabel.text = item["tagname"]?.htmlToString
}
func GetValueCell(cell: basicTag, indexPath: NSIndexPath) {
let item = tagResults[indexPath.row]!
cell.valueLabel.text = item["value"]?.htmlToString
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if filledDescription == false {
return getDescriptionAtIndexPath(indexPath)
} else {
return getTagAtIndexPath(indexPath)
}
}
func getDescriptionAtIndexPath(indexPath:NSIndexPath) -> descCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier(descriptionCell) as descCell
GetDescription(cell, indexPath: indexPath)
filledDescription = true
return cell
}
func getTagAtIndexPath(indexPath: NSIndexPath) -> basicTag {
let cell = self.tableView.dequeueReusableCellWithIdentifier(tagCell) as basicTag
GetTagCell(cell, indexPath: indexPath)
GetValueCell(cell, indexPath: indexPath)
return cell
}
So how can I make Swift remember what is in the first cell? Because I am guessing that that is what happens, that it removes what was in the first cell as soon as you get it out of the view. I am guessing I have to do something with "indexPath" but I am not exactly sure how to implement it in this case and if I am far off, please tell me what I am doing wrong. Thanks!
Change the following :
if filledDescription == false {
return getDescriptionAtIndexPath(indexPath)
} else {
return getTagAtIndexPath(indexPath)
}
With:
if indexPath.row == 0 {
return getDescriptionAtIndexPath(indexPath)
} else {
return getTagAtIndexPath(indexPath)
}
This will make sure that the first cell in the table will always treated as a "Description" cell. Since the filledDescription never becomes false after your set it to true, when you get back to the first cell it is treated as a "Tag" cell (due to the if line) where in fact the reusable cell contains "Description" cell data