1

I am trying to create a program which dynamically updates the collectoinView as the user enters text into the UITextField. Below is the extension I have created to achieve this

I have updated code based on your suggestions to include insert and delete statements,I am still in the process of refactoring and adding delegation but while the code doesn't result in an error it doesn't allow the user to keep typing and closes the keyboard have I implemented everything correctly

Update #2 (Thank you for all of your Help)

import UIKit

class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate, UISearchBarDelegate {

    lazy var collectionView : UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        cv.backgroundColor = .white
        cv.delegate = self
        cv.dataSource = self
        return cv
    }()

    var genericTagsArray:[String] = ["tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9","tag10","tag11","tag12","A","B","C","D","E","F","G","Ab","Abc","za","tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9","tag10","tag11","tag12","A","B","C","D","E","F","G","Ab","Abc","za"]

    var currentTagsArray:[String] = [String]() {
        didSet {
            collectionView.reloadData()
        }
    }



    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.addSubview(collectionView)
        collectionView.register(Cell.self, forCellWithReuseIdentifier: "cell")
        collectionView.register(Header.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "header")
        collectionView.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: self.view.leadingAnchor, bottom: self.view.bottomAnchor, trailing: self.view.trailingAnchor, padding: .init(top: 30, left: 0, bottom: 30, right: 0))
        collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)


    }


    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return currentTagsArray.count
    }


    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell
        cell.label.text = currentTagsArray[indexPath.item]
        return cell
    }

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "header", for: indexPath) as! Header
        header.searchBar.delegate = self
        return header
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        return CGSize(width: collectionView.contentSize.width, height: 75)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

        return CGSize(width: self.view.frame.size.width, height: 50)

    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 5
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 5
    }

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        self.currentTagsArray = self.genericTagsArray.filter { (text) -> Bool in
            return text.contains(searchText.lowercased())
        }
    }
}

class Cell : UICollectionViewCell {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setupViews() {
        self.backgroundColor = .gray
        self.addSubview(label)
        label.anchor(top: self.topAnchor, leading: self.leadingAnchor, bottom: self.bottomAnchor, trailing: self.trailingAnchor)
    }

}

class Header: UICollectionReusableView{

    var searchBar = UISearchBar(frame: .zero)

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setupViews() {
        self.backgroundColor = .gray
        self.addSubview(searchBar)
        searchBar.anchor(top: self.topAnchor, leading: self.leadingAnchor, bottom: nil, trailing: self.trailingAnchor, padding: .init(top: 0, left: 5, bottom: 0, right: 5), size: .init(width: 0, height: 50))
    }
}

extension UIView {
    func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero) {
        translatesAutoresizingMaskIntoConstraints = false

        if let top = top {
            topAnchor.constraint(equalTo: top, constant: padding.top).isActive = true
        }

        if let leading = leading {
            leadingAnchor.constraint(equalTo: leading, constant: padding.left).isActive = true
        }

        if let bottom = bottom {
            bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom).isActive = true
        }

        if let trailing = trailing {
            trailingAnchor.constraint(equalTo: trailing, constant: -padding.right).isActive = true
        }

        if size.width != 0 {
            widthAnchor.constraint(equalToConstant: size.width).isActive = true
        }

        if size.height != 0 {
            heightAnchor.constraint(equalToConstant: size.height).isActive = true
        }
    }
}

