UIProgressView shows on incorrect row when uitableview is scroll - ios

Within UITableView, I have a custom cell with download button. Once Download button is tapped, I update the tag with indexPath.row and in download function progress view is displayed in that cell. The problem is when user scrolls and cell becomes invisible, that specific progress view starts showing in a different cell.
This is my cellForRowAtIndexPath function:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:CellWithDownload! = tableView.dequeueReusableCellWithIdentifier("CellWithDownload") as CellWithDownload
var title = rowDataAll[indexPath.row].valueForKey("BayanTitle") as String
cell.TitleLabel.text = title
cell.AuthorLabel.text = rowDataAll[indexPath.row].valueForKey("Artist") as? String
var countOfLikesString =
cell.downloadButton.tag = indexPath.row
cell.downloadButton.addTarget(self, action: Selector("downloadAudio:"), forControlEvents: UIControlEvents.TouchUpInside)
// If cell becomes visible again, then star
if let isDownloading = rowDataAll[indexPath.row].valueForKey("isDownloading") {
if (isDownloading as NSString == "true") {
cell.showDownloading()
cell.progressView.progress = getDownloadObjectWithURL(url).progress
} else if (isDownloading as NSString == "completed") {
cell.hideDownloading()
}
}
return cell
}
Please advice.

Most likely, the issue is that you are not re-initializing the cells once reused. When you dequeue a new cell it could be reusing one of the cells which just scrolled off screen. If that had the progress bar showing and you never initialize dequeued cells to have no progress bar, then as soon as you reuse one which had a progress bar, it will appear.
So I think you just need to make sure you initialize the cells after you dequeue one.
In your cellForRowAtIndexPath I think you need an else statement added to deal with a cell which is not downloading and set no progress bar:
if let isDownloading = rowDataAll[indexPath.row].valueForKey("isDownloading"){
<Your existing code seems to setup the progress bar here>
}
else{
<Set it to have no pogress bar in here.>
cell.hideDownloading()
}

Related

Showing and hiding a view only on a specific cell of a table view

