0

I am trying to query images from parse, and when I open my app everything works correctly, but if I try and refresh I get this crash shown below...

enter image description here

Not too sure what is causing this...To explain a bit about how I have things set up :

I have a tableView with 1 cell, in that cell are three imageView connected to a collection Outlet. Then I am getting my images from parse and placing them in my imageViews, then in the numberOfRowsInSection I divide it by 3 so it doesn't repeat the image 3 times...!

Here's my code below:

    var countries = [PFObject]()



    override func viewDidLoad() {
        super.viewDidLoad()

       loadPosts()


        // Do any additional setup after loading the view.
    }

 func loadPosts() {
        PFUser.query()
        // Build a parse query object
        let query = PFQuery(className:"Post")
        query.whereKey("user", equalTo: PFUser.currentUser()!)
        query.orderByDescending("createdAt")


        // Fetch data from the parse platform
        query.findObjectsInBackgroundWithBlock {
            (objects: [PFObject]?, error: NSError?) -> Void in

            // The find succeeded now rocess the found objects into the countries array
            if error == nil {

                self.countries.removeAll(keepCapacity: true)

                if let objects = objects {
                    self.countries = Array(objects.generate())
                }

                // reload our data into the collection view
               self.tableView.reloadData()

            } else {
                // Log details of the failure
                print("Error: \(error!) \(error!.userInfo)")
            }
        }


    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {

       return countries.count / 3

    }

    var counter = 0
     override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
     {

      let cell = tableView.dequeueReusableCellWithIdentifier("cell2") as! bdbTableViewCell


        for imageView in cell.threeImages {
            let placeHolder = UIImage(named: "camera")
             imageView.image = placeHolder

            let finalImage = countries[counter++]["imageFile"]
            finalImage!.getDataInBackgroundWithBlock {
                (imageData: NSData?, error: NSError?) -> Void in
                if error == nil {
                    if let imageData = imageData {
                        imageView.image = UIImage(data:imageData)
                    }
                }
            }}
            return cell
    }
  • You should also learn how to debug. Your code works the first time mainly because of luck, but it really won't work if you refresh, or as you scroll and cells go off the screen and back on. You use a class variable for counter that will simply increment by 3 every time a cell is displayed or re-displayed. This will cause it to be larger than the array of images. The images you load for the cell should be based on the indexPath that is passed into the method, not a counter that is never reset. See my answer below for more details. – wottle Feb 29 '16 at 22:00

3 Answers3

-1
var counter = 0 

should be reset before function

cellForRowAtIndexPath
m.ding
  • 3,172
  • 19
  • 27
  • How would I reset it before? if I put it inside of cellForRow, the images aren't changing! It displays 3 different images in the first row and them it repeats that same row as many times of the images... –  Feb 29 '16 at 21:48
  • @Jack before you call refresh function? – m.ding Feb 29 '16 at 21:50
  • But how would I reset it to 0? –  Feb 29 '16 at 21:51
  • @Jack before self.tableView.reloadData() you could set counter = 0? – m.ding Feb 29 '16 at 21:51
  • @Jack what is the scope of the counter variable, is it still global or local in function? and if possible, trying to print the countries size and content before accessing the array? – m.ding Feb 29 '16 at 22:00
  • Basically without the counter, 1 image is displayed 3 times per row. If I add the counter inside of the CellForRow, it then only changes the first row and repeats that. But when I add it outside of the cellForRow it changes them all and displays them correctly! Only anoying thing is, is that it crashes if the tableview gets reloaded...! –  Feb 29 '16 at 22:02
  • Using a counter is not the right solution. You should be determining the images based on the index row for the cell you are building. Resetting the counter will fix the crash, but it doesn't correct the underlying problem with your logic. – wottle Feb 29 '16 at 22:04
-1

Problem

You declared counter variable as static. That's why it is not being reset between cellForRowAtIndexPath calls but it gets incremented each time the method is invoked.

Solution:

Move counter declaration to the function body. Like below:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
     
    var counter = 0

    let cell = tableView.dequeueReusableCellWithIdentifier("cell2") as! bdbTableViewCell

        for imageView in cell.threeImages {
            let placeHolder = UIImage(named: "camera")
             imageView.image = placeHolder

            let finalImage = countries[counter++]["imageFile"]
            finalImage!.getDataInBackgroundWithBlock {
                (imageData: NSData?, error: NSError?) -> Void in
                if error == nil {
                    if let imageData = imageData {
                        imageView.image = UIImage(data:imageData)
                    }
                }
            }}
            return cell
    }
Community
  • 1
  • 1
Rafał Sroka
  • 39,540
  • 23
  • 113
  • 143
  • Hey thanks for that, but when I put it inside of cellForRow, the images aren't changing! It displays 3 different images in the first row and them it repeats that same row as many times of the images... –  Feb 29 '16 at 21:48
  • If you wanna have just one cell as you wrote in the question, return `1` in `numberOfRowsInSection`. – Rafał Sroka Feb 29 '16 at 21:53
  • I don't just want 1 cell, the images can be any number but they need to be different for each imageView...If I put the counter outside the cellforRow it works but if I update it crashes...And if I put it in the cellForRow it only changes the first 3 and repeats them for all the images –  Feb 29 '16 at 21:55
  • That's because you will constantly get the first 3 images in your array. What you want is the 3 images for the country. So it the cell row is 2, you want images with indexes 3,4,5. For cell row 3, you want images at indexed 6,7,8. And so on. See my answer for the correct solution. – wottle Feb 29 '16 at 22:03
-1

If I understand your data, need to move to the spot in the array that has your country's first image, then iterate through the next 3 images.

