© Kevin Languedoc 2016

Kevin Languedoc, Build iOS Database Apps with Swift and SQLite, 10.1007/978-1-4842-2232-4_6

6. Selecting Records

Kevin Languedoc

(1)Montreal, Canada

The SELECT statement is the workhorse of the SQL language no matter what platform you are using, and it is no different with SQLite. The SELECT statement is used to perform queries against a SQLite table or view. It is also used to populate views in SQLite databases, just as in other relational database systems.

This chapter demonstrates how to use SELECT in Swift by binding data from columns in SQLite tables and assigning them to Swift data-type variables.

The examples demonstrate how to perform SELECT queries to return text and numeric data as well as audio, image, and video data. The Winery app will be retrofitted with SELECT queries to display the list of wineries for the UIPickerView as well as display wines that are stored in the database.

In this chapter, we will explore the following:

  • the SELECT statement syntax

  • various SELECT use cases

  • binding data types

  • inner SELECT

  • joins

  • inline SELECT

  • adding SELECT queries to the Winery app

Column Data Types

Here is a list of the data-type binding functions that you can use to assign values from a SELECT query to a C-based variable, a Swift variable, or a custom object's properties:

  • sqlite3_column_blob(sqlite3_stmt, int iCol);

  • sqlite3_column_bytes(sqlite3_stmt, int iCol);

  • sqlite3_column_bytes16(sqlite3_stmt, int iCol);

  • sqlite3_column_double(sqlite3_stmt, int iCol);

  • sqlite3_column_int(sqlite3_stmt, int iCol);

  • sqlite3_column_int64(sqlite3_stmt, int iCol);

  • sqlite3_column_text(sqlite3_stmt, int iCol);

  • sqlite3_column_text16(sqlite3_stmt, int iCol);

  • sqlite3_column_type(sqlite3_stmt, int iCol);

  • sqlite3_column_value(sqlite3_stmt, int iCol);

  • sqlite3_bind_zeroblob(sqlite3_stmt, int, int iCol)

The SELECT Statement

The SQLite SELECT statement is used to extract data and information from a SQLite database. The SELECT statement is the most complicated in the SQL language, mostly because of all the permutations.

The basic syntax of a SELECT query is:

SELECT column(s) from main.table

You could also use a wildcard to return all the columns in the table:

SELECT * from main.table

If you wanted or needed to return only a subset of the data from a table, you could use a WHERE clause, as in the following example:

SELECT * from main.table WHERE column1 = ‘some value’

The WHERE clause could evaluate either custom or standard functions. You could also use the IN clause. For example:

SELECT id, column1 FROM main.table WHERE column1 IN (SELECT col_id, column1 from main.second_table WHERE col_id=id)

The SELECT statement can also include another SELECT statement instead of a column; this is known as an inline select query, as the following illustrates:

SELECT id, column1, (SELECT column FROM main.table )

Selecting Data

The first use case will demonstrate how to perform a basic, boilerplate SELECT without a WHERE clause . The SELECT in SQLite is performed through the select-stmt function. While you don't interface directly with this function, meaning you don't call this function directly when performing SELECT queries, the function does support the standard SQL API on SELECT. The following example follows the same pattern of accessing the SQLite database in the Documents path, opening it, and performing a SELECT query.

In this example, the SELECT query retrieves a result set of contacts and assigns them to a custom Swift data type. The SELECT query string is defined using a Swift String variable. Look at the database parameter in the sqlite3_open statement as an example of how to use an NSString to build a SELECT query and pass it to the sqlite3_stmt function to be executed by the database engine.

The SELECT statement SQL query is passed to the sqlite3_prepare_v2 function before it can be executed by the database engine. To access the result set, you will need to use the sqlite3_step function along with the SQLITE_ROW constant to loop through the results; as long as there are records in the result set SQLITE_ROW remains true.

Once the result set is exhausted, sqlite3_finalize is called to clean up the prepared statement, and sqlite3_close closes the database. String data is stored in the database using UTF-8 encoding, so when you need to assign a string value to a Swift variable, you need to use the NSUTF8StringEncoding method from the String class.

