Search code examples
swiftuicollectionviewuicollectionviewcelluisearchcontroller

Implement UISearchBarController within UICollectionViewController (Swift 4)?


I'm trying to implement an UISearchBarController in UICollectionViewController, here are the codes:

    class FirstViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UISearchResultsUpdating {

         var titles: [String] = []

         var card: [RecommendArticles]?

         var searchArr = [String](){
            didSet {
               self.collectionView?.reloadData()
        }
    }

        func updateSearchResults(for searchController: UISearchController) {

            guard let searchText = searchController.searchBar.text else {
                return
            }

            searchArr = titles.filter { (title) -> Bool in
                return title.contains(searchText)
            }


        }


        let searchController = UISearchController(searchResultsController: nil)

       override func viewDidLoad() {

        super.viewDidLoad()

        searchController.searchResultsUpdater = self
        searchController.dimsBackgroundDuringPresentation = false
        navigationItem.hidesSearchBarWhenScrolling = true
        self.navigationItem.searchController = searchController
 }


    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

        if (searchController.isActive) {
            return searchArr.count
        } else {
             return self.counters
        }

    }

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: verticalCellId, for: indexPath) as! VerticalCellId
        if (searchController.isActive) {
            cell.titleLabel.text = searchArr[indexPath.row]
            return cell
        } else {
            if let link = self.card?[indexPath.item]._imageURL {
                let url = URL(string: link)
                cell.photoImageView.kf.setImage(with: url)
            }

            if let title = self.card?[indexPath.item]._title {
                cell.titleLabel.text = title
                titles.append(title)
                print("\(titles)")
            }

            if let source = self.card?[indexPath.item]._source {
                cell.sourceLabel.text = source
            }

            return cell

        }




    }

Here are the errors locate:

                titles.append(title)
                print("\(titles)")

When I search a keyword, the filtered results are incorrect, because the collection view cell will change dynamically (I'm not sure if my expression is accurate).

But, if I set up the variable titles like this, it works perfectly:

var titles = ["Tom","Jack","Lily"]

But I have to retrieve the values of titles from internet. Any suggestions would be appreciated.

Here are the update question for creating an array after self.card:

override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)

        self.view.addSubview(activityView)
        activityView.hidesWhenStopped = true
        activityView.center = self.view.center
        activityView.startAnimating()

        let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default()
        dynamoDbObjectMapper.load(TheUserIds2018.self, hashKey: "TheUserIds2018", rangeKey: nil, completionHandler: { (objectModel: AWSDynamoDBObjectModel?, error: Error?) -> Void in
            if let error = error {
                print("Amazon DynamoDB Read Error: \(error)")
                return
            }

            DispatchQueue.main.async {
                if let count = objectModel?.dictionaryValue["_articleCounters"] {
                    self.counters = count as! Int
                    self.collectionView?.reloadData()
                    self.updateItems()
                    self.activityView.stopAnimating()
                }

            }

        })

    }



 func updateItems() {
        let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default()
        var tasksList = Array<AWSTask<AnyObject>>()
        for i in 1...self.counters {
            tasksList.append(dynamoDbObjectMapper.load(RecommendArticles.self, hashKey: "userId" + String(self.counters + 1 - i), rangeKey: nil))
        }
        AWSTask<AnyObject>.init(forCompletionOfAllTasksWithResults: tasksList).continueWith { (task) -> Any? in
            if let cards = task.result as? [RecommendArticles] {
                self.card = cards
                DispatchQueue.main.async {
                if let totalCounts = self.card?.count {
                    for item in 0...totalCounts - 1 {
                        if let title = self.card?[item]._title {
                            self.newTitle = title
                        }
                        self.titles.append(self.newTitle)
                        print("titles: \(self.titles)")
                    }
                }
        }


            } else if let error = task.error {
                print(error.localizedDescription)
            }

            return nil

        }

    }

Solution

  • This is not an issue with UISearchController . it is with your data. Collection view data source method -

    cellForItemAtIndexPath works only when cell is presented in the view. So you can't keep the code for creating array of titles in the cellForItemAtIndexPath

    Instead of separately keeping title strings in array for search you can directly filter self.card array with search text in title with below code

    self.searchArr = self.card?.filter(NSPredicate(format:"_title CONTAINS[cd] %@",searchText)).sorted(byKeyPath: "_title", ascending: true)
    

    or

    you can create array of titles before loading collection view Some where after you create self.card