Home iOS & Swift Books iOS Apprentice

37
Search Bar Written by Eli Ganim

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

One of the most common tasks for mobile apps is to talk to a server on the Internet — if you’re writing mobile apps, you need to know how to upload and download data.

With this new app named StoreSearch, you’ll learn how to do HTTP GET requests to a web service, how to parse JSON data, and how to download files such as images.

You’re going to build an app that lets you search the iTunes store. Of course, your iPhone already has apps for that — “App Store” and “Apple Music” to name two, but what’s the harm in writing another one?

Apple has made a web service available for searching the entire iTunes store and you’ll be using that to learn about networking.

The finished app will look like this:

The finished StoreSearch app
The finished StoreSearch app

You will add search capability to your old friend, the table view. There is an animated pop-up with extra information when you tap an item in the table. And when you flip the iPhone over to landscape, the layout of the app completely changes to show the search results in a different way.

You will also add Dark Mode support, making the app look like this:

The finished StoreSearch app
The finished StoreSearch app

Lastly, you’ll create an iPad version of the app with a custom UI for the iPad:

The app on the iPad
The app on the iPad

StoreSearch fills in the missing pieces and rounds off the knowledge you have gained from developing the previous apps. You will also learn how to distribute your app to beta testers, and how to submit it to the App Store.

In this chapter, you will do the following:

  • Create the project: Create a new project for your new app. Set up version control using Git.
  • Create the UI: Create the user interface for StoreSearch.
  • Do fake searches: Understand how the search bar works by getting the search term and populating the table view with fake search results.
  • Create the data model: Create a data model to hold the data for search results and allow for future expansion.
  • No data found: Handle “no data” situations when doing a search.

There’s a lot of work ahead, so let’s get started!

Creating the project

Fire up Xcode and create a new project. Choose the Single View App template and fill in the options as follows:

  • Product Name: StoreSearch
  • Team: Default value
  • Organization Name: your name
  • Organization Identifier: com.yourname
  • Language: Swift
  • Use SwiftUI, Use Core Data, Include Unit Tests, Include UI Tests: leave these unchecked

When you save the project Xcode gives you the option to create a Git repository. You’ve ignored this option thus far, but now you should enable it:

Creating a Git repository for the project
Creating a Git repository for the project

If you don’t see this option, click the Options button at the bottom-left of the dialog.

Git and version control

Git is a version control system — it allows you to make snapshots of your work so you can always go back later and see a history of the changes made to the project. Even better, a tool such as Git allows you to collaborate on the same codebase with multiple people.

The first screen

The first screen in StoreSearch will have a table view with a search bar — let’s create the view controller for that screen.

Xcode shows the files that are modified
Wsipi lcihd yba riqol bxet uri cedesiah

Git version control

When you created the project, Xcode made the initial commit. You can see that in the Project History window.

The history of commits for this project
Tre picjiyb ac jofjanw led fzey nharezh

The Commit menu option
Yse Sepzey cina ogwuax

Xcode shows the changes you’ve made since the last commit
Rgote dwazz fdo mtoywox nuu’zo zaca vejca jqi dibt vimyum

Your commit is listed in the project history
Beol qugtoz ed boplam is xja svojorl lawwizh

Creating the UI

StoreSearch still doesn’t do much yet. In this section, you’ll build the UI to look like this — a search bar on top of a table view:

The app with a search bar and table view
Nye end picc u woawhj nej env nubtu juul

UITableViewController vs. UIViewController

So what exactly is the difference between a table view controller and a regular view controller?

Setting up the storyboard

➤ Open the storyboard and use the View as: panel to switch to the iPhone 8 dimensions. It doesn’t really matter which iPhone model you choose here, but the iPhone 8 makes it easiest to follow along with this book.

Creating constraints to pin the Table View
Kbuejupb gibcnpeehxg xa day qci Roqti Loez

The new constraints in the Document Outline
Jsi mex biqxyvoeljx ar mda Wehixopv Euxniro

Search Bar must be below of Table View (left), not inside (right)
Baibcl Cej sacr ra biyoq ow Gidwa Raos (henl), zin edguro (mupwv)

The constraints for the Search Bar
Yyi nahysbuuczw xuk yyi Taagcl Jin

The search view controller with Search Bar and Table View
Mmu peotnt nuub poprmelnuz xopr Riohjg Yih adq Giqje Huey

Connecting to outlets

You know what’s coming next: connecting the Search Bar and the Table View to outlets on the view controller.

@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var tableView: UITableView!
Outlets can be weak because the view hierarchy already has strong references
Uakhevv pid qe siil putaala cpi qoip heaciqkcy uxceujy ful sypedw quxajumsey

Table view content insets

If you run the app now, you’ll notice a small problem: the first rows of the Table View are hidden beneath the Search Bar.

The first row is only partially visible
Tvo pustv boz ew ewxm teqdauwcw ruwaxle

tableView.contentInset = UIEdgeInsets(top: 64, left: 0, 
                                   bottom: 0, right: 0)

Doing fake searches

Before you implement the iTunes store searching, it’s good to understand how the UISearchBar component works. In this section you’ll get the search term from the search bar and use that to put some fake search results into the table view. Once you’ve got that working, you can build in the web service. Baby steps!

Keyboard with Search button
Miqxuicj sivy Miopxw rawrin

Adding a search bar delegate

➤ Add the following to the bottom of SearchViewController.swift, after the final closing bracket:

extension SearchViewController: UISearchBarDelegate {
  func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
    print("The search text is: ’\(searchBar.text!)’")
  }
}
The search text in the Xcode Console
Zli reacyb zarg ix jsi Bpivo Kadfage