The database values that are retrieved from the result set must be passed or assigned to a variable or custom data type using a special function like sqlite3_column_text. There is a sqlite3_column for each data type, including blobs. Access to the columns in a table is done through a zero-based array, as in the example code that follows.

Once the values are assigned to either a Swift variable or a custom data-type property, if you wanted to display them in a UIPickerView or a UITableView you would need to add the values to an array or NSDictionary, which is the preferred, most commonly used data source method for these list-based objects. See the example code here:

class SelectWithSwift: NSObject {

    var dbPath:URL = URL()
    let db:COpaquePointer?=nil
    let sqlite3_stmt:COpaquePointer?=nil


    override init() {
        let dirManager = FileManager.default()
        do {
            let directoryURL = try dirManager.urlForDirectory(FileManagerSearchPathDirectory.documentDirectory, in FileManager.SearchPathDomainMask.UserDomainMask, appropriateFor: nil, create: true)


            dbPath = try! directoryURL.appendingPathComponent("Contacts.sqlite")

        } catch let err as NSError {
            print("Error: (err.domain)")
        }
    }


    func simpleSelect(){
        var contactList = [Person]()
        let sql:String = "Select id, name, address, city, zip,country from contact"


        if(sqlite3_open(dbPath.path!, &db)==SQLITE_OK){
            if(sqlite3_prepare_v2(db, sql.cString (using:String.Encoding.utf8)!, -1, &sqlStatement,  nil) == SQLITE_OK)
            {
                while (sqlite3_step(sqlStatement)==SQLITE_ROW) {
                    let contact:Person = Person()
                    contact.name = String(cString:UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 0)))!
                    contact.address = String(cString:UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 1)))!
                    contact.city = String(cString:UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 2)))!
                    contact.zip = String(cString:UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 3)))!
                    contact.country = String(cString:UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 4)))!contactList.append(contact)


                }
        }
        }
        sqlite3_finalize(sqlStatement);
        sqlite3_close(db);
    }
Note

Obviously, if you are only retrieving one value from the database, like we will see in the example that uses a WHERE clause, you don't need to loop through the result set.

Using a Dynamic WHERE Clause

The next use case will demonstrate how to use a WHERE clause. Using a WHERE clause with your SELECT query is a powerful feature in SQL. The WHERE clause in SQLite acts no differently than it does in any other database platform. The trick is how to dynamically pass values to the WHERE clause's Boolean variables.

Basically, there are two formats you can use to pass values to the WHERE clause's variables. You can build an NSString string or use string formatters, like in the following example:

    func SelectWhereContactInformation(){
        var contactList = [Person]()
        if(sqlite3_open(dbPath.path!, &db)==SQLITE_OK){
            let sql:String = "Select id, name, address, city, zip,country from contact where name=?";
            if(sqlite3_prepare_v2(db, sql, -1, &sqlStatement,  nil) == SQLITE_OK)
            {
                // input value for the WHERE clause unless you are
                sqlite3_bind_text(sqlStatement, 0, sql, -1, nil);


                //Bind each column in the table to the property names
                //for the contact object.
                while (sqlite3_step(sqlStatement)==SQLITE_ROW) {
                    let contact:Person = Person()
                    contact.name = String(cString: UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 0)))!
                    contact.address = String(cString: UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 1)))!
                    contact.city = String(cString: UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 2)))!
                    contact.zip = String(cString: UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 3)))!
                    contact.country = String(cString: UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 4)))! contactList.append(contact)
                }
            }
}


    sqlite3_finalize(sqlStatement);
    sqlite3_close(db);
    }

In the preceding code, the value for the name variable in the WHERE clause gets updated from the sqlite3_bind_text value, which replaces the “?” placeholder in the sql: String variable. You then pass the string to the sqlite3_prepare_v2 method by converting the sql query string to UTF-8, as follows:

sql.cString (using: String.Encoding.utf8)!

Perform a SELECT using a Sub-Query

The next example I want to show you is how to do a SELECT using a sub-query. Actually, a sub-query is a result set of a SELECT query, and you perform your SELECT query on this result set rather than on the complete table.

Consider this basic example, which shows the basic syntax of a sub-query:

        SELECT id, name, sales, sales_quota, region FROM Sales WHERE id IN
                (SELECT id FROM Sales WHERE closed_deals > 10000000)

