Firebase & Alamofire Url TableViewCell image load Issue Swift 4 - ios

I am settiing an image onto a tableViewCell with Alamofire
let messageImage = cell?.viewWithTag(6) as! UIImageView
messageImage.image = nil
messageImage.af_setImage(withURL: photoUrl! as URL)
return cell
I am appending each cell item to an array using Firebase:
ref.child("messages").child(ticker.current).queryOrderedByKey().observe(.childAdded, with: {snapshot in
let snapDict = snapshot.value as? NSDictionary
let photoUrl = snapDict?["photo_url"] as? String ?? ""
messageArray.insert(messageCellStruct(photoUrl: photoUrl), at: 0)
})
Then I am updating the image on the Same Exact URL with FirebaseStorage
When I re-call the cell ViewController that the TableViewCell is in, the image is not changed on the first couple of cells. But as I scroll to older cells the image is updated:
messageArray.removeAll()
tableView.reload()
If I rebuild the app all cell images are how they are supposed to be.
I am assuming this is because of an Observer error or Im not removing the observer. I really dont know.
1.) The cells share the same exact URL im just changing the data.
2.) It seems to be only working when a cell hasnt loaded yet(or been assigned).
3.) It works perfectly fine after I rebuild and run the app.
Maybe I need to clear the Alamofire cach?

It sounds like you need to set the old image to nil before you set the image in messageImage.af_setImage(withURL: photoUrl! as URL).
Cell's are supposed to get cleaned up before they are reused: Apple -PrepareForReuse but Apple also says:
For performance reasons, you should only reset attributes of the cell
that are not related to content, for example, alpha, editing, and
selection state. The table view's delegate in
tableView(_:cellForRowAt:) should always reset all content when
reusing a cell.
That basically means you Shouldn't set your imageView's image to nil in prepareForReuse BUT if you sub classed the cell you can create a function inside the subclass and in cellForRowAtIndePath call it before you run messageImage.af_setImage(withURL: photoUrl! as URL). Example Below:
Create subclass for cell and name it MyCustomCell
class MyCustomCell: UITableViewCell {
#IBOutlet weak var messageImage: UIImageView!
func setImageToNil(){
messageImage.image = nil
// I don't use AlamoFire and don't know anything about it but if they have something to set the imageView's image to nil you can also try using that instead
}
}
Inside your tableView's cellForRowAtIndexPath cast the cell then call setImageToNil() before you run messageImage.af_setImage
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 0. cast the cell as MyCustomCell
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCustomCell", for: indexPath) as! MyCustomCell
// 1. call the setImageToNil() method and whatever image is inside messageImage will get set to nil every time the cell is reused
cell.setImageToNil()
// 2. the new image should always appear because the old image was set to nil
cell.messageImage.af_setImage(withURL: photoUrl! as URL)
return cell
}

Alamofire saves image cache locally.
Because I was changing data to the Same Exact URL I had to remove the cache.
UIImageView.af_sharedImageDownloader.imageCache?.removeAllImages()
UIImageView.af_sharedImageDownloader.sessionManager.session.configuration.urlCache?.removeAllCachedResponses()

Related

How to get all custom tableview cell textField and textView value outside tableView

