tableView.reload() freezes the app while running - ios

I have this huge problem! I'm fetching data from firebase, saving it to an array and then using cellForRowAt I populate the row. In the end I run tableView.reload(). I have to run tableView.reload() because cellForRowAt runs before the array can populate with db elements.
The problem is that when I run tableView.reload() the app freezes and if I click any button it won't work. The button runs only when tableView.reload() finished running.
I run tableView.reload() as soon as the array have been populated.
Some code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ImageTableViewCell
let preview2 = imageDownload(images[counter!]["previewURL"]! as! String)
cell.img2.imagePreview = preview2
cell.img2.setImage(preview2, for: .normal)
cell.img2.addTarget(self, action: #selector(imgTapped(_:)), for: .touchUpInside)
let preview1 = imageDownload(images[counter!-1]["previewURL"]! as! String)
cell.img1.imagePreview = preview1
cell.img1.setImage(preview1, for: .normal)
cell.img1.addTarget(self, action: #selector(imgTapped(_:)), for: .touchUpInside)
counter = counter! - 2
return cell
}
func imageDownload(_ url: String) -> UIImage {
let imageURL = URL(string: url)
let imageData = try? Data(contentsOf: imageURL!)
let image = UIImage(data: imageData!)
return image!
}
class ImageTableViewCell: UITableViewCell {
#IBOutlet var img1: ExtendedButton!
#IBOutlet var img2: ExtendedButton!
}

The problem is in using
let imageData = try? Data(contentsOf: imageURL!)
that blocks the current main thread not because of tableView.reloadData() , you better use SDWebImage

Related

How to get different image from assets and assign it to image view in different table view cells

I am trying to add an image to my tableview cell by using an NFC reader session. So, my problem here is that every first reader session, I am getting the correct image in image view, but when I try the reader session the second time, I am stuck with two same last assigned image on both cells of my table view. I know it because of tableView.dequeueReusableCell method, but I am not sure which method to use to get correct image incorrect cells.
I have also attached a screenshot to make more clear of what I mean.
In the screenshot is should see an image of a water bottle from my assets, but instead, I am getting the last assigned image to every cell
Here is the code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
cell.nfcModel = arrData[indexPath.row]
// IMG CELL
cell.img.image = UIImage(named: name)
return cell
}
Not an expert in NFC readers.
1.Create an array of products to store product data from NFC render.
2.in tableView func cellForRowAt you can render the images from
favoriteMovies using displayMovieImage function.
Sidenote:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var favoriteMovies: [Movie] = []
override func viewWillAppear(_ animated: Bool) {
mainTableView.reloadData()
super.viewWillAppear(animated)
if favoriteMovies.count == 0 {
favoriteMovies.append(Movie(id: "tt0372784", title: "Batman Begins", year: "2005", imageUrl: "https://images-na.ssl-images-amazon.com/images/M/MV5BNTM3OTc0MzM2OV5BMl5BanBnXkFtZTYwNzUwMTI3._V1_SX300.jpg"))
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let moviecell = tableView.dequeueReusableCell(withIdentifier: "customcell", for: indexPath) as! CustomTableViewCell
let idx: Int = indexPath.row
moviecell.tag = idx
//title
moviecell.movieTitle?.text = favoriteMovies[idx].title
//year
moviecell.movieYear?.text = favoriteMovies[idx].year
// image
displayMovieImage(idx, moviecell: moviecell)
return moviecell
}
func displayMovieImage(_ row: Int, moviecell: CustomTableViewCell) {
let url: String = (URL(string: favoriteMovies[row].imageUrl)?.absoluteString)!
URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { (data, response, error) -> Void in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async(execute: {
let image = UIImage(data: data!)
moviecell.movieImageView?.image = image
})
}).resume()
}

Returns nil if I scroll tableView fast