From a Swift standpoint, this wouldn't be any different than a normal query, and you could pass the value of the WHERE expression as in previous examples. Formatted as a String variable assignment, however, this would look like the following:

let intValue:Int = 1000000
        let subquery:String = "SELECT id, name, sales, sales_quota, region FROM Sales WHERE id " +
        "IN (SELECT id FROM Sales WHERE closed_deals > (intValue)"

Or you could bind the value as you have seen in previous examples:

let intValue:Int = 1000000
        let subquery:String = "SELECT id, name, sales, sales_quota, region FROM Sales WHERE id " +
        "IN (SELECT id FROM Sales WHERE closed_deals > ?"
        // sqlite3_prepare_v2 code
sqlite3_bind_int(sqlStatement, 0, Int32(intValue))

There are a couple of rules that you need to keep in mind when working with sub-queries. First, the sub-query can only have one column in the SELECT clause unless several columns in the main SELECT match up with the same columns in the sub-query. Second, sub-queries must be enclosed in parentheses. Third, you cannot include an ORDER BY clause in the sub-query. However, you can use ORDER BY in the main SELECT. Fourth, a sub-query that returns more than one row must use a multiple-value operator like IN. Finally, you can only use the BETWEEN clause in a sub-query.

Perform a SELECT using Joins

Joins are an essential operation in SQL, or, more precisely, in a relational database, since we look up data in different tables based on the relationship between those tables. SQLite implements joins in a fashion similar to that of other SQL derivatives. In this section, we will explore the standard join patterns or clauses.

Using an INNER Join

The most common join is the inner join . The inner join uses a matching key (primary–foreign) to create a relationship between two tables. The key doesn't have to have the same name, only the same data type:

        Select c.id, c.name, r.hotel, r.checkin, r.checkout from customer as C JOIN reservations as r ON on c.id = r.custid

You could also use a WHERE clause to achieve the same result:

        Select c.id, c.name, r.hotel, r.checkin, r.checkout from customer as C JOIN reservations as r WHERE c.id = r.custid

Using a CROSS Join

The NATURAL JOIN is similar to the inner join except that the query presumes and expects to find an identical column in each joined table and will any matching column (name and data type) that it finds:

Select c.id, c.name, r.hotel, r.checkin, r.checkout from customer as C NATURAL JOIN reservations AS r

Using the OUTER Join

With the left outer join , all the records from the left table—or, in other words, all the records from the table immediately following the FROM keyword—even if there are no records in the table on the right.

Select c.customerid, c.name, v.visits from customer as C LEFT JOIN Visits v ON c.id=v.custid

Select and Display Images

This example demonstrates how to select binary data like images and convert that binary data into the UIImage data type that can be displayed in a UIImageView:

    func selectImages(_ filename:String)->Array<UIImage>{
        var imageArr = [UIImage]()


        if(!(sqlite3_open(dbPath.path!, &db) == SQLITE_OK))
        {
            print("An error has occured.");
            return imageArr;
        }else{
            let sql:String = "SELECT id, filename, image FROM images where filename=?";
            if(sqlite3_prepare(db, sql, -1, &sqlStatement, nil) != SQLITE_OK)
            {
                print("Problem with prepared statement");
            }else{
                //WHERE parameter value
                sqlite3_bind_text(sqlStatement, 1,filename, -1,SQLITE_TRANSIENT)
                while (sqlite3_step(sqlStatement)==SQLITE_ROW) {
                    let contact:Person = Person()


                    let raw:UnsafePointer = sqlite3_column_blob(sqlStatement, 3);
                    let rawLen:Int32 = sqlite3_column_bytes(sqlStatement, 3);
                    let data:Data = Data(bytes: raw, count: Int(rawLen))


                    //Convert the binary data into an UIImage
                    contact.avatar = UIImage(data: data)!
                    imageArr.append(contact.avatar)


                }
            }


        }
       return imageArr
    }

Select and Playback Audio Records