I tried to access custom tableView cell textfield from outside tableView like this :
for i in 0..<NameCounter {
let indexPath = IndexPath(row: i, section: 0)
guard let cell = sampleTableView.cellForRow(at: indexPath) as? sampleCellView else{
return
}
if let text = cell.txtName.text, !text.isEmpty {
let value = text
print("Getting NameText : \(value)")
}
if let text = cell.txtNote.text, !text.isEmpty {
let value = text
print("Getting noteText : \(value)")
}}
But the problem is above method you can only get visible cell of tableView except cell is nil. and because I guard it to avoid nil cell, I did not get all textfield value.
If I remove the guard like this :
let cell = sampleTableView.cellForRow(at: indexPath) as! sampleCellView
It will crash and got some cell is nil.
How to access all textfield value from outside tableView and got all cell (cell maynot nil)?
I have multiple tableView, and inside each tableView cell, I put txtName and txtNote. I want to detect which txtName and txtNote is being edited, so I can put in the right model.
note: txtName is textfield and txtNote is textView
You shouldn't rely on the values being taken from the cell labels, text fields etc.
Once a cell goes off-screen - it gets thrown to a pool for later reuse, and it may even get deallocated.
You should keep the view state inside some array, and then you can SAFELY get any value at any index.
If you have 1000 cells, perhaps only 10-20 will be visible at any time, and maybe 40-50 or so in the reusable cells pool. If you are at index path row 100 - obviously the cells after index path row 150 will be nil.
This return
let cell = sampleTableView.cellForRow(at: indexPath) as! sampleCellView
nil and crash for any cell that's not visible as of the fact that the table cells are reusable , you need to save the user typing for each cell to model ( tableView dataSource array ), then get the values from it
var model = [String]()
// inside cellForRowAt
cell.textView.delegate = self
cell.textView.tag = indexPath.row
}
#objc func textViewDidChange(_ tex: UITextView) {
model[tex.tag] = tex.text!
}
class VC:UIViewController,UITextViewDelegate {
As a good standard practise, there should be no need to get values from the UITableViewCells. You should instead get the values from the Model that is presented in the UITableView, based on some logic as per the need.
Further, when it is needed to access any text that was typed on UITableViewCell, then it should be updated in the associated model first and should be accessed from there.
Behind the scene UITableViewCell are re-used and only the cells currently visible will show you actual data if you access it from UITableView. Hence persisting cell's entered values to some array / dictionary for later use is the option.

Comparing an list of array and selected value and displaying it in tableview cell based on index

I have an application wherein when tableview cell is clicked, another tableview is loaded and an api call is made. Based on the response from api, table view list is loaded and when a particular item in the second tableview is selected, there is a selected checkbox displayed just besides the tableview text label and at the same time database is updated with selected value,
so when I come back to the first tableview I display a label with selected item.
When the first tableview cell I clicked, api is called and results of api should be compared with the active list from database and that particular cell should remain selected.
When there is some item selected in first tableview and when i click on that particular cell, api results reload the tableview and selection for respective cell is not displayed.
Following is the code:
for selectedDict in (appDelegate?.selectedCategoryFilterArray)! {
let selectedUuid = selectedDict.categoryUuid
print("selectedUuid\(selectedUuid)")
for allDict in self.requestedFiltersArray!{
let allUuid = allDict.objectForKey("uuid") as? String
if selectedUuid == allUuid {
cell.imgSelected.image = UIImage(named: "radio_selected")
continue
}else{
cell.imgSelected.image = UIImage(named: "radio")
}
print("allUuid\(allUuid)")
}
}
This is not working as expected, no cell is displaying as selected even if their is a cell selected.
Where have you placed this code?
From what I think you might be trying to achieve, here is an approach that I would suggest.
For your second tableView which is loaded based on the API call results, I would add a String property called uuid to your custom UITableViewCell class.
Then when you call cellForRowAtIndexPath to populate your second tableView, instantiate each cell as your custom UITableViewCell and set its uuid property to the appropriate value based on the results array, using the indexPath.row.
After setting that, next you can run the for loop with your condition to match the cell's uuid property value against the selectedUuid value from your AppDelegate and thereafter set the image as per the logic.
A rough implementation:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier") as! YourCustomTableViewCell
let allDict = self.requestedFiltersArray[indexPath.row]
let allUuid = allDict.objectForKey("uuid") as? String
cell.uuid = allUuid
for selectedDict in (appDelegate?.selectedCategoryFilterArray)! {
let selectedUuid = selectedDict.categoryUuid
if selectedUuid = cell.uuid {
cell.imageSelected.image = UIImage(named: "radio_selected")
} else {
cell.imageSelected.image = UIImage(named: "radio")
}
}
}

Should conditional binding be used always for dequeuing cell

