Swift Collection View: Phase 6/9

So far, you learned how to implement these features of the UICollectionView class:

Today you’ll learn how to implement these features in the SwiftCollectionView application:

  • Search the collection view by typing text in a Search Bar’s text field.
  • Dismiss the keyboard by tapping the Search Bar’s Cancel button.
  • Dismiss the keyboard by double-tapping the collection view’s background.

Take a look at this QuickTime video, it show results of implementing the search bar and keyboard dismissal feature in the SwiftCollectionView application.

Create a New Branch

Ok, start things off by launching the SwiftCollectionView project in Xcode. Use the Source Control menu to create a new branch. Call it search-collection-view.

Add New Scene on The Storyboard

Now, in order to implement the search feature in the project, you’ll have to add a new scene on the storyboard canvas and configure it so it look like the one shown in Figure 6-2 below. Start by clicking the Main.storyboard file to load it in Interface Builder. Now do the following:

  1. Delete the Collection View Controller scene and its Navigation Controller.
  2. Drag a View Controller from the Object Library and drop it on the storyboard canvas.
  3. Embed the View Controller scene in a Navigation Controller (Editor | Embed In | Navigation Controller).
  4. Click the Collection View scene’s Navigation Controller scene.
  5. In the Attributes inspector, tick the Is Initial View Controller checkbox. This make the Collection View scene the initial scene.
  6. Drag a Search Bar from the Object Library and drop it below the View Controller scene’ Navigation Item. Make sure it is the same width as the scene-see Figure 6-2 below.
  7. Click the Search Bar. Locate the Placeholder field in the Attributes Inspector and enter this text: Enter the photo name here
  8. Locate the Shows Cancel Button checkbox in the Attributes Inspector and tick it. This place a Cancel button on the Search Bar-see Figure 6-2 below.
  9. Drag a Collection View from the Object Library and drop it below the Search Bar. Make sure it cover remaining surface of the scene-see Figure 6-2 below.
  10. Click the Collection View Cell, locate the Identifier field in the Attributes Inspector, enter this text: Cell. Next, locate the Keyboard menu. It’s located in the Scroll View section. Select the Dismiss on drag option. This dismiss the keyboard and reset the collection view back its default mode.
  11. Control-click the Collection View Cell and drag resulting blue line to the Photo Detail scene’s Navigation Bar Scene. Release your mouse when the Navigation Controller scene is highlighted. Select present modally from the pop up menu.
  12. Click the segue you created for the Collection View Cell. Locate the Identifier field in the Attributes Inspector and enter this text: photoDetail
  13. Click the Collection View. In the Size Inspector, add sizes shown in Figure 6-1 below.
  14. Click the View Controller scene’s Navigation Item. Locate the Title field in the Attributes Inspector and enter this text: Photos
  15. Click the View Controller scene object, locate the Class menu in the Identity Inspector, and select MasterViewController option.
  16. Select the View Controller object and use the Auto Layout bar’s Issues button to add missing constraints to the default Size Class; which is wAny hAny.

That’s it you are done designing the new scene. Rearrange the scenes so they look like Figure 6-3.

swiftcv-figure1-8 swiftcv-figure6-1
Figure 6-1 Figure 6-2
swiftcv-figure6-2
Figure 6-3

The Search Bar and Collection View Connections

In Interface Builder, you have to do the following for the Search Bar and the Collection View you added on the Photos scene:

  1. Connect the Search Bar’s delegate outlet to the Photos scene object via the Connections Inspector.
  2. Create and connect an IBOutlet variable called searchBar via the Assistant editor.
  3. Connect the Collection View’s dataSource and delegate outlets via the Connections Inspector.
  4. Create and connect an IBOutlet variable called collectionView via the Assistant Editor.

Once you’ve perform above tasks, the Connections Inspector for the Photos scene’s Search Bar and the Collection View should look like these images:

swiftcv-figure6-3 swiftcv-figure6-4
Figure 6-4: Search Bar Connections Figure 6-5: Collection View Connections

The Master View Controller Class Code

That’s it; you are done with the Main.storyboard, so switch to the MasterViewController.swift file because you will be adding code in it to implement the collection view search feature and to dismiss the on-screen keyboard.

Start by scrolling to the end of the MasterViewController.swift file and add code shown below, just above the class file’s closing } bracket.

// MARK: UISearchBarDelegate functions

// This function is fired when the user tap the Search Bar's Cancel button
func searchBarCancelButtonClicked(searchBar: UISearchBar!) {
  searchBar.text = nil // Clear out the Search Bar's text field
  searchBar.showsCancelButton = false // Hide the Search Bar's Cancel button
  searchBar.resignFirstResponder()  // Dismiss the keyboard
  isSearchOn = false // Turn off search function
  self.collectionView.reloadData()  // Refresh the collection view
}

// this function is fired when the user start entering text in the Search Bar's text field
func searchBar(searchBar: UISearchBar!, textDidChange searchText: String!) {
  searchBar.showsCancelButton = true  // Show the Search Bar's Cancel button
  if !searchText.isEmpty {
    isSearchOn = true  // Turn on searching function
    self.filterContentForSearchText() // Search the collection view's dataSource
    self.collectionView.reloadData()
  }
}

Here are the helper functions for the UISearchBarDelegate functions. Add them in the MasterViewController.swift file.