func selectAudioWithPlayback(_ selectedFile:String)->Data{

        var audio:Data = Data()
        let sql:String = "SELECT audioData FROM audios wherefileName= (selectedFile)"


        if(sqlite3_open(dbPath.path!, &db)==SQLITE_OK){
            if(sqlite3_prepare_v2(db, sql, -1, &sqlStatement, nil)==SQLITE_OK){
                while (sqlite3_step(sqlStatement)==SQLITE_ROW) {
                    let raw:UnsafePointer = sqlite3_column_blob(sqlStatement, 3);
                    let rawLen:Int32 = sqlite3_column_bytes(sqlStatement, 3);
                    audio = Data(bytes: raw, count: Int(rawLen))
                }
            }
            sqlite3_finalize(sqlStatement);
            sqlite3_close(db);
            return audio
        }
    }
Note

The following code is for representation only, meaning that it cannot be run from here. Rather, you would need to include it in a view controller attached to an IBAction. The self-reference that follows refers to the view controller.

func playback(_ selectedAudioFile:NSData){

        do {
            var audioPlayer = try AVAudioPlayer(data:selectedAudioFile)
            audioPlayer.delegate = self;
            audioPlayer.prepareToPlay()
            audioPlayer.play()
        } catch let err as NSError {
            print("Error: (err.domain)")
        }


    }

Select and Display Video Records

Selecting videos for playback follows the same pattern as doing so for images and audio. To perform a SELECT, extract the data using the blob and store the binary data in an NSData object or objects that can be stored in an NSMutableArray or NSDictionary, for example.

However, you can't convert or pass the NSData object directly to the MPMoviePlayerControllerobject for playback like you can with images or audio. You will need to convert the NSData object to a movie format like mp4 by saving it to a movie file in the Documents directory. To save the NSData to an mp4 file, simply write out the data to a file with the .mp4 extension; NSData is simply a container for data, so no transformation is needed.

Alternatively, you could store the NSData to an NString and convert this string to an NSURL object, which you could then use to initialize the MPMoviePlayerController using the initWithContentURL method.

Adding SELECT Functionality to the Winery App

In this section of the chapter, I will add the SELECT functionality to the Winery app. This functionality will include a function to select a winery when entering a wine, another function to select and display a list of wines from the database, as well as a function to display the different wineries. This third function will be enhanced in a later chapter on updating records. Let's start with the wineriesPickerView.

Add the SelectWineries UIPicker

This little function displays the UIPicker so that a user can select a winery from the list:

    @IBAction func selectWineryButton(sender: AnyObject) {
         self.wineriesPickerView.hidden = false
    }

The viewDidLoad Function

This standard ViewController function runs after the scene is loaded onto the view stack, or in our case when the app is launched. The function provides a channel through which to set up the wineriesPickerView by setting up the delegate and assigning the wineriesArray data source. Once the setup is complete, the UIPicker is added as a sub-view to the current view.

override func viewDidLoad() {
        super.viewDidLoad()
        //build data source


        self.wineriesPickerView.isHidden = true
        self.wineriesArray = dbDAO.selectWineriesList()
        self.wineriesPickerView.dataSource = self
        self.wineriesPickerView.delegate = self
        self.wineriesPickerView.frame = CGRect(x:19, y:243, width: 336, height: 216)
        self.wineriesPickerView.backgroundColor = UIColor.white()
        self.wineriesPickerView.layer.borderColor = UIColor.blueColor().cgColor
        self.wineriesPickerView.layer.borderWidth = 1


        //other pickerView code like dataSource and delegate
        self.view.addSubview(wineriesPickerView)        
    }

The UIPickerView Functions

The following functions are related to the UIPickerView. Some are for the delegate, which provides the interactivity to the UI component, while others identify the number of columns, the title, and the data source, as well as identify which row is selected.

