Friday, September 11, 2015

A Simple Swift iOS App from Start to Finish - Implementing the List View's code

Introduction

This is part of a series of articles describing the creation of a simple iOS app. A complete list of the articles is included in the first part A Simple Swift iOS App from Start to Finish - Introduction.

I have set up a web site for the finished app at http://DaysWithoutThings.com and you can download it free directly from the app store:





In this article I will be implementing the basic list view methods to do the following:1. Adding  new  things.
2. Listing all the things that have been defined.
3. Deleting things from the list.



Adding items to a Table View

I already added an Add button to the Navigation Item for my List view. However to make use of it I need to create an IBAction method and link it to the button. To do this I am going to open my Storyboard and select the Add button in the object tree. With that selected I will open the Assistant editor which will automatically open my ListTableViewController swift file.




  To create the IBAction method I will right click on the Add item, which opens its connections list, and then drag from the empty circle next to the Sent Actions selector into the body of my class in the Assistant editor. This opens a prompt to set the name of the method I am creating.




I am going to call my method didTapAdd. I will not be using the sender parameter so I am going to leave its type as the default AnyObject.


Xcode creates an empty method for me. The filled circle  in the margin indicates that it is connected to something.



If I right click the Add object again I can see that the connection is now defined.





The didTapAdd method will create a new thing object with a unique name.  It will do this by using the addThing method of the ThingCollection class I created in my previous article A Simple iOS App from Start to Finish - Creating the Collection class.
That method takes a name string and returns false if a thing with that name already exists or returns true and creates a new thing with that name if it does not. I can use this behavior to create a thing with the name 'new thing x' where x is the first whole number for which a name in this format does not already exist.

Before I can do that I need to give my ListTableViewController class a reference to the global AppDelegate object. I am going to do this in exactly the same way I did in my last article for the Main View.


import Foundation
import UIKit

class ListTableViewController: UITableViewController {
    
    let appDelegate:AppDelegate
    
    required init(coder aDecoder: NSCoder) {
        appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
        super.init(coder: aDecoder)
    }
    
    @IBAction func didTapAdd(sender: AnyObject) {
    }

}


I can now create a method to return a thing object with a new unique name.


    func createNextNewThing() {
        var number = 1
        var name = ""
        var thingCollection = appDelegate.thingCollection!
        
        do {
            name = "new thing \(number)"
            number++
        } while thingCollection.addThing(name) == false

    }

I will call this from my didTapAdd method.

    @IBAction func didTapAdd(sender: AnyObject) {
        createNextNewThing()

    }

My app is now capable of adding new things to its list. However it will not display any of then until I implement the TableView delegate method.


Listing all the things that have been defined

To actually display anything in a Table View you need to create a data source for it.  The data source is a class that inherits the UITableViewDataSource protocol. I could create a separate class for this. However, as my app is so small, I am just going to create an extension to my ListTableViewController class. Creating it as an extension rather than just adding it to the existing class definition helps keep the code organized within Xcode. (Swift does not support the pragma marks that can be used with Objective-C to group methods within a class definition (Thanks to this Stackoverflow thread for this idea.). 



class ListTableViewController: UITableViewController {
    ...
}

extension ListTableViewController: UITableViewDataSource {

UITableViewDataSource

The UITableViewDataSource protocol has two required methods. One to return the number of rows in each section of the table and one to return cell objects for each row. 


extension ListTableViewController: UITableViewDataSource {
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
    }
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        
    }

}

For my app the return values for both of these methods will be based on the contents of my ThingCollection object. The tableView method with the numberOfRowsInSection parameter is just going to return the number of things from my thinCollection object. 


    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return appDelegate.thingCollection!.things.count

    }

My app only needs one section in its tableview. However it is possible to have more than one section by overriding an optional method of the UITableViewDataSource protocol called numberOfSectionsInTableView.

The cellForRowAtIndexPath method is a little more complicated. It is called once for each row in the table every time the tableview's reloadData method is called.


    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("ThingCell", forIndexPath: indexPath) as UITableViewCell
        var thing = appDelegate.thingCollection!.things[indexPath.row]
        
        cell.textLabel.text = thing.name
        return cell

    }


This first line use the tableView's dequeueReusableCellWithIdentifier method to find suitable cell object to use. The UITableView class caches the cell objects that are created for it in this method and will attempt to reuse them whenever possible. The dequeueReusableCellWithIdentifier will either return a cached cell object or create a new one automatically if none is available.
The reuseIdentifer parameter is used to test if any available cells are compatible. I defined "ThingCell" as my reuse identifier in my storyboard in my previous article A Simple Swift iOS App from Start to Finish - Creating the List View.
The IndexPath is an object that holds the row and section number for the requested cell. In the next line I use the row from this as the index to retrieve a thing object from the array my collection class holds.

In the third line I assign the name of the thing object I just retrieved to the text label of the cell. For this to work I need to go back to my storyboard and set the cell Style for the prototype cell to Basic. This will cause the dequeueReusableCellWithIdentifier method to create cells with an embedded text label.










If I run the app now and click the add button nothing appears to happen.





However if I then rerun the app it stays on the main display view and if I tap that to move to the list view I see I now have a new thing in my list.







The reason I did not see my new thing until after I restarted the app was that the didTapAdd method only created the new thing in the store. In order for it to be displayed in my list view   the didTapAdd method also needs to force both the ThingCollection and the TableView to reload their data.

    @IBAction func didTapAdd(sender: AnyObject) {
        createNextNewThing()
        reloadData()
    }
    
    func reloadData() {
        appDelegate.thingCollection?.reloadData()
        tableView.reloadData()

    }

Now if I run my app and add another thing it appears in the list immediately.



Deleting things from the list


The UITableView class has a built in delete function. When enabled the user just swipes left on the item to be deleted and a delete button is displayed.
To enable this I need it override another UITableViewDataSource method in my extension.

    
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {

    }

I found the method definition by starting to they the method name tableView and then picked the full definition from the autocomplete list.





    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        var thingCollection = appDelegate.thingCollection!
    
        thingCollection.deleteThing(thingCollection.things[indexPath.row])
        
        tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
        
    }

In this method I take a reference to my thingCollection and then delete the thing that corresponds to the row from the indexPath parameter. Finally I tell the tableview to delete the row from the display using an animation. Its important to delete the thing from the thingCollection first as the tableview will throw an exception if the number of rows has not decreased by one since the table was last redrawn.

Wrapping up

When I run my app now I can add and remove things from my list view.





In my next article  I will implement the code for the edit view that will allow me to customize the things my app creates.

Next:       A Simple Swift iOS App from Start to Finish - Implementing the Edit View's Code

No comments:

Post a Comment