// MARK: Helper functions for the UISearchBarDelegate functions

    // This function search the collection view's dataSource object (imageFileNames)for an item that match
    // text entered in the Search Bar's text field. If any items found, add them in the searchResults array
    func filterContentForSearchText() {
        // Remove all elements from the searchResults array
        searchResults.removeAll(keepCapacity: false)

        // Loop throught the collection view's dataSource object
        for imageFileName in imageFileNames {
            let stringToLookFor = imageFileName as NSString
            let sourceString = searchBar.text as NSString

            if stringToLookFor.localizedCaseInsensitiveContainsString(sourceString) {
                // Match found, so add it to the searchResults array variable
                searchResults.append(imageFileName)
            }
        }
    }

    // Target function for the tap gesture recognizer object created in the viewDidLoad function
    func collectionViewBackgroundTapped() {
        // Dismiss the keyboard that's shown on the device's screen
        searchBar.resignFirstResponder()
    }

    /***********************
    This function allow the default tap gesture added to the collection view cell,
    an the collection view's background to to work simultaneously
    **************************/
    func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOfGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    // Perform default tap gesture
    return true
  }
}

Now, scroll up to the top of the MasterViewController.swift and change its code to this:

import UIKit

let reuseIdentifier = "Cell"

class MasterViewController: UIViewController, UISearchBarDelegate, UIGestureRecognizerDelegate {

    @IBOutlet weak var editButton: UIBarButtonItem!
    @IBOutlet weak var searchBar: UISearchBar!
    @IBOutlet weak var collectionView: UICollectionView!
    var icon: PhotoCell!
    var imageFileNames = [String]()
    var selectedPhotoName = String()
    var isSearchOn = false
    var searchResults = [String]()

Modify the viewDidLoad function’s code to this:

override func viewDidLoad() {
  super.viewDidLoad()

  // Set the collection view's backgroundcolor to display an image
  self.collectionView!.backgroundColor = UIColor(patternImage: UIImage(named: "purty_wood")!)

  // Register the  class with the collection view
  self.collectionView!.registerClass(PhotoCell.self, forCellWithReuseIdentifier: reuseIdentifier)

  // Add objects in the collection view dataSource, imageFileNames Array
  loadImages()

  //1. Create a tap gesture recognizer object
  let tapRecognizer = UITapGestureRecognizer()
  tapRecognizer.numberOfTapsRequired = 2

  //2. Set the target function for the tap gesture recognizer object
  tapRecognizer.addTarget(self, action: "collectionViewBackgroundTapped")

  //3. Add the tap gesture recognizer to the collection view
  self.collectionView.addGestureRecognizer(tapRecognizer)
}

Scroll down to the collection view data source functions and change their code to those shown below:

func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
  // Return the number of sections
  return 1
}

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  // Return the number of items in the section
  // Return the number of items in a section (number of photos in an album)
  if isSearchOn == true && !searchResults.isEmpty {
    return searchResults.count
  } else {
    return imageFileNames.count
  }
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
  // Initialize the reusable Collection View Cell with our custom class
  icon = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as PhotoCell
  var photoName = String()

  // Initialize the photoName variable with an item in the searchResults array or an item in the imageFileNames array
  if isSearchOn == true && !searchResults.isEmpty {
    photoName = searchResults[indexPath.item]
  } else {
    photoName = imageFileNames[indexPath.item]
  }

  // Configure the collection view cell
  icon.imageView.image = UIImage(named: photoName)
  var stringArray: Array = photoName.componentsSeparatedByString(".")
  icon.caption.text = stringArray[0]

  // Return the cell
  return icon
}

Also, change the collection view delegate function’s code to this:

 // MARK: UICollectionViewDelegate
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
  // Put the selected collection view cell's photo file name in a variable
  if isSearchOn == true && !searchResults.isEmpty {
    selectedPhotoName = searchResults[indexPath.row] as String
  } else {
    selectedPhotoName = imageFileNames[indexPath.row] as String
  }

  // Pass control to the PhotoDetailViewController
  self.performSegueWithIdentifier("photoDetail", sender:self)
}

The last thing I want you to do is, change the PhotoCell.swift file’s code so it look like this:

import UIKit

class PhotoCell: UICollectionViewCell {
  // The collection view cell's objects
  var imageView: UIImageView!
  var caption: UILabel!

  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }

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

    // Create an ImageView and add it to the collection view
    imageView = UIImageView(frame: CGRect(x:30, y:12, width:55, height:55))
    imageView.contentMode = UIViewContentMode.ScaleAspectFill
  contentView.addSubview(imageView)

    // Create a Label view and add it to the collection view
    let textFrame = CGRect(x:5, y:67, width:100, height:35)
    caption = UILabel(frame: textFrame)
    caption.font = UIFont.systemFontOfSize(14.0)
    caption.textAlignment = .Center
    caption.numberOfLines = 2
    caption.lineBreakMode = NSLineBreakMode.ByWordWrapping
    caption.textColor = UIColor.whiteColor()
    caption.backgroundColor = UIColor.blackColor()
    contentView.addSubview(caption)
  }
}

Go ahead and run the app in the iOS Simulator and test the Search Bar’s and code, and the keyboard dismissal code. The app should function just like the one shown in above QuickTime video.

That’s All Folks!

That’s all for phase 6 of the SwiftCollectionView app. Next week, you will learn how to implement these features in the app:

  • Fetch image urls from a plist file, add them in the collection view’s dataSource variable.
  • Load the photo urls’ images in the collection view cells

. Until then, happy coding! 🙂