Inside my cell for row at indexPath, I have been using the following code to do most of my work because that is what I have been taught. I was wondering, is it necessary to always use if let to do this work? Because I never find that I ever fall into the else statement.
When would I need to use if let or just let inside cellForRowAtIndexPath?
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("myCustomCell") as? myCustomCell {
} else {
return myCustomCell()
}
}
UITableView has two dequeue modes:
dequeueReusableCell(withIdentifier:): The table view tries to dequeue a cell. If there are none, it will try to create one using the cell you registered with the reuseIdentifier. If you didn't register a cell, it will return nil giving you the chance to create one yourself.
This is where the else clause in your code would come into effect. Basically never, since presumably you did register a cell class. Most likely in the Storyboard (by setting the identifier in the inspector) but you can also do it in code.
dequeueReusableCell(withIdentifier:for:), note the additional parameter. The table view tries to dequeue a cell: If there are none, it will try to create one using the cell class you registered using the reuseIdentifier. If you didn't register a cell, it will crash.
Solution
If you can guarantee that a) the cell is correctly registered and b) the type of the cell is set correctly, you would generally use the second option. This avoids the exact issue you're having:
let cell = tableView.dequeueReusableCellWithIdentifier("myCustomCell", forIndexPath: indexPath) as! myCustomCell
(However, it is still perfectly fine to use if let even in this case.)

caching images on UITableViewCells with AlamofireImages in my Swift app

I'm writing a Swift app that displays photos fetched from server on each cell of my UITableViewController.
So far my code looks as follows:
func tableView(detailsPanel: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = detailsPanel.dequeueReusableCellWithIdentifier("cell") as! DetailsCell
let test:SingleTest = self.items[indexPath.row] as! SingleTest
if(test.photo != "") {
cell.myPhoto.af_setImageWithURL(NSURL(string: test.photo)!)
}
}
Now, the problem is that not every cell has a photo stored in cell.photo. Some of them are empty strings (""). In that situation when I quickly scroll through the table view, I see that those empty UIImageViews are filled with photos from other cells.
The quick fix for that seems to be adding an else block:
if(test.photo != "") {
cell.myPhoto.af_setImageWithURL(NSURL(string: test.photo)!)
} //this one below:
else {
cell.myPhoto.image = UIImage(named: "placeholderImg")
}
Now whenever there is no photo, the placeHolderImg will be displayed there. But... is there a way of avoiding it and just do not display anything there? And by not displaying anything I mean not displaying images from different cells?
You are reusing your cells, thus the cell will still use the previous image that it has loaded unless you set it to nil or a placeholder image. However I believe you do not need to use an if statement. You can use the placeholderImage parameter of af_setImageWithURL.
cell.myPhoto.af_setImageWithURL(NSURL(string: test.photo)!, placeholderImage: image)

PFImage in table view too large

I'm using Parse. I have a column in my table to store images as PFFiles. I am using a PFQueryTableViewController. I want to display images in my Parse table's "image" column as thumbnails for each table view cell that has an image associated with it.
Here is the relevant cellForRowAtIndexPath code I'm suspicious of:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!, object: PFObject!) -> PFTableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("ListingCell", forIndexPath: indexPath) as PFTableViewCell
// This works as you can see "grapes" displayed on the screenshot I added.
var label = cell.viewWithTag(2) as UILabel
label.text = object["title"] as? String
// Accessing the PFImageView within my custom PFTableViewCell via a tag
let imageView = cell.viewWithTag(3) as PFImageView
// Extracting the image stored as a PFFile stored in the database
imageView.file = object["image"] as? PFFile // remote image
// Setting the actual thumbnail with the image when loaded
imageView.loadInBackground { (image: UIImage!, error: NSError!) -> Void in
imageView.image = image
}
return cell
}
I have 2 records stored in this Parse table, the first does not have an image and the second one does (hence why the "grapes" cell is empty). My question is, why is the image not displaying in the thumbnail (properly constrained) I created and rather taking up the entire screen? What am I doing wrong here? The even weirder part is that I used Xcode's view debugging to capture the view hierarchy and it shows it properly placed within the thumbnail. Any ideas?
You need to make sure your image mode is set to something like "Scale To Fill".

Resources