how to load images fast during scrolling in swift 3? - ios

I had implemented a set of images from url and displaying it on collection view and table view and deployed in the iphone 5 device after loading app the images page was not scrolling fast and taking more time delay how to avoid this can anyone help me ?
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return imageArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "productsCell", for: indexPath) as! productsCell
let arr = imageArray[indexPath.row]
let urls = NSURL(string: arr)
let data = NSData (contentsOf: urls! as URL) //make sure your image in this url does exist, otherwise unwrap in a if let check
cell.productImage.image = UIImage(data: data! as Data)
cell.productName.lineBreakMode = NSLineBreakMode.byWordWrapping
cell.productName.numberOfLines = 2
cell.productName.text = self.productName[indexPath.row]
cell.productPrice.text = self.productprice[indexPath.row]
cell.buynowButton .addTarget(self, action: #selector(buyNowButton(_:)), for:UIControlEvents.touchUpInside)
cell.cartButton .addTarget(self, action: #selector(cartButton(_:)), for:UIControlEvents.touchUpInside)
return cell
}

You can use HanekeSwift. It automatically manages caching so your view won't block while loading heavy Images. https://github.com/Haneke/HanekeSwift
Hope this helps!

I suggest to use https://github.com/onevcat/Kingfisher Kingfisher is a lightweight, pure-Swift library for downloading and caching images from the web. It provides a build in activity indicator which can be shown in the image view. Kingfisher makes sure images from the exact same url are only downloaded once, it also provides a way to cancel downloading when for example: when the cell that should display the image got scrolled outside the visible area.

Related

swift: loading array with 300 images

I have ViewController with button. When I press on button I move to TableViewController with images in cells. I have array with 300 images.
But when I press on button my app paused for 12-14 seconds to load an array of images. After 14 second I move to TableViewController. How to fix it?
class TableViewController: UITableViewController {
var imageArray: [UIImage] =
[UIImage(named: "1.jpg")!,
...
UIImage(named: "300.jpg")!]
var textArray: [String] = ["text1", ... "text300"]
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return textArray
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
cell.cardImageView.image = imageArray[indexPath.row]
cell.label.text = "\(textArray[indexPath.row])"
}
}
Rather than creating an array of images straight out why don't you try this:
For numberOfRowsInSection set:
return 299
Add this method to your Viewcontroller:
func loadImageAsync(imageName: String, completion: #escaping (UIImage) -> ()) {
DispatchQueue.global(qos: .userInteractive).async {
guard let image = UIImage(named: imageName) else {return}
DispatchQueue.main.async {
completion(image)
}
}
}
in your tableView cellForRowAt all you will have to call is:
loadImageAsync(imageName: "\(indexPath.row + 1).jpg") { (image) in
cell.cardImageView.image = image
}
cell.label.text = "text\\(indexPath.row + 1)"
It means the app will only have to load the images on demand instead of making a huge array of images that it may never use if the user doesn't scroll all the way down. A 300 image array would be very CPU and memory intensive to create.
Don't store the object of UIImage in the array, as your code suggested, your images are locally stored as asset, just load the image names.
instead of this
var imageArray: [UIImage] =
[UIImage(named: "1.jpg")!,
...
UIImage(named: "300.jpg")!]
Do this
var imageNameArray: [String] =
["1.jpg",
...
"300.jpg"]
In the cellForRowAtIndexPath do following
cell.cardImageView.image = UIImage(named: imageArray[indexPath.row])
You could load the image data in background thread, once available then set it. But this approach has several shortcoming. If you do this you have to manually manage the image caching mechanism, because you dont want to load same image twice while shown twice in the cell.
There is a good news for you that, Uiimage(named:) method is thread safe for iOS9+. So if you are targeting iOS9+ you can load the image using UIImage(named:) in background thread. It will automatically manage the caching mechanism
DispatchQueue.global(qos: .background).async {
// load image here
let image = UIImage(named......
DispatchQueue.main.async {
// set image here
cell.card........
}
}
one possible bug may arise in this approach, such that while scrolling fast you may see old images for a while. You could solve it easily by managing the indexpath for which this image was loaded and comparing with current one.
I believe you should perform some kind of lazy initialization here so you get the benefit of cell reusing.
your code must be like this:
var imageTitles: [String] =
"1.jpg",
...
"300.jpg"]
var textArray: [String] = ["text1", ... "text300"]
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return textArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
cell.cardImageView.image = UIImage(named: imageTitles[indexPath.row])
cell.label.text = textArray[indexPath.row]
}