The numberOfComponentsInPickerView function sets the number of columns the UIPicker will display. The pickerView function used along with the numberOfRowsInComponent argument sets the number of rows the UIPickerView will have in its data source. Typically, this return value is the data source array’s count property. Next, the pickerView function used with titleForRow displays the actual list of values. didSelectRow returns the item that is selected by the user. Since I have a foreign key for the winery in the wine table, I need to get the ID for the winery, which I am doing with the vintner.id value; this is also the row id in the data source. Then, I set the hidden property to false to hide the picker view again from the user. The next two functions set the height and width of the rows. Finally, the typePickerViewSelected, although I added it in this section, is not a required function. Rather, it is an IBAction that I defined to display the UIPickerView when the user clicks on the Select Winery button.

    func numberOfComponents (_ in pickerView: UIPickerView) -> Int {
        return 1
    }


    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return wineriesArray.count
    }


    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?   {
        vintnor = wineriesArray[row] as Wineries
        let pickernames = vintnor.name
        return  pickernames    }


    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        vintnor = wineriesArray[row] as Wineries
        vintnor.id = Int32(row)
        wineriesPickerView.isHidden = false    
    }


    func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
        return 300.0
    }


    func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
        return 56.0
    }


    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        vintnor = wineriesArray[row] as Wineries
        selectWineryField.text = vintnor.name


        wineriesPickerView.endEditing(true)
        wineriesPickerView.hidden = true
    }
    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
        let test:UILabel = UILabel()
        let titleData = wineriesArray[row].name
        let myTitle = AttributedString(string: titleData, attributes: [NSFontAttributeName:UIFont(name: "Georgia", size: 15.0)!,NSForegroundColorAttributeName:UIColor.red()])
        test.attributedText = myTitle
        return test


    }

The selectWineriesList Function

This function will act as the data source for the UIPickerView that is activated when you enter the Select Winery field in the FirstViewController. This function will also be called by the UITableViewController to display the list of wineries. After you open the database, if it's not already open, the SELECT query string is passed to the sqlite3_prepared_v2 function along with the sqlite3-stmt pointer, sqlStatement. While there are records, meaning while sqlite3_step returns SQLITE_ROW, the code will loop and assign the returned values to the wine object’s properties.

Unbinding the returned values requires a couple of hoops, but as long you know how to deal with the return data types, everything will move along smoothly. sqlite3_column_int returns an Int32 value. You can convert this value to an Int, if needed, by wrapping sqlite3_column_int in an Int() function. Likewise, for sqlite3_column_text, which returns an UnsafePointer of an Int8 type, you just need to convert the returned value into an Int8 character, then convert this value to a String by using fromCString, which requires an Int8 value.

The returned columns are zero based, so the last parameter in the sqlite3 column-mapping functions is the column number whose value you need returned. See the following:

func selectWineriesList()->Array<Wineries>{
        var wineryArray = [Wineries]()


let sql:String = "Select name, country, region, volume, uom from main.winery"

        if(sqlite3_open(dbPath.path!, &db)==SQLITE_OK){
            if(sqlite3_prepare_v2(db, sql, -1, &sqlStatement, nil)==SQLITE_OK){
                while(sqlite3_step(sqlStatement)==SQLITE_ROW){
                    let vintnor:Wineries = Wineries.init()
                    vintnor.name = String(cString:UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 0)))!
                    vintnor.country = String(cString: UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 1)))!
                    vintnor.region = String(cString: UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 2)))!
                    vintnor.volume = sqlite3_column_double(sqlStatement, 3)
                    vintnor.uom = String(cString: UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 4)))!
                    wineryArray.append(vintnor)
                }
            }
        }
        sqlite3_close(db)
        return wineryArray
    }

The selectWineList Function

The selectWineList function acts the same way as the previous function. Its one special feature is the blob, or stored image. In order to convert the stored blob, you need the NSData, as we saw earlier in this chapter.

The NSData function needs the returned data as bytes, and it needs to know the length of the bytes. These values are stored in the raw and rawLen variables, respectively. The raw variable is an UnsafePointer because this is the return type of the sqlite3_column_blob function, and rawLen is an Int32 data type. You then pass these two values to the NSData initializer, which is then appended to the wine.image property. However, this value is still a binary format. Later in the chapter, the app will convert NSData into an UIImage and display it in the UIImageView. See the following:

