swift: loading array with 300 images - ios

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]
}

Related

How to configure a button containing an image in TableViewCell.swift file, and implement showing different images in each table view cell?

I am trying to show a button as an image of restaurant food and a button below that as a text label containing the name of that restaurant all in a custom table view cell. So when you scroll down the table view, you see pictures of different restaurant food and the name of the restaurant below the picture. I have a XIB file I’m using for this.
At the moment, I’m using a UIImage for the pictures of the restaurants, and this is working, but I’m trying to instead use a button with an image, so that I can make an #IBAction after the picture of the food that is a button is clicked.
I have configured a button showing restaurant names in the TableViewCell.swift file as shown below (the labels show when ran):
TableViewCell.swift code:
#IBOutlet var labelButton: UIButton!
//For retaining title in a property for IBAction use.
private var title: String = ""
func configureLabelButton(with title: String) {
self.title = title //For retaining title in a property for IBAction use.
labelButton.setTitle(title, for: .normal)
}
and implemented showing different restaurant names in each table view cell from an array with the restaurant names as strings in the view controller as shown below.
ViewController.swift code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantTableViewCell", for: indexPath) as! RestaurantTableViewCell
cell.configureLabelButton(with: myData[indexPath.row])
return cell
}
I am trying to do the same thing for showing an image in a button. I have an array of images I'm using for this same purpose called myImages. Here is the code I have so far, but it is not working when run.
TableViewCell.swift code:
#IBOutlet var imageButton: UIButton!
//For retaining image in a property for IBAction use.
private var image: UIImage = UIImage()
func configureImageButton(with image: UIImage) {
self.image = image //For retaining image in a property for IBAction use.
imageButton.setImage(image, for: .normal)
}
ViewController.swift code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantTableViewCell", for: indexPath) as! RestaurantTableViewCell
cell.configureImageButton(with: myImages[indexPath.row]!)
cell.imageButton.contentMode = .scaleAspectFill
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 185
}
I think my error is somewhere in TableViewCell.swift. I think I do not have the correct code there and potentially in ViewController.swift.
I think there is a problem in the UIImage array declaration, the issue might be it not getting an image from the array.
try with this one
var arrImg : [UIImage] = [UIImage(named: "res-1")!,UIImage(named: "res-2")!,UIImage(named: "res-3")!]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantTableViewCell", for: indexPath) as! RestaurantTableViewCell
cell.configureImageButton(with: arrImg[indexPath.row])
cell.btnResturant.contentMode = .scaleAspectFill
cell.configureLabel(with: arrRes[indexPath.row])
cell.layoutIfNeeded()
cell.selectionStyle = .none
cell.layoutIfNeeded()
return cell
}
Found the answer to my question. The syntax in TableView.swift was wrong. This may have been due to a Swift update.
Was:
imageButton.setImage(image, for: .normal)
Should be:
imageButton.setBackgroundImage(image, for: .normal)

swift: uitableview populated from a local json file stutters heavily while scrolling