Swift sorting tableview into separate sections

I've written a program where a user can take pictures and track information about their miniature collections and everything works as intended. Now I'm looking to add some different functionality for the tableview, and I'm not quite sure what would be the best way to go about it. At present when the user adds a model, it appends to a single dictionary of dictionaries then displays it in the tableview in the order it was appended. I would like to sort or separate the data into separate sections based on what codex the model is from.
Whether it would be better to generate separate sections programmatically or use an index, I'm not sure. But in either case, I am at a complete loss of how to accomplish this.
Here is the tableview code I currently have, in case it helps
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return models.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue reusable Cell
let cell = tableView.dequeueReusableCell(withIdentifier: "modelCell", for: indexPath) as! modelCellTableViewCell
// Fetch Item
let model = models[indexPath.row]
// Configure Table View Cell
cell.modelNickname?.text = model.modelNickname
cell.modelNickname.textColor = UIColor(red:0.24, green:0.31, blue:0.35, alpha:1.0)
cell.modelName?.text = model.modelName
cell.codexName?.text = model.codexName
cell.modelOption1?.text = model.modelOption1
cell.modelOption2?.text = model.modelOption2
cell.modelOption3?.text = model.modelOption3
cell.modelOption4?.text = model.modelOption4
let fileManager = FileManager.default
let imageName = model.codexName + model.modelName + model.modelOption1
let imagePath = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent(imageName)
if fileManager.fileExists(atPath: imagePath){
cell.modelImage.image = UIImage(contentsOfFile: imagePath)
}else{
print("No Image found")
}
return cell
}
Any help/suggestions you could offer would be a big help.

Image repeates in every cell of table view in swift3

My JsonData -
let imagestring : String? = (myData as AnyObject).value(forKey: "Post_mid_image") as? String
if imagestring != nil {
let imageTrueString = "https://www.zdoof.com/" + imagestring!
self.imageStringArray.append(imageTrueString )
}
if let NameString = (myData as AnyObject).value(forKey: "Name") as? String {
self.nameStringArray.append(NameString)
}
When i am trying to set it to the table view cell
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.postLableArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reUse", for: indexPath)
let myImage = cell.viewWithTag(30) as! UIImageView
myImage.clipsToBounds = true
if indexPath.row < imageStringArray.count {
if let myImageString = imageStringArray[indexPath.row] as? String {
let ImageUrl = URL.init(string: myImageString)
myImage.kf.setImage(with: ImageUrl)
}
}
return cell
}
The image is repeating in every cell . Why it is happening ? Please help
As per the response you have given, you can show the image like below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let dict = myData as AnyObject
let cell = tableView.dequeueReusableCell(withIdentifier: "reUse", for: indexPath)
let myImage = cell.viewWithTag(30) as! UIImageView
if dict["Post_mid_image"] != nil {
let imageUrl = URL.init(string: strImageUrl)
myImage.kf.setImage(with: imageUrl)
} else {
//Set placeholder image showing no image available
}
return cell
}
Problem is with cell re-usablity of table view here ,you have to handle it , you can have SDWebImage library for loading images in cell or you can have your own image cache which caches images with key/values , key as in image url , so dynamically checking image url for item at indexpath and load cached image with that image url as key.
Hope it helps!!
This is happening because of using tableView.dequeueReusableCell(withIdentifier: "reUse", for: indexPath).
Basically whenever you use dequeueReusableCell(withIdentifier:,For:), it will use the same cell for all of data. It means the total number of cell which are on screen are only going to load, for all other cell, it will use same cell with different value.
now consider a scenario that you are having 500 cells in tableview, but we can manage at most 10-15 cells in display, so for all other cells it will use same cells just modify the value of cell.
so what you can do here is, whenever you use if statement, don't forgot to add else too.
because for one scenario if cell's background is set to red, than we need to add else for another scenario, as cells are just repeated.