I have a table view with custom cells. They are quite tall, so only one cell is completely visible on the screen and maybe, depending on the position of that cell, the top 25% of the second one. These cells represent dummy items, which have names. Inside of each cell there is a button. When tapped for the first time, it shows a small UIView inside the cell and adds the item to an array, and being tapped for the second time, hides it and removes the item. The part of adding and removing items works fine, however, there is a problem related to showing and hiding views because of the fact that cells are reused in a UITableView
When I add the view, for example, on the first cell, on the third or fourth cell (after the cell is reused) I can still see that view.
To prevent this I've tried to loop the array of items and check their names against each cell's name label's text. I know that this method is not very efficient (what if there are thousands of them?), but I've tried it anyway.
Here is the simple code for it (checkedItems is the array of items, for which the view should be visible):
if let cell = cell as? ItemTableViewCell {
if cell.itemNameLabel.text != nil {
for item in checkedItems {
if cell.itemNameLabel.text == item.name {
cell.checkedView.isHidden = false
} else {
cell.checkedView.isHidden = true
}
}
}
This code works fine at a first glance, but after digging a bit deeper some issues show up. When I tap on the first cell to show the view, and then I tap on the second one to show the view on it, too, it works fine. However, when I tap, for example, on the first one and the third one, the view on the first cell disappears, but the item is still in the array. I suspect, that the reason is still the fact of cells being reused because, again, cells are quite big in their height so the first cell is not visible when the third one is. I've tried to use the code above inside tableView(_:,cellForRow:) and tableView(_:,willDisplay:,forRowAt:) methods but the result is the same.
So, here is the problem: I need to find an EFFICIENT way to check cells and show the view ONLY inside of those which items are in the checkedItems array.
EDITED
Here is how the cell looks with and without the view (the purple circle is the button, and the view is the orange one)
And here is the code for the button:
protocol ItemTableViewCellDelegate: class {
func cellCheckButtonDidTapped(cell: ExampleTableViewCell)
}
Inside the cell:
#IBAction func checkButtonTapped(_ sender: UIButton) {
delegate?.cellCheckButtonDidTapped(cell: self)
}
Inside the view controller (NOTE: the code here just shows and hides the view. The purpose of the code is to show how the button interacts with the table view):
extension ItemCellsTableViewController: ItemTableViewCellDelegate {
func cellCheckButtonDidTapped(cell: ItemTableViewCell) {
UIView.transition(with: cell.checkedView, duration: 0.1, options: .transitionCrossDissolve, animations: {
cell.checkedView.isHidden = !cell.checkedView.isHidden
}, completion: nil)
}
EDITED 2
Here is the full code of tableView(_ cellForRowAt:) method (I've deleted the looping part from the question to make it clear what was the method initially doing). The item property on the cell just sets the name of the item (itemNameLabel's text).
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier:
ItemTableViewCell.identifier, for: indexPath) as? ItemTableViewCell{
cell.item = items[indexPath.row]
cell.delegate = self
cell.selectionStyle = .none
return cell
}
return UITableViewCell()
}
I've tried the solution, suggested here, but this doesn't work for me.
If you have faced with such a problem and know how to solve it, I would appreciate your help and suggestions very much.
Try this.
Define Globally : var arrIndexPaths = NSMutableArray()
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 30
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TableViewCell = self.tblVW.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
cell.textLabel?.text = String.init(format: "Row %d", indexPath.row)
cell.btn.tag = indexPath.row
cell.btn.addTarget(self, action: #selector(btnTapped), for: .touchUpInside)
if arrIndexPaths.contains(indexPath) {
cell.backgroundColor = UIColor.red.withAlphaComponent(0.2)
}
else {
cell.backgroundColor = UIColor.white
}
return cell;
}
#IBAction func btnTapped(_ sender: UIButton) {
let selectedIndexPath = NSIndexPath.init(row: sender.tag, section: 0)
// IF YOU WANT TO SHOW SINGLE SELECTED VIEW AT A TIME THAN TRY THIS
arrIndexPaths.removeAllObjects()
arrIndexPaths.add(selectedIndexPath)
self.tblVW.reloadData()
}
I would keep the state of your individual cells as part of the modeldata that lies behind every cell.
I assume that you have an array of model objects that you use when populating you tableview in tableView(_:,cellForRow:). That model is populated from some backend service that gives you some JSON, which you then map to model objects once the view is loaded the first time.
If you add a property to your model objects indicating whether the cell has been pressed or not, you can use that when you populate your cell.
You should probably create a "wrapper object" containing your original JSON data and then a variable containing the state, lets call it isHidden. You can either use a Bool value or you can use an enum if you're up for it. Here is an example using just a Bool
struct MyWrappedModel {
var yourJSONDataHere: YourModelType
var isHidden = true
init(yourJSONModel: YourModelType) {
self.yourJSONDataHere = yourJSONModel
}
}
In any case, when your cell is tapped (in didSelectRow) you would:
find the right MyWrappedModel object in your array of wrapped modeldata objects based on the indexpath
toggle the isHidden value on that
reload your affected row in the table view with reloadRows(at:with:)
In tableView(_:,cellForRow:) you can now check if isHidden and do some rendering based on that:
...//fetch the modelObject for the current IndexPath
cell.checkedView.isHidden = modelObject.isHidden
Futhermore, know that the method prepareForReuse exists on a UITableViewCell. This method is called when ever a cell is just about to be recycled. That means that you can use that as a last resort to "initialize" your table view cells before they are rendered. So in your case you could hide the checkedView as a default.
If you do this, you no longer have to use an array to keep track of which cells have been tapped. The modeldata it self knows what state it holds and is completely independent of cell positions and recycling.
Hope this helps.

How to Draw The View Controller with the Dyanamic Value

I have to make a view as image is below. I am confused and not able to darw Screen as Screen will have more Image on Bottom of Image as User will scroll the View and data on the Each time will come up Dynamically and Will on every time View Come up. Thanks any Help Appricated
after a long Research for this kind of view Finally i got a Solution Which is
take a Table View
take multiple Prototype Cell as much you Want
Set the Identifier Individually of each cell
create a Subcalss of UITableViewCell
Method like callForRowatindexPath you have to call like That with Diffrent Cell
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
var cell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as? specificTopicTableViewCell`cell.textlabel.text = "Hello"
return cell!
}
else {
let cell2 = tableView.dequeueReusableCellWithIdentifier(kSecCellIdentifier) as? scondTopicTableViewCell
cell2.textlabel.text = "Hello"
return cell2!
}
}

Why are some cells being modified even if they don't fulfill a condition?

I have the following code in my table view controller:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("itemCell") as! ItemTableViewCell
cell.itemTitle.text = sortedItems[sortedArray[indexPath.section]]![indexPath.row].itemTitle
cell.itemType.backgroundColor = sortedItems[sortedArray[indexPath.section]]![indexPath.row].itemColor()
// Darkening cells
if /*certain condition is met*/ {
cell.backgroundColor = .redColor() //this colors other cells while scrolling which shouldn't happen
cell.itemTitle.text = "Hello" //this is applied correctly, but why?
}
return cell
}
As you can see in the 'comments', the code which changes the title is applied properly, while coloring the cell doesn't. Why is this? Does it have anything to do with cells being dequeued? How can I avoid this behavior in order to be able to color certain cells?
Table cells are reused when they move off screen. Therefore you must assume they have "left over" data from another cell. Consequently, you need to reset them to a known state. The easiest way to do this in your case, is to handle the else situation.
if /*certain condition is met*/ {
cell.backgroundColor = .redColor() //this colors other cells while scrolling which shouldn't happen
cell.itemTitle.text = "Hello" //this is applied correctly, but why?
} else {
cell.backgroundColor = .whiteColor() // whatever the default color is
cell.itemTitle.text = ""
}

Extremely weird behavior with table cells when going off screen

Here is the really weird behavior. When the table view is first loaded, it looks like this:
Now, when I scroll down and then scroll back up, the buttons appear on cells that didn't have buttons on them before! Like so:
I know this has to do with "This is the intended behaviour of a UITableView. The whole point of a UITableView is to queue up cells that are needed and release cells that aren't needed to manage memory" as described in this post: UITableView in Swift: Offscreen cells are not pre-loaded.
Here is my code:
var messages = [String]()
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CathyTaskLogTableViewCell
if messages[indexPath.row] != "" {
cell.messageButton.hidden = false
}
return cell
}
Anybody have a solution to this problem?
The reason for getting this result is because of UITableViewCell is being reuse.
if messages[indexPath.row] != "" {
cell.messageButton.hidden = false
}
else
{
cell.messageButton.hidden = true
}
There are two possible solutions to your problem:
Always set the hidden property:
cell.messageButton.hidden = messages[indexPath.row] != ""
Reset the state of your cell when it is reused, this provides deterministic behaviour in the table view controller, without burdening the controller class with tasks that a cell should do. This can be done by overwriting prepareForReuse in CathyTaskLogTableViewCell.
func prepareForReuse() {
super.prepareForReuse()
self.messageButton.hidden = true // or false, depending on what's the initial state
// other stuff that needs reset
}
With the current code, messageButton gets hidden only if the cell doesn't find something in messages. So for cells that had this button visible, were reused, and now correspond to a cell that doesn't have a correspondent in messages, the button will remain visible.

Stop the reuse of custom cells Swift

I have an uitableview with a custom cell which gets data from the array.
Custom cell has an uilabel and an uibutton (which is not visible until the uilabel text or the array object which loads for the text - is nil).
On launch everything is fine. When i press the uibutton the array is being appended, the new cells are being inserted below the cell.
But when i scroll - all of a sudden the uibutton appears on other cells where this conditional uilabel text isEmpty is not implied.
Here is how the whole process looks like
Here is my code for cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! TblCell
cell.lblCarName.text = someTagsArray[indexPath.row]
if let text = cell.lblCarName.text where text.isEmpty {
cell.act1.hidden = false
} else {
println("Failed")
}
cell.act1.setTitle(answersdict[answersdict.endIndex - 2], forState:UIControlState.Normal)
cell.act2.setTitle(answersdict.last, forState:UIControlState.Normal)
return cell
}
So my general question is how do i stop the reuse of those custom cells?
As far as i'm aware there is no direct way of doing this on reusablecellswithidentifier in swift, but maybe there are some workarounds on that issue?
When a cell is reused, it still has the old values from its previous use.
You have to prepare it for reuse by resetting that flag which showed your hidden control.
You can do this either in tableView:cellForRowAtIndexPath: or the cell's prepareForReuse method.
Update:
Here's an example you can add for TblCell:
override func prepareForReuse()
{
super.prepareForReuse()
// Reset the cell for new row's data
self.act1.hidden = true
}

Resources