When the button is tapped to segue to the tableview, it takes about 5 seconds for it to segue. After it finally segues, when the tableview scrolls, it stutters and sometimes crashes. The tableview is populated from a local json file and references local images. The images are optimized to low sizes. What is causing this and how can I optimize/fix my code to stop this from happening?
import UIKit
class PDList: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var pdTableView: UITableView!
var pdArt = [Decode]()
override func viewDidLoad() {
super.viewDidLoad()
json()
pdTableView.delegate = self
pdTableView.dataSource = self
}
func json() {
let path = Bundle.main.path(forResource: "palmdesert", ofType: "json")
let url = URL(fileURLWithPath: path!)
do {
let data = try Data(contentsOf: url)
self.pdArt = try JSONDecoder().decode([Decode].self, from: data)
}
catch {}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pdArt.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "pdCell")
let image = UIImage(named: pdArt[indexPath.row].pic)
cell.textLabel?.text = pdArt[indexPath.row].art
cell.detailTextLabel?.text = pdArt[indexPath.row].artist.capitalized
cell.imageView?.image = image
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "pdDetail", sender: self)
self.pdTableView.deselectRow(at: indexPath, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? PDDetail {
destination.pdArt = pdArt[(pdTableView.indexPathForSelectedRow?.row)!]
}
}
#IBAction func done(sender: AnyObject) {
dismiss(animated: true, completion: nil)
}
}
JSON Example:
{
"art": "Agave",
"desc": "Agave by Michael Watling serves as the City's entry monument and is inspired by the imagery of the agave, a succulent native to the desert. The stone forms are representative of Palm Desert's many well-managed resources for survival and growth.",
"artist": "Michael Watling",
"lat": 33.7215,
"long": -116.362,
"pic": "test.png"
}
Time Profile:
Let me know if you need any other information. Any help is appreciated.
I built a sample UITableView project with an asset library of 20 large jpeg images of 4032 × 3024 to feed in via UIImage(named:) to my cells.
This was vastly overspec for an image view of 120 x 70 so a max requirement of 360 x 210.
The time profiler showed this for scrolling in an entirely new batch of 12 cells.
Roughly 54ms per cell (650/12). However even large JPGs take advantage of hardware decoding so not too bad.
A modern iWidget at 60Hz refresh gives you a maximum of 16ms per frame to refresh the cell. So I got stuttering.
Replacing the images with appropriately scaled images (360 x 210 ) gives this.
Roughly 14ms per cell. The table scrolls mostly smooth.
So the problem here is likely one of these things.
Your images are too large. Ensure your assets are a pixel size of 3x the size of the image view.
Your images are not PNG or JPEG and cannot be handed off to the hardware decoder or treated with pngcrush. Make sure your images are PNG or JPEG. e.g GIF would be bad.
You are using PNG when JPG might be more appropriate. Real world images would be best as JPG. Line art/solid colors would be best as PNG.
Dequeue cells instead of creating new one every time. Change cellForRow to the following:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("pdCell", forIndexPath: indexPath)
let image = UIImage(named: pdArt[indexPath.row].pic)
cell.textLabel?.text = pdArt[indexPath.row].art
cell.detailTextLabel?.text = pdArt[indexPath.row].artist.capitalized
cell.imageView?.image = image
return cell
}
My idea is to make a class or Struct something like this.
struct ObjectFromJSON {
var art = ""
var desc = ""
var artist = ""
var lat = 0.0
var long = 0.0
var pic = ""
}
where you decode the data create an array:
var decodedData = [ObjectFromJSON]()
then add all the decoded data to this array. If that will not help then try to add a CocoaPod called SwiftyJSON it makes JSON decoding a breeze and might help.
after decoding just set the info into the ObjectFromJSON object and append it to the decodedData array.
let decodedArt = json["art"].stringValue
let decodedDesc = json["desc"].stringValue
let decodedArtist = json["artist"].stringValue
let decodedLat = json["lat"].doubleValue
let decodedLong = json["long"].doubleValue
let decodedPic = json["pic"].stringValue
let newObject = ObjectFromJSON(art: decodedArt, desc: decodedDesc, artist: decodedArtist, lat: decodedLat, long: decodedLong, pic: decodedPic)
decodedData.append(newObject)
this is based on your JSON however if you have more than 1 object in that json than it makes sense to put them into an array and then decoding would look a little different for example if you number the objects than would be something like:
let decodedArt = json[0]["art"].stringValue
if you have multiple JSON files than the first solution should be fine

Applied image filter to images using core graphics in UITableView

I have used core graphics to display images in greyscale in tableview. But the table is scrolling very slowly.Is there any solution by which i can speed up the scrolling.
You may want to follow these steps:
let's say you have an array of images prepared in your viewDidLoad, called originalImages.
inside your viewDidLoad, you have to scroll all the originalImages applying to each image the gray scale filter and appending the new image inside a new array called grayScaleImages.
use such grayScaleImages as pivot for your UITableViewDataSource (and delegate).
A very rough example might be:
import UIKit
class ViewController:UIViewController, UITableViewDataSource {
var originalImages:[UIImage]?
var grayScaleImages:[UIImage]?
override func viewDidLoad() {
super.viewDidLoad()
// load original images
self.originalImages = [UIImage]()
//... load whatever here (eg: from bundle) ...
// load gray scale images
self.grayScaleImages = [UIImage]()
self.originalImages?.forEach { image in
let grayScaleImage = image.applyGrayScale()
self.grayScaleImages?.append(grayScaleImage)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.grayScaleImages?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "yourCellID", for: indexPath)
cell.imageView?.image = self.grayScaleImages?[indexPath.row]
return cell
}
}
extension UIImage {
func applyGrayScale() -> UIImage {
let filter = CIFilter(name: "CIPhotoEffectNoir")!
filter.setValue(CIImage(image: self), forKey: kCIInputImageKey)
let output = filter.outputImage!
let cgImage = CIContext(options: nil).createCGImage(output, from: output.extent)!
return UIImage(cgImage: cgImage, scale: scale, orientation: imageOrientation)
}
}

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.

how to load images fast during scrolling in swift 3?

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.

Resources