Updated Function With Edits

    @objc func textFieldDidChange(){
        guard(!(feedSearchBar.text?.isEmpty)!) else{
            VC.currentTagsArray = VC.genericTagsArray
            VC.feedScreenCollectionView.reloadData()
            return
        }
        VC.currentTagsArray = VC.genericTagsArray.filter({letter -> Bool in
            if feedSearchBar.text!.count > letter.count{
                return false
            }
            let stringRange = letter.index(letter.startIndex, offsetBy: feedSearchBar.text!.count)
            let subword = letter[..<stringRange]
            return subword.lowercased().contains(feedSearchBar.text!.lowercased())
        })

        if VC.currentTagsArray.isEmpty{
            VC.feedScreenCollectionView.deleteItems()
            VC.currentTagsArray.insert(feedSearchBar.text!, at: 0)           
            VC.feedScreenCollectionView.insertItems(at: [IndexPath(item: 0, section: 0)])
            //VC.feedScreenCollectionView.reloadItems(inSection: 0)           
           // VC.feedScreenCollectionView.reloadItems(at: [IndexPath(item: 0, section: 0)])
        }
      //VC.feedScreenCollectionView.reloadData()
     VC.feedScreenCollectionView.reloadItems(inSection: 0)
   }

extension UICollectionView{
    func reloadItems(inSection section:Int = 0) {
        var indicies:[IndexPath] = [IndexPath]()
        print("current number of items to reload \(self.numberOfItems(inSection: section))")
        for i in 0..<self.numberOfItems(inSection: section){
        indicies.append(IndexPath(item: i, section: section))
            print("current item number: \(i)")
        }
        self.reloadItems(at: indicies)
       // self.insertItems(at: indicies)
    }

    func deleteItems(inSection section:Int = 0){
        var indicies:[IndexPath] = [IndexPath]()
        for i in 0..<self.numberOfItems(inSection: section){
            indicies.append(IndexPath(item: i, section: section))
        }
        self.deleteItems(at: indicies)
    }
}

Original Function:

extension UICollectionView{
        func reloadItems(inSection section:Int = 0) {
            var indicies:[IndexPath] = [IndexPath]()
            for i in 0..<self.numberOfItems(inSection: section){
            indicies.append(IndexPath(item: i, section: section))
            }
            self.reloadItems(at: indicies)
        }
    }

However when ever I call this function after the textField has been edited the program crashes with the error

* Assertion failure in -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3698.54.4/UICollectionView.m:5867 2018-07-18 16:28:02.226606-0400 FeedScreenReuseableView[87752:9704142] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert item 6 into section 0, but there are only 6 items in section 0 after the update'

I am not sure how to fix this. Is there a better way to go about reloading cells that does not affect the header. Thank you for all of your help in advance. Below is the code from the project in its entirety.

import UIKit

class ViewController: UIViewController,UICollectionViewDelegateFlowLayout,UICollectionViewDelegate,UICollectionViewDataSource,printDelegateWorkedDelegate,updateCollectionView{

    func updateCollectionView() {
        self.feedScreenCollectionView.reloadData()
    }

    func printDelegateWorkedDelegate() {
        print("The delegate worked")
    }

    var genericTagsArray:[String] = ["tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9","tag10","tag11","tag12","A","B","C","D","E","F","G","Ab","Abc","za","tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9","tag10","tag11","tag12","A","B","C","D","E","F","G","Ab","Abc","za"]
    var currentTagsArray:[String] = [String]()
    var tagsSelected:[String] = [String]()
    let keyboardSlider = KeyboardSlider()

    var header:feedViewHeader = feedViewHeader()

    @IBOutlet weak var feedScreenCollectionView: UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()
        keyboardSlider.subscribeToKeyboardNotifications(view: view)
        currentTagsArray = genericTagsArray

