Search bar Swift UI with history – SQLite
SQLite is used to store the data locally inside the app in SQL structure. It is a relational local database. You can use this database to store data inside the app using Swift and Swift UI. We will be creating a simple search bar in an iOS app in Swift UI to search animals from an array and save the user’s searched strings in the SQLite database.
We will be using a library called SQLite by Stephen Celis. To install this library, you must have Cocoapods installed in your system.
Installation
You can install Cocoapods in your system by simply running the following command in your terminal:
sudo gem install cocoapods
First, you need to open a command prompt (Terminal) at the root of your XCode project and run the following command in it:
pod init
Now you will see a new file created at the root of your project named Podfile. Open that file in your text editor and add the line to install the library. Following will be the content of your Podfile:
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'SQLite_Database' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for SQLite_Database
pod 'SQLite.swift', '~> 0.12.0'
end
After that, run the following command in your Terminal to install this library:
pod update
Once installed, close your XCode and re-open but this time double-click on the file which has an extension “.xcworkspace“. This file will be created only after the pod has been installed/updated.
Create a search bar
First, we will create a search bar using TextField. Then, when the user hits submit, we will send the searched value in the next view.
In the second view, we will get the value from the text field of the first view and search that value in an array. Then, display all the records that matched the searched string and display them in the list view.
Create a state variable in your content view:
// variable to goto search view
@State var gotoSearchPage: Bool = false
// value that is searched from the text field
@State var searchedText: String = ""
Following will be the content of your content view’s body:
// live tracking of input field value
let binding = Binding<String>(get: {
self.searchedText
}, set: { (value) in
self.searchedText = value
})
// navigation view to goto search view
return NavigationView {
VStack (alignment: .leading, spacing: 10) {
// navigation link to goto search view
NavigationLink (destination: SearchView(searchedText: self.$searchedText), isActive: self.$gotoSearchPage) {
EmptyView()
}
// search field
TextField("Search ...", text: binding, onCommit: {
// goto search view when search icon is clicked
self.gotoSearchPage = true
})
.padding(7)
.padding(.horizontal, 5)
.background(Color(.systemGray6))
.cornerRadius(8)
.disableAutocorrection(true)
.keyboardType(.webSearch)
}
.padding(20)
.navigationBarTitle("Search - SQLite")
}
This will create a text field where you can enter your query, on hitting the “search icon” it should take you to the search view which we will create in the next step.
Create a search view
Now, we need to create a search view where we will perform the searching and display the filtered records in a list view.
Create a modal class for Animal:
//
// Animal.swift
// Search bar with history
//
// Created by Adnan Afzal on 02/12/2020.
// Copyright © 2020 Adnan Afzal. All rights reserved.
//
import Foundation
class Animal: Identifiable {
var id: Int64 = 0
var name: String = ""
init() {
//
}
init(name: String) {
self.name = name
}
}
Create a new file named SearchView.swift and paste the following code in it:
//
// SearchView.swift
// Search bar with history
//
// Created by Adnan Afzal on 02/12/2020.
// Copyright © 2020 Adnan Afzal. All rights reserved.
//
import SwiftUI
struct SearchView: View {
// get searched value from the previous view
@Binding var searchedText: String
// a list of all items (change this variable as per your need)
@State var animals: [Animal] = []
// list of items that matched the searched text
@State var searchedArray: [Animal] = []
var body: some View {
VStack {
// show searched text
Text("\(searchedText)").bold()
// show items that matched the searched text in list view
List (self.searchedArray) { (item) in
Button(action: {
//
}, label: {
Text(item.name)
})
}
}.onAppear(perform: {
// make the items empty when the page loads
self.animals.removeAll()
// add the data that needs to be searched (you can put your own array items here)
self.animals.append(Animal(name: "Lion"))
self.animals.append(Animal(name: "Tiger"))
self.animals.append(Animal(name: "Rhino"))
self.animals.append(Animal(name: "Elephant"))
self.animals.append(Animal(name: "Cheetah"))
self.animals.append(Animal(name: "Polar bear"))
self.animals.append(Animal(name: "Leopard"))
self.animals.append(Animal(name: "Wolf"))
// empty the searched array
self.searchedArray.removeAll()
// find all the elements that matched the searched string
for animal in self.animals {
if (animal.name.lowercased().contains(self.searchedText.lowercased())) {
self.searchedArray.append(animal)
}
}
})
}
}
struct SearchView_Previews: PreviewProvider {
// when using @Binding, use this in preview provider
@State static var searchedText: String = ""
static var previews: some View {
SearchView(searchedText: $searchedText)
}
}
Comments have been added on each line to explain each line individually. Now, at this point, you will be able to perform the search and see the animals which contain the name field that matches with the text field value you entered in the first view.
Show search history
Now, we need to show the search history in the content view. For that, first, we have to save each searched string in the SQLite database.
Create a separate class named “DB_Manager” that will hold all the functions for the SQLite database.
//
// DB_Manager.swift
// Search bar with history
//
// Created by Adnan Afzal on 02/12/2020.
// Copyright © 2020 Adnan Afzal. All rights reserved.
//
import Foundation
// import library
import SQLite
class DB_Manager {
// sqlite instance
private var db: Connection!
// table instance
private var animals: Table!
// columns instances of table
private var id: Expression<Int64>!
private var name: Expression<String>!
// constructor of this class
init () {
// exception handling
do {
// path of document directory
let path: String = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? ""
// creating database connection
db = try Connection("\(path)/my_animals.sqlite3")
// creating table object
animals = Table("animals")
// create instances of each column
id = Expression<Int64>("id")
name = Expression<String>("name")
// check if the animal's table is already created
if (!UserDefaults.standard.bool(forKey: "is_db_created")) {
// if not, then create the table
try db.run(animals.create { (t) in
t.column(id, primaryKey: true)
t.column(name)
})
// set the value to true, so it will not attempt to create the table again
UserDefaults.standard.set(true, forKey: "is_db_created")
}
} catch {
// show error message if any
print(error.localizedDescription)
}
}
// check if record already exists in SQLite
public func isExists(searchedText: String) -> Bool {
var isExists: Bool = false
// exception handling
do {
// get animal using ID
let animal: AnySequence<Row> = try db.prepare(animals.filter(name.lowercaseString == searchedText.lowercased()))
// get row
animal.forEach({ (rowValue) in
isExists = true
})
} catch {
print(error.localizedDescription)
}
return isExists
}
// add a new row in SQLite
public func addAnimal(nameValue: String) {
do {
try db.run(animals.insert(name <- nameValue))
} catch {
print(error.localizedDescription)
}
}
}
Comments have been added with each line for an explanation. Now, in your search view inside the onAppear() function, check if the searched string already exists in the SQLite database. If NOT, then add the searched text in the SQLite database.
// add the searched text in SQLite database if NOT exists
let isExists: Bool = DB_Manager().isExists(searchedText: self.searchedText)
if (!isExists) {
DB_Manager().addAnimal(nameValue: self.searchedText)
}
Now, we need to show the search history in the main content view when the user clicks on the search bar text field. So, create 2 state wrapper properties in your content view:
// variable to show search history view
@State var showSearchHistoryView: Bool = false
// array of history array
@State var history: [Animal] = []
And inside the body, we need to track the live text change of the input field. We have already created a binding variable inside the body of the content view. Change the binding variable inside the content view body to the following:
// live tracking of input field value
let binding = Binding<String>(get: {
self.searchedText
}, set: { (value) in
self.searchedText = value
// show history if the text field is not empty
self.showSearchHistoryView = !self.searchedText.isEmpty
})
Now, when the showSearchHistoryView variable is true, we need to display a list of all previously searched strings. So, create a list view under an if condition below the search bar text field:
// show history view only when a variable is true
if (self.showSearchHistoryView) {
// create list view to show all history items
List (self.history) { (model) in
// show history text
HStack {
Image(systemName: "arrow.counterclockwise")
Button(action: {
self.searchedText = model.name
self.gotoSearchPage = true
}, label: {
Text(model.name)
})
}
}
}
This will display a counter clock icon that represents that it is data from history, a button that shows the previously searched string, and which when clicked will move to the search view.
Now, we need to load the data in our history array. So, attach an onAppear() event to your VStack and fetch the data from the SQLite database and save it in the history array.
// load data in history models array
.onAppear(perform: {
self.history = DB_Manager().getAnimals()
})
Now, we need to create a function named getAnimals() in our DB_Manager class that will fetch the records from the SQLite database.
// return array of animal models
public func getAnimals() -> [Animal] {
// create empty array
var animalModels: [Animal] = []
// get all animals in descending order
animals = animals.order(id.desc)
// exception handling
do {
// loop through all animals
for animal in try db.prepare(animals) {
// create new model in each loop iteration
let animalModel: Animal = Animal()
// set values in model from database
animalModel.id = animal[id]
animalModel.name = animal[name]
// append in new array
animalModels.append(animalModel)
}
} catch {
print(error.localizedDescription)
}
// return array
return animalModels
}
Comments have been added with each line for an explanation. If you run the code now, you will be able to view all the searched strings in a list view, upon click it will take you to the search view screen.
At this point, the search bar in Swift UI is completed. But you can go further and add the delete functionality. So user can remove his searched queries.
Delete history
Now, we need a function to delete the string from search history. First, create a button inside your history list after the counter-clock icon and button in your content view:
... counter-clock icon and button
// show delete button
Spacer()
Button(action: {
// delete history item from SQLite
DB_Manager().deleteAnimal(idValue: model.id)
// refresh the list view
self.history = DB_Manager().getAnimals()
}, label: {
Text("Delete")
.foregroundColor(Color.red)
})// by default, buttons are full width.
// to prevent this, use the following
.buttonStyle(PlainButtonStyle())
Now, we need to create a function named deleteAnimal() in our DB_Manager class that will delete the row from the SQLite database.
// function to delete animal from search history
public func deleteAnimal(idValue: Int64) {
do {
// get animal using ID
let animal: Table = animals.filter(id == idValue)
// run the delete query
try db.run(animal.delete())
} catch {
print(error.localizedDescription)
}
}
Run the code now, and you will be able to see a delete button at the end of each history row. Upon clicking, it will remove the row from the SQLite database and from the list view as well.
So you have successfully created a search bar in Swift UI along with search history.
You can also learn to create a UICollectionView with a search bar from the following tutorial.
UICollectionview with Search Bar – Swift iOS
[wpdm_package id=’929′]