func selectWineList()->Array<Wine>{
    var wineArray = [Wine]()
        let sql:String = "Select name, rating, image, producer from main.wine"
if(sqlite3_open(dbPath.path!, &db)==SQLITE_OK){
            if(sqlite3_prepare_v2(db, sql.cString(String.Encoding.utf8)!, -1, &sqlStatement, nil)==SQLITE_OK){
                while(sqlite3_step(sqlStatement)==SQLITE_OK){
                    let wine:Wine = Wine.init()


                    wine.name = String(cString: UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 1)))!
                    wine.rating = sqlite3_column_int(sqlStatement, 2)
                    let raw:UnsafePointer = sqlite3_column_blob(sqlStatement, 3);
                    let rawLen:Int32 = sqlite3_column_bytes(sqlStatement, 3);
                    wine.image  = Data(bytes: raw, count: Int(rawLen))
                    wine.producer = String(cString: UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 4)))!
        wineArray.append(wine)
                }
            }
        }
        sqlite3_close(db)
        return wineArray
    }

The selectWineryByName Function

The wine producer property, which is the foreign key in the database, is an Int32 value. In order to get the winery name from the database for the Selected Winery field in the FirstViewController, the following function performs a SELECT statement with a WHERE clause, using the ID to locate the record. I pass the WHERE clause value using sqlite3_bind_int then retrieve the return value and assign it to the name property of the vintner object using the same method as before. Notice that the last parameter is a 1 instead of a 0. This is because in the SELECT query I am requesting two columns: the ID and the name.

    func selectWineryByName(name:String)->String{
        let vintnor:Wineries = Wineries.init()
        let sql:String = "Select name from main.winery where name=?"
        if(sqlite3_open(dbPath.path!, &db)==SQLITE_OK){
            if(sqlite3_prepare_v2(db, sql.cString(using: String.Encoding.utf8)!, -1, &sqlStatement, nil)==SQLITE_OK){
                sqlite3_bind_text(sqlStatement, 0, vintner.name.cString(String.Encoding.utf8)!, -1, SQLITE_TRANSIENT)
                if(sqlite3_step(sqlStatement)==SQLITE_OK){
                    vintnor.name = String(cString:UnsafePointer<Int8>(sqlite3_column_text(sqlStatement, 1)))!
                }
            }
        }
        sqlite3_close(db)
        return vintnor.name
    }

Modifying the UI for Displaying Records

With the SELECT queries in place, all that is needed is to add the view controllers to the UI and wire everything up. The app is going to need two TableViewControllers to display the list of records. These will have to be wired to the TabBarController.

Figure 6-1 provides a visual of the additional design elements that are added to the Winery app for the select and display functionality. These new elements include the following:

A427374_1_En_6_Fig1_HTML.jpg
Figure 6-1. Extended storyboard with TableViewControllers
  • Two table view controllers

  • Two navigation controllers

  • Two UITableViewCellControllers

  • Three UILabels for the WineList cell prototype and five for the WineryList cell prototype

Adding the UITableViewControllers

We will first add the UITableViewControllerfor the wine list, which I will call the Cellar. Select the main.storyboard to open it (Figure 6-1). Drag a UITablewViewController from the palette on the lower right onto the canvas. Select the UITableViewController and then select the Attributes inspector and enter the “Cellar” title in the Title field. To connect the TableView to the TabBarController, you first need to create a NavigationViewController for the TableViewController. The easiest way to create the NavigationViewController, other than dragging it from the palette onto the canvas, is to select it and select Editor/Embed/Navigation Controller from the Xcode menu.

Once the navigation controller has been created, select the TabBarController and drag (press Control or CTRL and press the left mouse button while dragging a line from the TabBar to the navigation controller) a connection to the navigation controller. Upon releasing the mouse button, a popup will appear. From this popup, you need to select ViewControllers from the choices. This will create a connection with the TabBarController and will add a navigation item to the existing tab bar. You can change the item's value by selecting the navigation controller and changing the title value in the Attributes inspector. We need to repeat this process for the next UITableViewController for the winery list, which will also have a modified TableViewCell.

For now, that is all that is needed for this UITableViewController. We will add the TableViewControllers and the TableViewCellController in the next section.

After creating the second UITableViewController, using the preceding instructions for the Cellar, drag two UILabels onto the Cell Prototype (Figure 6-2). Change the label names to Wine and Winery as in the following figure. Also add an ImageView for the wine image. We will add the view controllers in the next section as well as create the IBOutlets.