        let viewTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(feedViewHeader.viewTapped(gestureRecognizer:)))
        viewTapGestureRecognizer.cancelsTouchesInView = false
        self.feedScreenCollectionView.addGestureRecognizer(viewTapGestureRecognizer)

        feedScreenCollectionView.delegate = self
        //
        feedScreenCollectionView.dataSource = self
        //

        let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
        layout.sectionInset = UIEdgeInsets(top: 5, left: 1, bottom: 0, right: 1)
        layout.minimumLineSpacing = 0
        layout.headerReferenceSize = CGSize(width: 50, height: 75)

        layout.sectionHeadersPinToVisibleBounds = true
        feedScreenCollectionView.collectionViewLayout = layout
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
            return CGSize(width: 50, height: 75)
    }
    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
                header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "feedViewHeader", for: indexPath) as! feedViewHeader
            header.VC = self
        return header

    }
    //


    //Data Source
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return currentTagsArray.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "feedViewCell", for: indexPath) as! feedViewCell
        cell.feedImageView.backgroundColor = .blue
        cell.feedImageView.clipsToBounds = true
        cell.feedImageView.layer.cornerRadius = CGFloat((cell.feedImageView.frame.width)/5)
        cell.feedLabel.text = currentTagsArray[indexPath.item]
        return cell
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 0
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat{
        return 0
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let collectionViewWidth = collectionView.bounds.width/4.0
        let collectionViewHeight = collectionViewWidth
        return CGSize(width: collectionViewWidth-4, height: collectionViewHeight+25)
    }
    var lastContentOffset:CGFloat = 0

    func scrollViewDidScroll(_ scrollView: UIScrollView) {

        if self.lastContentOffset > self.feedScreenCollectionView.contentOffset.y && self.feedScreenCollectionView.contentOffset.y > 0 && self.feedScreenCollectionView.contentOffset.y < self.feedScreenCollectionView.frame.maxY {
            self.lastContentOffset = scrollView.contentOffset.y
            header.isHidden = false
        }
        else if (self.lastContentOffset < self.feedScreenCollectionView.contentOffset.y) && (self.feedScreenCollectionView.contentOffset.y < self.feedScreenCollectionView.frame.maxY) && (self.feedScreenCollectionView.contentOffset.y > 0)  {
            print("you scrolled down,content offSet: \(scrollView.contentOffset.y)->\(self.feedScreenCollectionView.contentOffset.y)")
            header.isHidden = true
        }
        else{
            self.lastContentOffset = scrollView.contentOffset.y
            print("content offSet: \(scrollView.contentOffset.y)")
            print("Nothing happened")
            //  self.headerDelegate?.hideHeaderView(hide: true)
        }


    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardFrameChangeNotification(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
    }

    var offsetY:CGFloat = 0

    @objc func keyboardFrameChangeNotification(notification: Notification) {
    }


}


class feedViewCell:UICollectionViewCell{

    @IBOutlet weak var feedImageView: UIImageView!
    @IBOutlet weak var feedLabel: UILabel!

    let keyboardSlider = KeyboardSlider()

    override func awakeFromNib() {
        super.awakeFromNib()
        feedLabel.translatesAutoresizingMaskIntoConstraints = false
        feedImageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        feedImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
        feedImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
        feedImageView.bottomAnchor.constraint(equalTo: self.feedLabel.topAnchor).isActive = true

        feedImageView.translatesAutoresizingMaskIntoConstraints = false
        feedLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
        feedLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
        feedLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        feedLabel.topAnchor.constraint(equalTo: self.feedImageView.bottomAnchor).isActive = true
        feedLabel.textAlignment = .center

    }

}




class feedViewHeader:UICollectionReusableView,UITextFieldDelegate,UICollectionViewDelegate{

    @IBOutlet weak var feedSearchBar: UITextField!

    var delegateWorked:printDelegateWorkedDelegate?
    var updateCV:updateCollectionView?


    var VC:ViewController!
    var collectionView:UICollectionView?
    var stringToBeSet = "String to be set"