Trying to load images in tableView asynchronously in (Xcode 9 and Swift 4) and seems I have a correct way but my code stops working if I scroll my tableView fast. So basically I had found nil error.
Here is my code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
let feed = feeds[indexPath.row]
cell.titleLabel.text = feed.title
cell.pubDateLabel.text = feed.date
cell.thumbnailImageView.image = nil
if let image = cache.object(forKey: indexPath.row as AnyObject) as? UIImage {
cell.thumbnailImageView?.image = image
} else {
let imageStringURL = feed.imageUrl
guard let url = URL(string: imageStringURL) else { fatalError("there is no correct url") }
URLSession.shared.downloadTask(with: url, completionHandler: { (url, response, error) in
if let data = try? Data(contentsOf: url!) {
DispatchQueue.main.async(execute: {
guard let image = UIImage(data: data) else { fatalError("can't create image") }
let updateCell = tableView.cellForRow(at: indexPath) as! CustomTableViewCell // fast scroll issue line
updateCell.thumbnailImageView.image = image
self.cache.setObject(image, forKey: indexPath.row as AnyObject)
})
}
}).resume()
}
return cell
}
I have issue on the line:
let updateCell = tableView.cellForRow(at: indexPath) as! CustomTableViewCell
If I scroll down slowly everything works just fine and no mistakes appear.
Does anyone know where I've made a mistake?
This may happens if cell you are trying to get using tableView.cellForRow(at:) is not visible currently.
To avoid crash you can use optionals as:
let updateCell = tableView.cellForRow(at: indexPath) as? CustomTableViewCell // fast scroll issue line
updateCell?.thumbnailImageView.image = image
Keep everything as it is, I hope it should work without any errors.
You can consider one of popular UIImage extension libs, like I said in comment for example AlamofireImage and set thumbnail with the placeholder, as soon as image will be ready it will be replaced automatically.
One more thing I change no need to have updateCell I removed it.
Please add placeholder image and test it should work, sorry I didn't fully check syntax.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
let feed = feeds[indexPath.row]
if let image = cache.object(forKey: indexPath.row as AnyObject) as? UIImage {
cell.thumbnailImageView?.image = image
} else {
let imageStringURL = feed.imageUrl
guard let url = URL(string: imageStringURL) else { fatalError("there is no correct url") }
cell.thumbnailImageView.af_setImage(withURL : url, placeholderImage: <your_placeholderImage>)
return cell
}

create a group from selected tableview cells

I am trying to send the checked rows from my table view into a newly created group to my firebase database, once the user selects the rows and the checkmark is displayed, how can I send the selected rows information to my database. I am not sure if I should be doing this in the didSelectRowAt function or to add a "create group" button once the rows are selected. Any information will be helpful thank you.
import UIKit
class FriendsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var userList = [Users]()
#IBOutlet weak var myTableView: UITableView!
final let urlString = "https://api.lookfwd.io/v1/test/users"
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return userList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
myCell.selectionStyle = UITableViewCellSelectionStyle.none
myCell.nameLabel.text = userList[indexPath.row].name
return myCell
}
override func viewDidLoad() {
super.viewDidLoad()
self.downloadJsonWithTask()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if myTableView.cellForRow(at: indexPath)?.accessoryType == UITableViewCellAccessoryType.checkmark{
myTableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.none}
else{
myTableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.checkmark
}
}
func downloadJsonWithTask() {
let url = NSURL(string: urlString)
var downloadTask = URLRequest(url: (url as URL?)!, cachePolicy: URLRequest.CachePolicy.reloadIgnoringCacheData, timeoutInterval: 20)
downloadTask.httpMethod = "GET"
URLSession.shared.dataTask(with: downloadTask, completionHandler: {(data, response, error) -> Void in
if let response = data {
if let jsonData = try? JSONSerialization.jsonObject(with: response, options: .allowFragments) as? [String:Any] {
if let dataArray = (jsonData as AnyObject).value(forKey: "users") as? [[String:Any]] {
for data in dataArray{
let newUser = Users(data: data)
self.userList.append(newUser)
print(jsonData!)
}
}
OperationQueue.main.addOperation({
for use in self.userList {
print(use.name ?? "")
}
self.myTableView.reloadData()
})
print(jsonData!)
}
}
}).resume()
}
}
The best way to achieve this to add a button instead of checkbox, set the images of checkbox for the selected and default state of button and in your "cellForRowAt" method do this:
In your "cellForRowAt" method:
let button = cell.viewWithTag(123) as? UIButton // 123 is tag that is defined in Stoyboard for this button/checkbox.
button.accessibilityHint = "\(indexPath.row)"
button.addTarget(self, action: "action:", forControlEvents:
UIControlEvents.TouchUpInside)
Paste this method any where in your ViewController:
func action(sender:UIButton!) {
let position = Int(sender.accessibilityHint) // This is the position that will help you to get the specific item from your list.
if (sender.selected == true)
{
sender.setBackgroundImage(UIImage(named: "box"), forState:
UIControlState.Normal)
sender.selected = false
// Remove from Datbase
}
else
{
sender.setBackgroundImage(UIImage(named: "checkBox"), forState:
UIControlState.Normal)
sender.selected = true
// Add into database.
}
}
This is a method of a button directly connected from Storyboard to ViewController. For your table view you have o follow these steps:
Get your button in "cellForIndexAt" method with Tag.
Set "accessibilityHint" of your button with "indexPath.row".
Add target to your button.
Get the position through "accessibilityHint" in your targeted method.
Use logic given above in your targeted method.
But if you don't want to go through all this process then simply add your item in databse in your "didSelectRowAt" method, but it will perform the action wherever user taps on the whole cell.