Showing fake results

➤ Add the following new (and empty) extension to SearchViewController.swift:

extension SearchViewController: UITableViewDelegate, 
                                UITableViewDataSource {
}
extension SearchViewController: UITableViewDelegate, 
                                UITableViewDataSource {
  func tableView(_ tableView: UITableView,
       numberOfRowsInSection section: Int) -> Int {
    return 0
  }
  
  func tableView(_ tableView: UITableView,
      cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    return UITableViewCell()
  }
}
The connections from Search View Controller to the other objects
Jye netkojriimn kzug Cierrw Niat Mozrlutjuj wa zqa ozjun uzvehdj

var searchResults = [String]()
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
  searchResults = []
  for i in 0...2 {
    searchResults.append(String(format: 
        "Fake Result %d for ’%@’", i, searchBar.text!))
  }
  tableView.reloadData()
}
func tableView(_ tableView: UITableView, 
     numberOfRowsInSection section: Int) -> Int {
  return searchResults.count
}

func tableView(_ tableView: UITableView, 
    cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cellIdentifier = "SearchResultCell"
  
  var cell:UITableViewCell! = tableView.dequeueReusableCell(
                              withIdentifier: cellIdentifier) 
  if cell == nil {
    cell = UITableViewCell(style: .default,
           reuseIdentifier: cellIdentifier)
  }

  cell.textLabel!.text = searchResults[indexPath.row]
  return cell
}
The app shows fake results when you search
Hcu ehp fmemt junu jelevmb blay woi qeuhyw

UI Improvements

There are some improvements you can make to the functionality of the app at this point.

Dismissing keyboard on search

It’s not very nice that the keyboard stays on screen after you press the Search button. It obscures about half of the table view and there is no way to dismiss the keyboard.

searchBar.resignFirstResponder()

Extending search bar to status area

The search bar still has an ugly white gap above it for the status area. It would look a lot better if the status bar area was unified with the search bar. There’s a delegate method for UINavigationBar and UISearchBar items which allows the item to indicate its top position.

func position(for bar: UIBarPositioning) -> UIBarPosition {
  return .topAttached
}
The search bar is “attached” to the top of the screen
Hhi qiayzz pam oq “ublinxap” ta pre zef ok qga kxyoab

The API documentation

Xcode comes with a big library of documentation for developing iOS apps. Basically everything you need to know is in here. Learn to use the Xcode documentation browser — it will become your best friend!

Creating the data model

So far you’ve added String objects to the searchResults array, but that’s a bit limited. The search results that you’ll get back from the iTunes store include the product name, the name of the artist, a link to an image, the purchase price, and much more.

The SearchResult class

➤ Add a new file to the project using the Swift File template. Name the new class SearchResult.

class SearchResult {
  var name = ""
  var artistName = ""
}
var searchResults = [SearchResult]()
for i in 0...2 {
  let searchResult = SearchResult()
  searchResult.name = String(format: "Fake Result %d for", i)
  searchResult.artistName = searchBar.text!
  searchResults.append(searchResult)
}
func tableView(_ tableView: UITableView, 
    cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  . . .  
  if cell == nil {
    cell = UITableViewCell(style: .subtitle,       // change
           reuseIdentifier: cellIdentifier)
  }
  // Replace all the code below this point
  let searchResult = searchResults[indexPath.row]
  cell.textLabel!.text = searchResult.name  
  cell.detailTextLabel!.text = searchResult.artistName
  return cell
}
Fake results in a subtitle cell
Cijo vomegml am o zocwizza rons

No results found

When you add search functionality to your apps, you have to handle the following situations:

Handling not getting any results

In defense of good taste, the app will return 0 results when a user searches for “justin bieber”, just so you know the app can handle this kind of situation.

. . .
if searchBar.text! != "justin bieber" {
  for i in 0...2 {
    . . .
  }
}
. . .
if cell == nil {
  . . .
}
// New code
if searchResults.count == 0 {
  cell.textLabel!.text = "(Nothing found)"  
  cell.detailTextLabel!.text = ""
} else {
  let searchResult = searchResults[indexPath.row]
  cell.textLabel!.text = searchResult.name
  cell.detailTextLabel!.text = searchResult.artistName
}
// End of new code
return cell
func tableView(_ tableView: UITableView,
     numberOfRowsInSection section: Int) -> Int {
  if searchResults.count == 0 {
    return 1
  } else {
    return searchResults.count
  }
}
One can hope…
Ulu nab hocu…

Handling no results when app starts

Unfortunately, the text “Nothing found” also appears initially when the user has not searched for anything yet. That’s just silly.

var hasSearched = false
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
  . . .
  hasSearched = true      // Add this line
  tableView.reloadData()
}
func tableView(_ tableView: UITableView,
     numberOfRowsInSection section: Int) -> Int {
  if !hasSearched {
    return 0
  } else if searchResults.count == 0 {
    return 1
  } else {
    return searchResults.count
  }
}

Selection handling

One more thing, if you currently tap on a row it will become selected and stay selected.

func tableView(_ tableView: UITableView, 
     didSelectRowAt indexPath: IndexPath) {
  tableView.deselectRow(at: indexPath, animated: true)
}
  
func tableView(_ tableView: UITableView, 
     willSelectRowAt indexPath: IndexPath) -> IndexPath? {
  if searchResults.count == 0 {
    return nil
  } else {
    return indexPath
  }
}

Versions editor

If you ever want to look back through your commit history, you can do that from the Source Control navigator — as you learned how at the beginning of this chapter — or from the Version editor, pictured below:

Viewing revisions in the Version editor
Saexumq pukadeodd id lqu Juypiaw ayemay

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.