    override func awakeFromNib() {
        super.awakeFromNib()

        feedSearchBar.delegate = self
        feedSearchBar.autocorrectionType = .no
        feedSearchBar.keyboardType = .default
          feedSearchBar.addTarget(self, action: #selector(feedViewHeader.textFieldDidChange), for: .editingChanged)
        self.feedSearchBar.borderStyle = .roundedRect
        self.feedSearchBar.layer.borderColor = UIColor.black.cgColor
        self.feedSearchBar.layer.borderWidth = 4
        var searchBarHeight = self.feedSearchBar.bounds.height
        self.feedSearchBar.placeholder = "Tap To Search"
        self.feedSearchBar.returnKeyType = .search
        self.feedSearchBar.rightViewMode = .always
    }


    @objc func viewTapped(gestureRecognizer:UIGestureRecognizer){

        if  feedSearchBar.isFirstResponder{
            feedSearchBar.resignFirstResponder()
        }
    }

   func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
     VC.feedScreenCollectionView.reloadData()

    //VC.feedScreenCollectionView.reloadSections([0])
    return true
    }

    /// Helper to dismiss keyboard
    @objc func didStopEditing() {
    }

    func textFieldDidEndEditing(_ textField: UITextField) {
        UIView.setAnimationCurve(UIViewAnimationCurve.easeInOut)
        UIView.animate(withDuration: 0.2) {
            self.VC.view.frame.origin.y = 0
        }
    }
    @objc func textFieldDidChange(){
        guard(!(feedSearchBar.text?.isEmpty)!) else{
            VC.currentTagsArray = VC.genericTagsArray
            VC.feedScreenCollectionView.reloadData()
            return
        }

        VC.currentTagsArray = VC.genericTagsArray.filter({letter -> Bool in
           if feedSearchBar.text!.count > letter.count{
                return false
            }
            let stringRange = letter.index(letter.startIndex, offsetBy: feedSearchBar.text!.count)
            let subword = letter[..<stringRange]
            return subword.lowercased().contains(feedSearchBar.text!.lowercased())

        })

        if VC.currentTagsArray.isEmpty{
            VC.currentTagsArray.insert(feedSearchBar.text!, at: 0)
        }
    VC.feedScreenCollectionView.reloadItems(inSection: 0)
   }
}

extension Notification.Name{
    static let showKeyboard = Notification.Name("showKeyboard")
}
class KeyboardSlider: NSObject {
    // variables to hold and process information from the view using this class
    weak var view: UIView?

    @objc func keyboardWillShow(notification: NSNotification) {
        // method to move keyboard up
        // view?.frame.origin.y = 0 - getKeyboardHeight(notification as Notification)
        print("made it to keyboard will show")
    }

    func getKeyboardHeight(_ notification:Notification) -> CGFloat {
        // get exact height of keyboard on all devices and convert to float value to return for use
        let userInfo = notification.userInfo
        let keyboardSize = userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue
        return keyboardSize.cgRectValue.height
    }

    func subscribeToKeyboardNotifications(view: UIView) {
        // assigning view to class' counterpart
        self.view = view
        // when UIKeyboardWillShow do keyboardWillShow function
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: .UIKeyboardWillShow, object: nil)
    }

    func unsubscribeFromKeyboardNotifications() {
        NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil)
    }
}

class blankView:UICollectionReusableView{

}

extension UICollectionView{
    func reloadItems(inSection section:Int = 0) {
        print("made it to reload")
        for i in 0..<self.numberOfItems(inSection: section){
        self.reloadItems(at: [IndexPath(item: i, section: section)])
        }
    }
}
  • You basically have **one** section with multiple items, why don't you just call `reloadData()` over an `IBOutlet` to your collection view? I believe it should be `self.feedScreenCollectionView.reloadData()` – Alejandro Iván Jul 18 '18 at 21:42
  • @AlejandroIván Thank you for your response. I tried that initially but then it reloads the header along with the cells. Which causes the keyboard for the textField to close while the user is still typing, since `reloadData` is being called inside of the `@objc func textFieldDidChange() function`. – TheRedCamaro3.0 3.0 Jul 18 '18 at 21:46
  • 1
    If your `UITextField` should be visible all the time, I wouldn't add it to the header view of the collection, but rather at the same level of it (another view on the view controller). I'm not sure, but I think you can use something like `let indexSet = IndexSet(integer: 0); self.feedScreenCollectionView.reloadSections(indexSet)` to achieve what you want to do. – Alejandro Iván Jul 18 '18 at 21:49