Swift: set Image to Custom cell ImageView

i want to set the Image to Custom cell. I am confuse how to do this.
lets say I have a custom cell in my UITable View.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCellOne", forIndexPath: indexPath) as! CustomOneCell
let tapGestureRecognizer = UITapGestureRecognizer(target:self, action:#selector(imageTapped(img:)))
cell.imageView.isUserInteractionEnabled = true
cell.imageView.addGestureRecognizer(tapGestureRecognizer)
return cell
}
Now as soon as i tap in the Image the following function gets called:
func imageTapped(img: AnyObject){
print("Image clicked.")
if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
let imageData: NSData = UIImagePNGRepresentation(myImage)
let image: UIImage = UIImage(data:imageData,scale:1.0)
print(image)
//i am uploading the photo from here. and i want to set this picture to my cell imageView.
}
dismissViewControllerAnimated(true, completion: nil
}
I am confused how to call the cell imageview from here??
How can i further procced?? I just need to set That obtained image to imgeView in cell other everything works fine..
Here's an example I have just tested
I have a custom TableViewCell, named ImageTableViewCell, which contains a single UIImageView, named customImageView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cellImage", for: indexPath) as! ImageTableViewCell
// initialising with some dummy data of mine
cell.customImageView.image = UIImage(named: "Image\(indexPath.row).png")
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.imageTapped(_:)))
cell.addGestureRecognizer(tapGesture)
return cell
}
and then in the ViewController
func imageTapped(_ sender: UITapGestureRecognizer)
{
let myCell = sender.view as! ImageTableViewCell
myCell.customImageView.image = UIImage(named: "imageTapped.jpg")
}
This should update the image immediately, without any requirement to reload the table

How to load an image from URL and display it in tableView cell (Swift 3)

I have the image URL and I want to display that image in UIImageView which is placed in a tableView cell.
I created a custom cell and added an outlet for the imageView.
Since I am loading news the URL changes accordingly.
NOTE: I am using Alamofire to process my HTTP requests.
struct News {
let title: String
let text: String
let link: String
let imgUrl: String
init(dictionary: [String:String]) {
self.title = dictionary["title"] ?? ""
self.text = dictionary["text"] ?? ""
self.link = dictionary["link"] ?? ""
self.imgUrl = dictionary["imgUrl"] ?? ""
}
}
And loading info to my custom cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? newsCellTableViewCell
let news = newsData[indexPath.row]
cell?.headline.text = news.title
cell?.excerpt.text = news.text
cell?.thumbnailImage.text = news.imgUrl
return cell!
}
You can use this code to get image from url and can set a placeholder image as well. for example:-
cell.imageView?.imageFromURL(urlString: "urlString", PlaceHolderImage: UIImage.init(named: "imagename")!)
extension UIImageView {
public func imageFromServerURL(urlString: String, PlaceHolderImage:UIImage) {
if self.image == nil{
self.image = PlaceHolderImage
}
URLSession.shared.dataTask(with: NSURL(string: urlString)! as URL, completionHandler: { (data, response, error) -> Void in
if error != nil {
print(error ?? "No Error")
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
self.image = image
})
}).resume()
}}
This becomes quite easy, as you're using Alamofire. Unlike #Mochi's example, this won't block the interface.
Here's an example:
Alamofire.request(news.imgUrl).response { response in
if let data = response.data {
let image = UIImage(data: data)
cell?.thumbnailImage.image = image
} else {
print("Data is nil. I don't know what to do :(")
}
}
*Please note I wasn't able to test this before I answered. You may need to tweak this code a bit.
I used a Kingfisher library
import Kingfisher
tableViewCell class:
class MyCell: UITableViewCell {
#IBOutlet weak var cellImg: UIImageView!
#IBOutlet weak var cellLbl: UILabel!
}
Assign this class to your cell in storyboard
And finally, do this in CellForRowAt
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell")! as! MyCell
let url = URL(string: "https://files.pitchbook.com/website/files/jpg/food_delivery_800.jpg")
cell.cellImg.kf.setImage(with: url)
return cell
}
That's it
Please Use SDWebImage. It's an imageview category that downloads the image in the background without freezing your UI, and also provides caching as well. You can set a placeholder image as well. Placeholders show until your actual image is downloaded. After downloading, the actual image in your cell's Imageview is updated automatically, and you don't need to use any completion handler.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? newsCellTableViewCell
let news = newsData[indexPath.row]
cell?.headline.text = news.title
cell?.excerpt.text = news.text
cell?.thumbnailImage.sd_setImageWithURL(NSURL(string: news.imgUrl), placeholderImage:UIImage(imageNamed:"placeholder.png"))
return cell!
}

Resources