A427374_1_En_6_Fig2_HTML.jpg
Figure 6-2. The Cellar TableView cell prototype

Adding the Navigation Controllers

The final step is to provide an interface between the new UI elements and the SELECT functions, and ultimately the SQLite database. This interface consists of TableViewControllers and a TableViewCellController that is configured or connected to the corresponding UI elements, which will contain the IBOutlets that will display the data from the database.

Both TableViewControllersare created the same way. From the File menu in Xcode, select New, then File. In the template selector, choose the Cocoa Touch Class template under the iOS Source category (Figure 6-3). In the next screen, enter WineryListTableViewController in the Class field. In the Subclass dropdown, select the UItableViewController class, and leave or select the Swift language (Figure 6-4). When the class is created, Xcode will append the name of the super class to the name. As shown in Figure 6-5, click the Next button and select the location in the project, then click Create.

A427374_1_En_6_Fig3_HTML.jpg
Figure 6-3. Select the Cocoa Touch Class
A427374_1_En_6_Fig4_HTML.jpg
Figure 6-4. Enter WineryList in Class field
A427374_1_En_6_Fig5_HTML.jpg
Figure 6-5. Add the file to the project
Tip

You can also add a new file by right-clicking anywhere in the Project Navigator and selecting New File…

The template provides a lot of code that will need to be modified later. Repeat the same process for the WineryList controller, WineCellTableViewCell, and WineryCellTableViewCell UITableViewCellController. Next, we will connect the view controllers and add the IBOutlets.

Connect the TableViewControllers and TableViewCellController

To add the IBOutlets, you first need to connect the view controllers with their corresponding UI elements. To connect the table view controller, select the WineList in the storyboard. Select the Identity inspector on the right side of the Xcode editor, and in the Custom Class field select WineListTableViewController(Figure 6-6). Repeat the process with the WineryListTableViewController.

A427374_1_En_6_Fig6_HTML.jpg
Figure 6-6. Connect TableViewControllers

To select the TableViewCellController, expand the Document Outline using the icon on the lower left of the IB canvas (Figure 6-7). Select the TableViewCell, then select the Identity inspector and choose the WineCellTableViewController from the dropdown (Figure 6-8).

A427374_1_En_6_Fig7_HTML.jpg
Figure 6-7. Document Outline selector
A427374_1_En_6_Fig8_HTML.jpg
Figure 6-8. Connect TableViewCellController

Adding the IBOutlets: WineList Controller

For the WineList cell, expand the Document Outline and select the WineCellTableViewCell and control-drag a connection to the Assistant Editor (Figure 6-9).

A427374_1_En_6_Fig9_HTML.jpg
Figure 6-9. Create WineCell outlet

Next, in turn, select the ImageView and the UILabels and create IBOutletsfor each of them, naming them wineImgOutlet, wineNameOutlet, and wineryNameOutlet.

With the IBOutlets set up, we can add the code to display the data in the scenes.

To display the winery information in the corresponding UITableViewCell, we will need to add UILabels for the winery name, country, region, volume, and unit of measure. Follow the same process as for the WineCellTableViewCell and connect them to the WineryCellTableViewCell.

import UIKit

class WineCellTableViewCell: UITableViewCell {

    @IBOutlet weak var wineNameOutlet: UILabel!
    @IBOutlet weak var wineryNameOutlet: UILabel!
    @IBOutlet weak var wineRatingOutlet: UILabel!
    @IBOutlet weak var wineImageOutlet: UIImageView!


}

Likewise, for the WineryCell in the WineryListTableViewController(Figure 6-10), add this code:

A427374_1_En_6_Fig10_HTML.jpg
Figure 6-10. WineryCell IBOutlet
class WineryCellTableViewCell: UITableViewCell {