Basically, you should move the counter declaration inside the function body and initialize it with the location of the first image. See the method definition below for the full implementation.

Your code works the first time by shear luck. Basically iOS will request the cell at row 1, and your counter will increment itself by getting images at index 0,1,and 2. Then iOS will request the cell at row 2, and it will pull images 3,4,5. The counter will keep incrementing. Then, when you refresh the table, the counter is still set to a number higher than the size of your images array, so you get a crash. You would also display image incorrectly if iOS for whatever reason asked for the cells in a different order. You need to use the indexPath.row parameter to get the right images.

Your code also does not account for additional images after the end of a multiple of 3. So if there are 17 images ,images 16 1nd 17 will not be given a row in the tableview. To correct that, add the following function:

func roundUp(value: Double) -> Int {
    if value == Double(Int(value)) {
        return Int(value)
    } else if value < 0 {
        return Int(value)
    } else {
        return Int(value) + 1
    }
}

Then change the tableView's numberOfRowsInSection method to the following:

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
    return roundUp(Double(countries.count / 3.0))
}

Finally, in your cellForRowAtIndexPath function, you will have to handle potentially trying to look for an image past the size of your array:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
    var imageIndex = (indexPath.row * 3)

    let cell = tableView.dequeueReusableCellWithIdentifier("cell2") as! bdbTableViewCell

    for imageView in cell.threeImages {
        let placeHolder = UIImage(named: "camera")
        imageView.image = placeHolder

        if imageIndex < countries.count
        {
            let finalImage = countries[imageIndex++]["imageFile"]
            finalImage!.getDataInBackgroundWithBlock {
                (imageData: NSData?, error: NSError?) -> Void in
                if error == nil {
                    if let imageData = imageData {
                        imageView.image = UIImage(data:imageData)
                    }
                }
            }
        }
    return cell
}

I believe this will fix all of your problems. I do suggest you take a tutorial on how UITableView works, as well as how to step through your own code to troubleshoot. That way you can understand why your original attempt wouldn't work.

wottle
  • 13,095
  • 4
  • 27
  • 68
  • Interesting method, Not sure I understand it much.. I would usually use a colelctionView for this type of thing, but as I cannot for design reasons another user helped me set this counter thing up! So to be honest I don't really understand how its working fully....I'll give your answer a go now. –  Feb 29 '16 at 22:04
  • I would suggest you learn how to use breakpoints so you can see what your code is doing when the tableview loads. Being able to step through your code will give you insights into what is happening that will allow you to figure these things out on your own. (http://jeffreysambells.com/2014/01/14/using-breakpoints-in-xcode) – wottle Feb 29 '16 at 22:09
  • Spot on! Do you know by any chance how I can display for example 16, Because at the moment it only shows the images if they're divisible by 3... –  Feb 29 '16 at 22:15
  • So basically if they're 15 it shows 5 rows...And if its 16 or 17 it will still show 5 untill it reaches 18 and displays 6...If you do I'll open a new question –  Feb 29 '16 at 22:16
  • In your numberOfRows method, use return ceil(countries.count / 3) – wottle Feb 29 '16 at 22:19
  • Basically, you need to add an extra row when the number of countries / 3 is not around integer. The ceiling function should give you a 6th cell in your table when you have 16 or 17 images. What is not working with the ceiling function? crashes? still no extra row? – wottle Feb 29 '16 at 22:24
  • I get ambiguous reference to member ceil when adding it to the numerOf Rows –  Feb 29 '16 at 22:27
  • I'm not using Swift much, so it must be related to that. Try adding the method here (http://stackoverflow.com/a/24182116/3708242) and then change it to `roundUp(countries.count / 3)` – wottle Feb 29 '16 at 22:31
  • So add that whole func, and then roundUp(countries.count / 3)? –  Feb 29 '16 at 22:41
  • Hmm, I'm getting these errors http://i.stack.imgur.com/wuO6P.png I'm sorry not very familiar with these terms! –  Feb 29 '16 at 22:45
  • Change the first line of the method to `if value == Double(Int(value)) {` and change the call to the method to `roundUp( Double(countries.count / 3) )` This will cast these variables to the correct type and you should get what you want. Now looking at your code, you will get an index out of bounds exception I will update the answer to provide details that should get it to work. – wottle Mar 01 '16 at 16:07
  • Hello thanks for your help again, I tried everything mentioned above. But when I add the "-1" I get the same error as I mentioned at the beginning, then if I remove it, no update is made it's the same as before. –  Mar 01 '16 at 17:57
  • You're correct, there should not be a -1 in there. So what is the behavior you are seeind now? Is the last row not showing if it is not a multiple of 3? – wottle Mar 01 '16 at 19:08
  • You'll need to put a breakpoint at the `numberOfRowsInSection` and see that it is returning 5 for 13,14,15 images, 6 for 16, 17, and 18. If not, there is something in the roundUp function that isn't right. I tested it and it is working for me. If you are getting the right number of cells, but you are not seeing the cells in the app, something else is going on. At this point, without your project, I can't help you further because I cannot run the exact code locally without all your custom cells and other classes. If you need more help, I would suggest opening another questions. – wottle Mar 01 '16 at 20:02
  • I tried your reccomendation, but couldn't seem to figure it out...After playing around with different ways in the last couple of hour's I cannot seem to fix it...So I have posted another question: http://stackoverflow.com/questions/35733756/numberofrowsinsection-3-only-returning-15-images-and-not-16-or-17-as-theyre –  Mar 01 '16 at 21:22
  • Fixed it! all I needed was 3.0!! –  Mar 01 '16 at 21:35