2 Answers2

3

You are adding a new string to your VC.currentTagsArray but you aren't calling insertItems(at:) on your collection view, so when numberOfItemsInSection suddenly starts returning more items than it did previously you get the consistency exception.

A couple of other observations on style:

  • I would use delegation to pass events back to the view controller rather than having the header view and cells holding a reference to the VC and tightly coupling the objects.
  • the variable VC should be vc. Capital first letter is a class
  • Just call it currentTags not currentTagsArray
Paulw11
  • 108,386
  • 14
  • 159
  • 186
  • 1
    Thank you so much for your answer, I appreciate your feed back on the style as well as functionality. As a novice improving the style of the code for both functionality and readability has been something I have been striving to improve as I move onto bigger projects. Often I find that there are so many ways to do something in swift Im not sure which way is proper. – TheRedCamaro3.0 3.0 Jul 18 '18 at 22:08
  • Your solution to add insertItems(at:) makes sense but I still get the same error when I add `insertItems(at: [IndexPath(item: 0, section: 0)])` right below `currentTags.insert(at:)`. But I still get the same error am I implementing this function in the wrong place. Thank you very much again for all of your help . – TheRedCamaro3.0 3.0 Jul 18 '18 at 22:11
  • The error I receive is like this "'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (1) must be equal to the number of items contained in that section before the update (44), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'" – TheRedCamaro3.0 3.0 Jul 18 '18 at 22:13
  • You need to look at why there were 44 items and now there is only 1. Remember that arrays are value types in Swift and are copied on modify, so you may be doing something like that. By refactoring your code so that onkynthe view controller is concerned with the tags you will be able to simplify your code and avoid issues. – Paulw11 Jul 18 '18 at 22:16
  • Calling `reloadItems` seems to refresh the headerView as well is there a way to just have `reloadItems` reload the items of the `collectionView` – TheRedCamaro3.0 3.0 Jul 19 '18 at 03:45
1

Your problem is because you are only calling insertItemsAt, which does exactly that, inserts an item. You are forgetting to deleteItemsAt, which should delete all the items that you don’t want to display any longer.

BTW you should really consider refactoring your code. It is not easy to read and things are not being done in the right place. For example, your header should never be the one in charge of updating your collection view. You should leave that to the controller itself and use delegation to get the text from the header up to the view controller.

Update

Here is an entire code that does the search for you. It is just an example so you can see how it can be done.

import UIKit

class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate, UISearchBarDelegate {

    lazy var collectionView : UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        cv.backgroundColor = .white
        cv.delegate = self
        cv.dataSource = self
        return cv
    }()

    var genericTagsArray:[String] = ["tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9","tag10","tag11","tag12","A","B","C","D","E","F","G","Ab","Abc","za","tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9","tag10","tag11","tag12","A","B","C","D","E","F","G","Ab","Abc","za"]

    var currentTagsArray:[String] = [String]() {
        didSet {
            collectionView.reloadSections(IndexSet.init(integer: 1))
        }
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.addSubview(collectionView)
        collectionView.register(Cell.self, forCellWithReuseIdentifier: "cell")
        collectionView.register(Header.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "header")
        collectionView.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: self.view.leadingAnchor, bottom: self.view.bottomAnchor, trailing: self.view.trailingAnchor, padding: .init(top: 0, left: 0, bottom: 0, right: 0))
        collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)


    }


    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        if section == 0 { return 0 }
        return currentTagsArray.count
    }

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 2
    }

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "header", for: indexPath) as! Header
        header.searchBar.delegate = self
        return header
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        if section == 0 {
            return CGSize(width: self.view.frame.width, height: 50)
        }
        return CGSize()
    }


    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell
        cell.label.text = currentTagsArray[indexPath.item]
        return cell
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

        return CGSize(width: self.view.frame.size.width, height: 50)

    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 5
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 5
    }

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        self.currentTagsArray = self.genericTagsArray.filter { (text) -> Bool in
            return text.contains(searchText.lowercased())
        }
    }
}