    @IBOutlet weak var wineryNameOutlet: UILabel!
    @IBOutlet weak var regionOutlet: UILabel!
    @IBOutlet weak var countryOutlet: UILabel!
    @IBOutlet weak var volumeOutlet: UILabel!
    @IBOutlet weak var uomOutlet: UILabel!

Add the Business Logic

To fetch the data from the database and display it in the UI, you need to add some code.

The WineListTableViewController

The WineryListTableViewControllerprovides the interface between the database and the table view cell controller. For the data source, I define a wine array, wineListArray. I populate the array using the selectWineList function in the wineDAO instance object from the loadWineList function. This latter function gets called when the view loads using the viewDidLoad standard function of the UITableViewController:

var wineListArray = [Wine]()
    let wineDAO:WineryDAO = WineryDAO()


    func loadWineList(){
        wineListArray = wineDAO.selectWineList()
    }


override func viewDidLoad() {
        super.viewDidLoad()
        loadWineList()
      }

Next, you need to tell the controller how many sections will be in the table, as well as how many rows the data source will have or will be displayed. Typically, the numberOfSectionsInTableView returns 1 to indicate one section. For the number of rows, you typically return the number of elements in the array:

  override func numberOfSections(in: tableView: UITableView) -> Int {
        return 1
    }


    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return wineListArray.count
    }

To display the data in the table, you need reference the cell that we previously set up in the IB and in the WineCellTableViewCell controller. You do that by passing the name of the cell identifier to the dequeueReusableCellIdentifier property of the table view. Then, cast the returning value as a WineCellTableViewCell. Next, you simply fetch each row in the array and assign the values to the cell’s IBOutlets. This function is called automatically for each row in the array that you defined in the previous function:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "WineCellTableViewCell", for: indexPath) as! WineCellTableViewCell


        // Configure the cell...
        let wine = wineListArray[(indexPath as NSIndexPath).row]


        cell.wineRatingOutlet.text = String(wine.rating)
        cell.wineNameOutlet.text = wine.name
        cell.wineryNameOutlet.text = wine.producer
        cell.wineImageOutlet.image =  UIImage.init(data: wine.image as Data)


        return cell
    }

The WineryListTableViewController

To set up the data source to the table, you need to define an array. In the sample app, I

class WineryListTableViewController: UITableViewController {
    var wineryListArray = [Wineries]()
    let wineDAO:WineryDAO = WineryDAO()


    func loadWineList(){
        wineryListArray = wineDAO.selectWineriesList()
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        loadWineList()


}
override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }


override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return wineryListArray.count
    }

To display the individual rows in the cell’s UILabels, set up the cellForRowAt function. To reference the cell prototype, you need to pass the WineryCellTableViewCell cell identifier to the dequeueReusableCellWithIdentifier property and cast the table view cell as WineryCellTableViewCell. With the cell reference in hand, you need to get each row in the data-source array and assign the values to the components in the cell, which in our case are the IBOutlets you set up before.

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "WineryCellTableViewCell", for: indexPath) as! WineryCellTableViewCell
        let winery:Wineries = wineryListArray[(indexPath as NSIndexPath).row]


        cell.wineryNameOutlet.text = winery.name
        cell.regionOutlet.text = winery.region
        cell.countryOutlet.text = winery.country
        cell.volumeOutlet.text = String(winery.volume)
        cell.uomOutlet.text = winery.uom


        return cell
    }

Running the App

With everything now created, fire up the app and actual iPhone, or you can use the camera in the simulator (Figure 6-11).

A427374_1_En_6_Fig11_HTML.jpg
Figure 6-11. Photo capture

Next, select the winery and rating and click the Save button to insert it into the record in the database (Figure 6-12).

A427374_1_En_6_Fig12_HTML.jpg
Figure 6-12. Select Wineries UIPickerView

To call the SELECT functions and display the data in the UITableViews, select either the Cellar or Vineyard button in the tab bar (Figure 6-13).

A427374_1_En_6_Fig13_HTML.jpg
Figure 6-13. Displaying the list of records

Summary

The focus of this chapter was using the SQLite SELECT statement to perform queries on a SQLite database. The first part was an overview of the API along with some sample code to demonstrate using SELECT with Swift. We also looked at performing SELECT queries on binary data.

Finally, we added the SELECT query's functionality to the Winery app so as to select wines and wineries for the UITableViewControllers as well as for the UIPickerView.

The next chapter will explore the UPDATE statement in SQLite, and we will also add the UPDATE functionality to the Winery app.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset