I am trying to display a progress indicator inside of a collectionView cell. To do this there is a background thread that sends a notification to the Main Thread to update the progress indicator.
In main view controller...
func updateProgressIndicator(notification:NSNotification){
let userInfo = notification.userInfo! as NSDictionary
let date = userInfo["date"] as! NSDate
let percentComplete = userInfo["percent"] as! Double
self.progressIndicator.text = "\(percentComplete)%" // this works
let dateIndex = self.calendarDates.indexOf(date)
let indexPath = NSIndexPath(forItem: dateIndex!, inSection: 0)
let cell = self.collectionView.dequeueReusableCellWithReuseIdentifier("DateCell", forIndexPath: indexPath) as! DateCell
cell.showProgress()
}
The function locates the indexPath for the cell to be updated. The cell's showProgress method is then called.
class DateCell: UICollectionViewCell {
#IBOutlet weak var dotGraph: DotGraphView!
func showProgress(){
print(" DateCell showProgress") // this does get printed
self.setNeedsDisplay()
}
override func drawRect(rect: CGRect) {
print(" DateCell drawRect") // this never gets printed
//drawing functions go here
}
}
The showProgress() method is called for the correct cell and the print message is displayed. When the showProgress method calls setNeedsDisplay() however, the drawRect function is never executed.
The only way I've gotten the cell to update is to fully reload the cell using reloadRowsAtIndexPaths, however this should be unnecessary.
Any ideas on how to get the drawRect function to be called?
You say that showProgress() is called for the correct cell, but that seems unlikely. When you call dequeueReusableCellWithReuseIdentifier(_:forIndexPath:), I would expect you to get a different instance of DateCell than the one the collection view is currently displaying. The one being displayed is in use, so it wouldn't be returned from a dequeue... method. You can test whether I'm correct about this by using NSLog on the cell. I expect the address of this one to be different than the one you returned in cellForItemAtIndexPath().
Rather than dequeuing the cell, you should just put the cell in a property so that everyone uses the same one. That's the cell you should return in cellForItemAtIndexPath() as well.
As Rob suggested, the dequeueReusableCellWithReuseIdentifier(_:forIndexPath:) was causing the problem. Storing the cell in a dictionary when it was created, allowed it to be referenced directly. This meant that the drawRect() would be called with setNeedsDisplay()
Below are the updated functions...
var calendarDateCells:[NSDate:DateCell] = [:]
func updateProgressIndicator(notification:NSNotification){
let userInfo = notification.userInfo! as NSDictionary
let date = userInfo["date"] as! NSDate
let myCell = self.calendarDateCells[date]
if(myCell != nil){
myCell!.showProgress()
}
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("DateCell", forIndexPath: indexPath) as! DateCell
let date = calendarDates[indexPath.item]
self.calendarDateCells[date] = cell
return cell
}
Related
i am using a collectionview inside a tablview cell, in which collectionview cells are having an imageview on which images are displaying from the array of URLs in cellForItemAtIndexPath. I am calling webservice inside tableview cell class to getting array of URLs.
My problem is that whenever i scrolls down or up tableview, collectionview cells are repeating, images of collectionview are repeating itself. so how to get rid out of this.
My Code is as follows -
inside response of webservice
let imgList : NSArray = arrReponseDetails.value(forKey: "imageList") as! NSArray
self.arrImgUrls.removeAll()
for dict in imgList[0] as! NSArray {
let dictionary = dict as! NSDictionary
let strUrl : String = dictionary["url_highRes"] as! String
let url : URL = URL(string: strUrl)!
self.arrImgUrls.append(url)
}
self.collectionView.reloadData()
code inside of cellForItemAtIndexPath
let cell : CollectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionCell
let url : URL = self.arrImgUrls[indexPath.row]
cell.imgSticker.sd_setShowActivityIndicatorView(true)
cell.imgSticker.sd_setIndicatorStyle(.gray)
cell.imgSticker.sd_setImage(with: url, placeholderImage: nil)
cell.imgSticker.contentMode = .scaleAspectFit
return cell
I am not getting much insight from your question but i think problem is with caching of image with reusable cell.
Simply implement prepareForReuse() method in collectionView Cell class(CollectionCell).
override func prepareForReuse() {
super.prepareForReuse()
self.imgSticker = nil
}
Reference of prepareForReuse()
You need to clear it out the old content from the cell's view before setting new one as in some case some content might be left and that reported issue occurs.
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell : CollectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionCell
cell.imageView.image = nil; //Remove the image (cache one)
}
Hope this helps!!
Hi You should add the below code on CollectionCell class:
- (void)prepareForReuse
{
[super prepareForReuse];
[[self imgSticker] setImage:nil];
}
Note: It would be better if you can set the placeholder image also.
I wouldn' use colelctionView inside tableView, it's really strange approach. I used to do it myself for the first time and it got me into trouble. Consider changing the logic for collectionView with custom layout...
But to answer your question, it's better to use this approach (check out this answer)
just set in the CellForRow the imageView method according to this answer:
https://stackoverflow.com/a/27712427/5774854
Good time of day, I'm new in iOS development. I'm developing project where i have tableViewCell and button,progressBar on it. When i tap button indexPath of this cell is passed through delegate to viewController and then with another method i'm
downloading some data and show it's progress in progressBar. So when i tap to one cell and then to another, progress in first cell stops and continues in second, could anyone help? Thanks in advance )
Here is delegate methods in viewController:
func didTouchButtonAt(_ indexPath: IndexPath) {
songs[indexPath.row].isTapped = true
let selectedSong = self.songs[indexPath.row] as Song
DownloadManager.shared.delegate = self
self.indexQueue.append(indexPath)
self.selectedIndexPath = indexPath
DownloadManager.shared.download(url: selectedSong.url , title: selectedSong.title)
}
func downloadProgress(_ progress: Progress) {
if (progress.completedUnitCount) < progress.totalUnitCount {
selectedIndexPath = indexQueue.first
}
else if(!indexQueue.isEmpty){
indexQueue.removeFirst()
}
print(progress.fractionCompleted)
print(progress.completedUnitCount, progress.totalUnitCount)
print(indexQueue.count)
var cell: ViewControllerTableViewCell?
cell = self.tableView.cellForRow(at: self.selectedIndexPath!) as? ViewControllerTableViewCell
if cell != nil {
cell?.progress = Float(progress.fractionCompleted)
}
}
this is cell:
#IBAction func downloadButtonTouched(sender: Any){
self.delegate?.didTouchButtonAt(self.indexPath!)
self.progressBar.isHidden = false
}
As #RakshithNandish mentioned, I'v used list of indexPathes and when i tap to button, list adds indexPath. So, before passing progress to cell i check if progress is completed: if not, pass progress to first element of queue, otherwise just delete first element from queue, works fine.
You can create a model that may be an array which will hold the indexpath of tapped button's cell i.e. append indexpath to the array whenever button is tapped and remove it whenever you want. Later on while returning cells in cellForRowAtIndexPath you check if the array contains indexPath for which you are returning cell.
class DemoCell: UITableViewCell {
#IBOutlet button: UIButton!
}
class DemoTableViewController: UITableViewController {
var buttonTappedIndexPaths: [IndexPath] = []
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: DemoCell.className, for: indexPath) as! DemoCell
if buttonTappedIndexPaths.contains(indexPath) {
//show progress view spinning or whatever you want
} else {
//don't show progress view
}
}
}
After reading through a bunch of other questions on SO about reloading Firebase observeEventType observers, I am pretty confused. I am adding my observer dictionaries to a variable accessible within the entire controller. I then assign that value to the data source dictionary and it loads all of the previously added children.
However, once I try to add new values from another simulator or manually inputting within the backend, my global dictionary updates with the new value, but my data source variable does not. Once I leave the controller it will eventually update, but it defeats the purpose of using Firebase.
I think an open observer should be in viewWillAppear, but a bunch of sources online seemed to have it in viewDidLoad.
I am using a segmented control to go through each custom class, which may be causing the issue. The setup is one collection view controller whose cells are custom collection views that are cells as well.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.loadFireData()
}
func loadFireData() {
if let locationId = location.locationId {
let postQuery = ref.child("Posts").child(selectedRegion).child(locationId).queryOrderedByChild("descTime")
postQuery.observeEventType(.ChildAdded, withBlock: { (snap) in
if snap.exists() {
let postQuery = snap.value! as! [String: AnyObject]
let feedPost = FeedModel()
feedPost.value = postQuery["value"] as? String
feedPost.key = postQuery["key"] as? Int
feedPost.balance = postQuery["balance"] as? Double
self.post.append(feedPost)
dispatch_async(dispatch_get_main_queue(), {
self.collectionView!.reloadData()
})
}
}
})
}
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if indexPath.section == 1 {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(feedCellId, forIndexPath: indexPath) as! LocationFeedCell
cell.location = location
//this doesn't seem to be updating here
cell.posts = posts
return cell
}
Any help will be greatly appreciated
you are only adding feetpost into post array. So after reload collectionview, you should add newest feetpost in to your custom cell's label according to indexpath.row
if indexPath.section == 1 {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(feedCellId, forIndexPath: indexPath) as! LocationFeedCell
cell.location = location
//Replace this below line:
cell.posts = post[indexpath.row] as! String
return cell
}
My TableView features custom Cells which have a button to display corresponding detailed info in another view.
This thread here got me started and I tried to implement the approach with the delegate inside the customCell:
How to access the content of a custom cell in swift using button tag?
What I want to achieve is that when I click on the button it reads the name of the cell and passes it on to the next controller. However it seems that I cannot pass the name with the delegate method and its field is nil.
How can I get the specific content of a cell when clicking on its button?
This is what I did so far:
In the class creating my own cell I set delegate:
protocol CustomCellDelegate {
func cellButtonTapped(cell: DemoCell)
}
(........)
var delegate: CustomCellDelegate?
#IBAction func buttonTapped(sender: AnyObject) {
delegate?.cellButtonTapped(self)
}
In the TableViewController I have the following:
override func tableView(tableView: UITableView, cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
let cell =
tableView.dequeueReusableCellWithIdentifier("FoldingCell",
forIndexPath: indexPath) as! DemoCell
cell.delegate = self
//TODO: set all custom cell properties here (retrieve JSON and set in cell), use indexPath.row as arraypointer
let resultList = self.items["result"] as! [[String: AnyObject]]
let itemForThisRow = resultList[indexPath.row]
cell.schoolNameClosedCell.text = itemForThisRow["name"] as! String
cell.schoolNameOpenedCell.text = itemForThisRow["name"] as! String
self.schoolIdHelperField = itemForThisRow["name"] as! String
cell.schoolIntroText.text = itemForThisRow["name"] as! String
//call method when button inside cell is tapped
cell.innerCellButton.addTarget(self, action: #selector(MainTableViewController.cellButtonTapped(_:)), forControlEvents: .TouchUpInside)
cell.school_id = itemForThisRow["name"] as! String
// cell.schoolIntroText.text = "We from xx University..."
return cell
}
And finally the target method when the button inside the cell is clicked
func cellButtonTapped(cell: DemoCell) {
print("the school id: ")
print(cell.schoolNameOpenedCell) //this line throws an error EXC_BAD_ACCESS 0x0
}
Firstly, the object innerCellButton is not a Cell, it's a button. The simple way to solve your problem is, just refer the index of the button. Please find the below method.
func cellButtonTapped(AnyObject: sender) {
let resultList = self.items["result"] as! [[String: AnyObject]]
//Get the tag value of the selected button.
//Button tag should be matching with the corresponding cell's indexpath.row
let selectedIndex = sender.tag
let itemForThisRow = resultList[selectedIndex]
print("the school id: \(itemForThisRow[\"name\"])")
}
* And set each button's tag as indexPath.row *
E.g.,
override func tableView(tableView: UITableView, cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
// Dequeue your cell and other code goes here.
// set the button's tag like below.
cell.innerCellButton.tag = indexPath.row
return cell
}
Close. I wouldn't use Suresh's method since it does not help find the IndexPath, which includes section and row.
First, I would recommend a model object for your table view data source. Learn more about the MVC pattern as well as parsing a JSON response to an object with mapping. However, this would give you the data you want.
func cellButtonTapped(cell: UITableViewCell) {
let indexPath = tableView.indexPathForCell(cell)
let resultList = self.items["result"] as! [[String: AnyObject]]
let itemForThisRow = resultList[indexPath.row]
let name = itemForThisRow["name"] as! String
}
I am developing news feed and I am using uitableview to display data. I am loading each cell data synchronically in other thread and use protocol method to display loaded data:
func nodeLoaded(node: NSMutableDictionary) {
for var i = 0; i < nodesArray.count; ++i {
if ((nodesArray[i]["id"] as! Int) == (node["id"] as! Int)) {
nodesArray[i] = node
}
}
}
The problem is that when I scroll my uitableview (while data synchronically loading), some of my cells repeats (8 row has same content like first, or 6 row has the same content like second row). When I scroll after some time (I suppose after data is loaded) then all become normal.
I looking for answers and found that I have to check if cell is nill at cellForRowAtIndexPath, but in swift my code is different then in objective C:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: NewsCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! NewsCell
var node = nodesArray[indexPath.row] as! NSDictionary
if (node["needLoad"] as! Bool) {
dbHelper.getNode(node["id"] as! Int, hash: node["id"] as! Int, tableName: DbHelper.newsTableName, callback: self)
} else {
cell.id = node["id"] as! Int
cell.titleLabel.text = node["title"] as? String
cell.descriptionLabel.text = node["description"] as? String
cell.imgView.image = WorkWithImage.loadImageFromSD((node["image"] as! String))
}
return cell
}
Also I can't check if cell == nil bcs of binary error (NewsCell can't be nil).
What should I do? Thx.
you seem to have created a separate class for UITableViewCell. The problem with your code is that you are not resetting the labels when reuse happens.
Oveeride prepareForReuse method in your custom UITableviewCell class and reset your interfaces there. That should fix the issue.