class Cell : UICollectionViewCell {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setupViews() {
        self.backgroundColor = .gray
        self.addSubview(label)
        label.anchor(top: self.topAnchor, leading: self.leadingAnchor, bottom: self.bottomAnchor, trailing: self.trailingAnchor)
    }

}

class Header : UICollectionViewCell {

    let searchBar = UISearchBar()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setupViews() {
        self.backgroundColor = .gray
        self.addSubview(searchBar)
        searchBar.anchor(top: self.topAnchor, leading: self.leadingAnchor, bottom: self.bottomAnchor, trailing: self.trailingAnchor)
    }

}

extension UIView {
    func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero) {
        translatesAutoresizingMaskIntoConstraints = false

        if let top = top {
            topAnchor.constraint(equalTo: top, constant: padding.top).isActive = true
        }

        if let leading = leading {
            leadingAnchor.constraint(equalTo: leading, constant: padding.left).isActive = true
        }

        if let bottom = bottom {
            bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom).isActive = true
        }

        if let trailing = trailing {
            trailingAnchor.constraint(equalTo: trailing, constant: -padding.right).isActive = true
        }

        if size.width != 0 {
            widthAnchor.constraint(equalToConstant: size.width).isActive = true
        }

        if size.height != 0 {
            heightAnchor.constraint(equalToConstant: size.height).isActive = true
        }
    }
}
Galo Torres Sevilla
  • 1,540
  • 2
  • 8
  • 15
  • Thank you for your help, I apologize that the code is a bit messy. I wasn't really sure how to combine the text from the textField and the dataSource for the collectionView. Thank you reading through it all. I tried to do the insert right after the array does the insert,Im not sure if that is the correct placement where would I put the delete should I delete all the cells before inserting into the array and inserting? – TheRedCamaro3.0 3.0 Jul 19 '18 at 03:13
  • It seems that when I call `reloadItems(at: )` after performing the delete and insert of items the header is reloaded as well collapsing the keyboard from the textField and forcing the user to tap the textField to continue typing. Is there a way to reload the items using `reloadItems(at: )` without reloading the header as well? – TheRedCamaro3.0 3.0 Jul 19 '18 at 03:55
  • Can you update your question with the your new code? So we can see where and how you are doing it. – Galo Torres Sevilla Jul 19 '18 at 07:19
  • Thank you for all of your help I have added the modified functions containing `deleteItems()`, `insertItems(at:)` and the modified extension to UICollectionView – TheRedCamaro3.0 3.0 Jul 19 '18 at 14:53
  • The problem is that I really don't understand why you are trying to make something so complicated work. There are several other options that you can use to create a search. See my edits. – Galo Torres Sevilla Jul 19 '18 at 17:09
  • Thank you for taking the time to create such a detailed answer and for having the patience to help solve this. I am trying to keep the searchBar in the header of the CollectionView so that as the user scrolls the searchBar slides up along with the cells. I have tried adding a header to the updated code you posted but it closes the keyboard after the user types each character. I have posted a second update containing your code to demonstrate. – TheRedCamaro3.0 3.0 Jul 19 '18 at 21:27
  • Got you. Ok. Check the Update code again. This should do it for you – Galo Torres Sevilla Jul 19 '18 at 22:03
  • Thank you so much for all of your help, I am very appreciative of all of your effort to help me. This is a very creative solution but yet so simple at the same time it is truly innovative. In order to make use of `sectionHeadersPinToVisibleBounds` would it be possible to have the header and the cells contained in the same section and still have a smooth reload? – TheRedCamaro3.0 3.0 Jul 20 '18 at 05:52