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
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()
    }
}
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With