UITableView’s Data Source

The process of providing rows to a UITableView in Cocoa Touch (the collection of frameworks used to build iOS apps) is different from the typical procedural programming task. In a procedural design, you tell the table view what it should display. In Cocoa Touch, the table view asks another object – its dataSource – what it should display. In this case, the ItemsViewController is the data source, so it needs a way to store item data.

You are going to use an array to store the Item instances, but with a twist. The array that holds the Item instances will be abstracted into another object – an ItemStore (Figure 10.6).

Figure 10.6  Homepwner object diagram

Figure shows the object diagram of the Homepwner project in Model, View, and Controller pattern.

If an object wants to see all of the items, it will ask the ItemStore for the array that contains them. In future chapters, the store will be responsible for performing operations on the array, like reordering, adding, and removing items. It will also be responsible for saving and loading the items from disk.

Create a new Swift file named ItemStore. In ItemStore.swift, define the ItemStore class and declare a property to store the list of Items.

import Foundation
import UIKit

class ItemStore {

    var allItems = [Item]()

}

ItemStore is a Swift base class – it does not inherit from any other class. Unlike the Item class that you defined earlier, ItemStore does not require any of the behavior that NSObject affords.

The ItemsViewController will call a method on ItemStore when it wants a new Item to be created. The ItemStore will oblige, creating the object and adding it to an array of instances of Item.

In ItemStore.swift, implement createItem() to create and return a new Item.

@discardableResult func createItem() -> Item {
    let newItem = Item(random: true)

    allItems.append(newItem)

    return newItem
}

The @discardableResult annotation means that a caller of this function is free to ignore the result of calling this function. Take a look at the following code listing that illustrates this effect.

// This is OK
let newItem = itemStore.createItem()

// This is also OK; the result is not assigned to a variable
itemStore.createItem()

Giving the controller access to the store

In ItemsViewController.swift, add a property for an ItemStore.

class ItemsViewController: UITableViewController {

    var itemStore: ItemStore!
}

Now, where should you set this property on the ItemsViewController instance? When the application first launches, the AppDelegate’s application(_:didFinishLaunchingWithOptions:) method is called. The AppDelegate is declared in AppDelegate.swift and, as the name implies, serves as the delegate for the application itself. It is responsible for handling the changes in state that the application goes through. You will learn more about the AppDelegate and the states that the application goes through in Chapter 16.

Open AppDelegate.swift. Access the ItemsViewController (which will be the rootViewController of the window) and set its itemStore property to be a new instance of ItemStore.

func application(_ application: UIApplication, didFinishLaunchingWithOptions
        launchOptions: [UIApplicationLaunchOptionsKey : Any]?) -> Bool {
    // Override point for customization after application launch.

    // Create an ItemStore
    let itemStore = ItemStore()

    // Access the ItemsViewController and set its item store
    let itemsController = window!.rootViewController as! ItemsViewController
    itemsController.itemStore = itemStore

    return true
}

Finally, in ItemStore.swift, implement the designated initializer to add five random items.

init() {
    for _ in 0..<5 {
        createItem()
    }
}

As a quick aside, if createItem() was not annotated with @discardableResult, then the call to that function would have needed to look like:

// Call the function, but ignore the result
let _ = createItem()

At this point you may be wondering why itemStore was set externally on the ItemsViewController. Why didn’t the ItemsViewController instance itself just create an instance of the store? The reason for this approach is based on a fairly complex topic called the dependency inversion principle. The essential goal of this principle is to decouple objects in an application by inverting certain dependencies between them. This results in more robust and maintainable code.

The dependency inversion principle states that:

  1. High-level objects should not depend on low-level objects. Both should depend on abstractions.

  2. Abstractions should not depend on details. Details should depend on abstractions.

The abstraction required by the dependency inversion principle in Homepwner is the concept of a store. A store is a lower-level object that retrieves and saves Item instances through details that are only known to that class. ItemsViewController is a higher-level object that only knows that it will be provided with a utility object (the store) from which it can obtain a list of Item instances and to which it can pass new or updated Item instances to be stored persistently. This results in a decoupling because ItemsViewController is not dependent on ItemStore. In fact, as long as the store abstraction is respected, ItemStore could be replaced by another object that fetches Item instances differently (such as by using a web service) without any changes to ItemsViewController.

A common pattern used when implementing the dependency inversion principle is dependency injection. In its simplest form, higher-level objects do not assume which lower-level objects they need to use. Instead, those are passed to them through an initializer or property. In your implementation of ItemsViewController, you used injection through a property to give it a store.

Implementing data source methods

Now that there are some items in the store, you need to teach ItemsViewController how to turn those items into rows that its UITableView can display. When a UITableView wants to know what to display, it calls methods from the set of methods declared in the UITableViewDataSource protocol.

Open the documentation and search for the UITableViewDataSource protocol reference. Scroll down to the section titled Configuring a Table View (Figure 10.7).

Figure 10.7  UITableViewDataSource protocol documentation

Screenshot of the Documentation displaying the protocol reference with respect to UI Table View Data Source.

In the Configuring a Table View section, notice that two of the methods are marked Required. For ItemsViewController to conform to UITableViewDataSource, it must implement tableView(_:numberOfRowsInSection:) and tableView(_:cellForRowAt:). These methods tell the table view how many rows it should display and what content to display in each row.

Whenever a UITableView needs to display itself, it calls a series of methods (the required methods plus any optional ones that have been implemented) on its dataSource. The required method tableView(_:numberOfRowsInSection:) returns an integer value for the number of rows that the UITableView should display. In the table view for Homepwner, there should be a row for each entry in the store.

In ItemsViewController.swift, implement tableView(_:numberOfRowsInSection:).

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

Wondering about the section that this method refers to? Table views can be broken up into sections, with each section having its own set of rows. For example, in the address book, all names beginning with C are grouped together in a section. By default, a table view has one section, and in this chapter you will work with only one. Once you understand how a table view works, it is not hard to use multiple sections. In fact, using sections is the first challenge at the end of this chapter.

The second required method in the UITableViewDataSource protocol is tableView(_:cellForRowAt:). To implement this method, you need to learn about another class – UITableViewCell.

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

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