Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionView received layout attributes for a cell with an index path that does not exist: <NSIndexPath: 0x79fe0f20> {length = 2, path = 0 - 4}

Tags:

swift

When I load the first time my UICollectionView does not have any problem, Im using custom layouts and a serachbar, when I search some text it crashes throwing the exception that the cell with an index path that does not exist, Im using the search bar like the next code:

import UIKit
import AVFoundation

class CategoryViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UISearchBarDelegate {

    var ListArray : JSON! = []
    var SelectedIds: [Int] = []
    var SearchActive : Bool = false
    var SearchArray = [Int]()

    @IBOutlet weak var SearchCategories: UISearchBar!

    @IBOutlet weak var CategoryCollection: UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()

        if let layout = self.CategoryCollection.collectionViewLayout as? InterestsLayout {
            layout.delegate = self
        }

        self.CategoryCollection.backgroundColor = UIColor.clearColor()
        self.CategoryCollection.contentInset = UIEdgeInsets(top: 18, left: 3, bottom: 10, right: 3)

        // Search Delegate
        self.SearchCategories.delegate = self
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        self.LoadData()
    }

    func LoadData() {
        MUBService.categoriesList(self) { (categories_list) -> () in

            self.ListArray = categories_list
            self.CategoryCollection.reloadData()

            self.view.hideLoading()

        }
    }

    func dismissKeyboard() {
        view.endEditing(true)
        self.SearchCategories.showsCancelButton = false
    }

    func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
        self.SearchCategories.showsCancelButton = true
        self.SearchActive = true
    }

    func searchBarTextDidEndEditing(searchBar: UISearchBar) {
        self.SearchActive = false
    }

    func searchBarCancelButtonClicked(searchBar: UISearchBar) {
        self.SearchActive = false
        self.dismissKeyboard()
    }

    func searchBarSearchButtonClicked(searchBar: UISearchBar) {
        self.SearchActive = false
    }

    func searchBar(searchBar: UISearchBar, let textDidChange searchText: String) {
        self.SearchArray = []
        for (index, object) in self.ListArray["categories"] {
            let name = object["name"].string!
            if name.localizedStandardContainsString(searchText) == true {
                self.SearchArray.append(Int(index)!)
            }
        }


        if(self.SearchArray.count == 0){
            self.SearchActive = false;
        } else {
            self.SearchActive = true;
        }


        self.CategoryCollection.reloadData()
    }

}

extension CategoryViewController {


    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {

        self.CategoryCollection?.collectionViewLayout.invalidateLayout()
        return 1
    }

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        if(self.SearchActive && self.SearchArray.count > 0) {

            return self.SearchArray.count
        }

        return self.ListArray["categories"].count
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {


        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CategoryCell", forIndexPath: indexPath) as! CategoryCell

        let row = indexPath.row

        if(self.SearchActive && self.SearchArray.count > 0) {

            let category = self.ListArray["categories"][self.SearchArray[row]]
            cell.configureWithPhoto(category, selected: self.ListArray["selected"])
        }else{

            let category = self.ListArray["categories"][row]
            cell.configureWithPhoto(category, selected: self.ListArray["selected"])
        }
        return cell
    }

    func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {


        let cell = self.CategoryCollection.cellForItemAtIndexPath(indexPath) as! CategoryCell

        cell.changeBackGroundColor()
        if (cell.is_active == true){
            self.SelectedIds.append(cell.id)
        }else{
            self.SelectedIds.removeObject(cell.id)
        }

    }

    @IBAction func RegisterDidTouch(sender: AnyObject) {

        MUBService.setMyCategories(self.SelectedIds, view_controller: self) { (categories_selected) -> () in
            self.performSegueWithIdentifier("HomeTabBarFromCategoriesSegue", sender: self)

        }
    }
}

extension CategoryViewController : InterestsLayoutDelegate {
    // 1. Returns the photo height
    func collectionView(collectionView:UICollectionView, heightForPhotoAtIndexPath indexPath:NSIndexPath , withWidth width:CGFloat) -> CGFloat {

        var row = indexPath.row

        if(self.SearchActive && self.SearchArray.count > 0) {

            row = self.SearchArray[row]
        }

        let category = self.ListArray["categories"][row]
        let url = NSURL(string:category["image"].string!)
        let data = NSData(contentsOfURL:url!)
        let image = UIImage(data:data!)!
        let boundingRect =  CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT))
        let rect  = AVMakeRectWithAspectRatioInsideRect((image.size), boundingRect)

        return rect.size.height
    }

    // 2. Returns the annotation size based on the text
    func collectionView(collectionView: UICollectionView, heightForAnnotationAtIndexPath indexPath: NSIndexPath, withWidth width: CGFloat) -> CGFloat {

        let annotationPadding = CGFloat(4)
        let annotationHeaderHeight = CGFloat(17)

        var row = indexPath.row
        if(self.SearchActive && self.SearchArray.count > 0) {

            row = self.SearchArray[row]
        }
        let category = self.ListArray["categories"][row]

        let font = UIFont(name: "AvenirNext-Regular", size: 10)!
        let rect = NSString(string: category["name"].string!).boundingRectWithSize(CGSize(width: width, height: CGFloat(MAXFLOAT)), options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
        let commentHeight = ceil(rect.height)
        var height = annotationPadding + annotationHeaderHeight + commentHeight + annotationPadding
        if (height != 70){
            height = 70
        }
        return 70
    }
}

I don't understand what is happening, thanks a lot for your help

like image 960
Alan Campos Avatar asked Feb 25 '16 22:02

Alan Campos


1 Answers

I've faced the same problem. Here an explanation: if you use a custom collectionViewLayout and you have a cache for layout attributes (best practice so you don't have to calculate every time attributes), you have to override invalidateLayout method in your custom layout class and purge your cache.

Here's my layout attributes array

private var cache = [UICollectionViewLayoutAttributes]()

Here the overrided method

    override func invalidateLayout() {
    super.invalidateLayout()
    cache.removeAll()
}

I call layout invalidation in my textDidChange delegate

    func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
    if searchText.characters.count > 0 {
        // search and reload data source
        self.searchBarActive    = true
        self.filterContentForSearchText(searchText)
        self.collectionView?.collectionViewLayout.invalidateLayout()
        self.collectionView?.reloadData()
    }else{
        self.searchBarActive = false
        self.collectionView?.collectionViewLayout.invalidateLayout()
        self.collectionView?.reloadData()
    }
}
like image 73
dpizzuto Avatar answered Oct 03 '22 10:10

dpizzuto