Loading image from URL is working inconsistently

I am storing URLs as Strings in an Organization. These URLs are used to fetch each Organization's logo and display it in a tableview. Here is the code I am using to do this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "orgcell", for: indexPath) as! OrgCell
cell.orgAcro.text = filteredOrgs[indexPath.row].acronym
cell.topTag.text = filteredOrgs[indexPath.row].tags[0].title
let data = try? Data(contentsOf: filteredOrgs[indexPath.row].url)
if (data == nil) {
cell.orgImg.image = UIImage()
} else {
cell.orgImg.image = UIImage(data: data!)
}
return cell
}
The only URL this is working for is this one https://pbs.twimg.com/profile_images/718076464856338432/zlcMj0Oo.jpg
It is not working for this one http://www.therightfew.com/wp-content/uploads/2016/09/Planned-Parenthood-Logo-Square.jpg
It has nothing to do with the order by which I load them, I have tried switching it up. Any ideas as to what's going on?

How to delete cache using HanekeSwift Swift?

I am stuck with a problem. I want to populate a tableview with some text and a profile image that can change i use this function for it.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CommonCellView!
cell = self.tableView.dequeueReusableCellWithIdentifier("CommonCellView") as! CommonCellView
cell.nameLabel.text = self.userCollection[indexPath.row].display_name
cell.companyLabel.text = self.userCollection[indexPath.row].user_organisation
cell.profileImage.hnk_setImageFromURL(NSURL(string: self.userCollection[indexPath.row].profile_picture)!)
self.makeImageViewCircular(cell.profileImage.layer, cornerRadius: cell.profileImage.frame.height)
cell.profileImage.clipsToBounds = true
return cell
}
nothing to suprising here. But when i change my own profile picture then i send it to the API and revist this function it shows the cached image. So i tought i might try something a bit diffent why not get all the images for every cell using Alamofire.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CommonCellView!
cell = self.tableView.dequeueReusableCellWithIdentifier("CommonCellView") as! CommonCellView
cell.nameLabel.text = self.userCollection[indexPath.row].display_name
cell.companyLabel.text = self.userCollection[indexPath.row].user_organisation
cell.profileImage.image = UIImage()
//getting the cell image
Alamofire.request(.GET, self.userCollection[indexPath.row].profile_picture)
.response {(request, response, avatarData, error) in
let img = UIImage(data: avatarData!)
cell.profileImage.image = img
}
self.makeImageViewCircular(cell.profileImage.layer, cornerRadius: cell.profileImage.frame.height)
cell.profileImage.clipsToBounds = true
return cell
}
this works to a point where user scrolles very fast the image of a different user will be shown until the request gets fulfilled. Okey so make that happen somewere else and use an array for the images. I also tried that. but because its async the images would go into the array in the wrong order. So back to HanekeSwift. I read the documentation and saw i had a cache on disk but i could not clear or delete it.
to clear the cache i also tried:
NSURLCache.sharedURLCache().removeAllCachedResponses()
but i did not do a thing. it works in the Alamofire situation but its not a good solution either.
I want to use hanekeSwift because HanekeSwift is fast enough to get all the images. but i want to clear the cache everytime the contoller loads.
Any suggestions would be appreciated!
Cees
I found a the problem.
First i was running an older version of the pod. So after updating i could use the function.
Shared.imageCache.removeAll()
after you import haneke into the controller.
the final pice of code looked like this.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CommonCellView!
cell = self.tableView.dequeueReusableCellWithIdentifier("CommonCellView") as! CommonCellView
cell.nameLabel.text = self.userCollection[indexPath.row].display_name
cell.companyLabel.text = self.userCollection[indexPath.row].user_organisation
cell.profileImage.image = UIImage()
//getting the cell image
cell.profileImage.hnk_setImageFromURL(NSURL(string: self.userCollection[indexPath.row].profile_picture)!)
//deleting it from cache
Shared.imageCache.removeAll()
self.makeImageViewCircular(cell.profileImage.layer, cornerRadius: cell.profileImage.frame.height)
cell.profileImage.clipsToBounds = true
return cell
}
it works now but removing the cache all the time an other cell gets filled seems a bit overkill.

Resources