DPS923 MAP523 Lab 3

Tabbed application. Simple persistence. Temporary views (alert or action sheet). Controller methods. Fun.

.

DPS923 MAP523 Lab 3 – due Tue Feb 11

Due date: Tuesday, February 11, at 12:00pm noon ET

Grade value: 4% of your final course grade

.

Objective(s)

Work with a tabbed application that manages three views

Use a temporary view (alert or action sheet)

Use simple ‘plist’ persistence

.

Introduction to the problem that you will solve

Your “Air Seneca” app (from Lab 2) needs some new features:

  1. Save the ‘buy tickets’ transactions, so they can be viewed
  2. Reset the ‘seats available’ and purchase history back to initial values

You will learn new topics and techniques in this app. Enjoy!

.

Specifications

Your app will have three scenes, managed by a tab bar controller. The three scenes:

  1. Your existing scene from your Lab 2 “Air Seneca” project
  2. A new scene that displays a list of ‘buy tickets’ transactions
  3. A new scene that enables the user to reset the app’s data to initial values

lab3-tab1-tickets . lab3-tab2-history . lab3-tab3-reset-normal

.

The app will also persist its data after every ‘buy tickets’ transaction.

Read/skim all of this document before you begin working on the lab.

Also, don’t begin by writing code. Instead, get paper and pencil, and sketch out the scenes (views), and write out your coding plan and algorithms. Then you will be ready to write code.

Work on one task at a time, and test it, before moving on. In other words, do not attempt to complete all tasks before you test your work.

Get started by creating an iOS app, using the “Tabbed Application” template. Its name should be “Tickets”.

Here is a preview of the tasks that you will perform:

  1. Add (import) your existing controller and model classes from your Lab 2 “Air Seneca” project
  2. Add a new scene to the storyboard, copy the user interface objects from your Lab 2 project, and connect the objects to your controller’s outlets and actions
  3. Configure the app delegate to initialize and pass on the model object
  4. Change the model class, and add new members (e.g. property, method)
  5. Change the original “Tickets” controller so that it works better
  6. Design a new scene for the ‘buy tickets’ transaction history, and write its controller code
  7. Design a new scene for the ‘reset’ functionality, and write its controller code

.

Debug help

In a recent class session, your professor demonstrated how to add an “exception breakpoint”, which may help debug and troubleshoot. Here’s the procedure again.

In Xcode, on the left-side panel, select the Debug Navigator.

At the bottom of the panel, click the ‘plus’ icon, and select “Add Exception Breakpoint…”:

add-exception-breakpoint

After you do, it appears at the top of the list:

add-exception-breakpoint-result

.

Add (import) your existing controller and model classes from your Lab 2 “Air Seneca” project

After you create a new Tabbed Application, it will have two scenes, managed by a Tab Bar Controller (an instance of UITabBarController).

Now, add your existing controller and model classes from your Lab 2 “Air Seneca” project:

  1. In the Project Navigator, select the group (folder) to which you want to add the existing items
  2. Right-click, and select “Add Files to Tickets…”
  3. Navigate and select ViewController .h and .m, and Model .h and .m, from your existing Lab 2 folder
  4. MAKE SURE that the “Destination” (Copy items into…) check box is check-marked (selected)
  5. Click the Add button to complete the task

In the pre-compile headers (.pch) file, add a statement to import the History.h file, so that your project will build/compile without errors.

We suggest that you rename the controller class. Here’s how:

  1. Open ViewController.h in the editor
  2. Select, then right-click the class name (in the @interface… statement), and choose Refactor > Rename
  3. The new name will be “Tickets”, and yes, “Rename related files”
  4. Read, understand, and then accept the dialogs

Build/compile your project. It should do so without errors.

.

Add a new scene to the storyboard, copy the user interface objects from your Lab 2 project, and connect the objects to your controller’s outlets and actions

On the storyboard, add a new scene, using the “View Controller” from the Object Library.

Select the new scene, and on the Identity Inspector, set the class to your renamed “Tickets” controller class.

Add a segue…

From the tab bar controller, Control+click-and-drag to the new scene:

tab-add-segue-start

From the popup menu, choose Relationship Segue > view controllers:

tab-add-segue-popup

The scene will be connected:

tab-add-segue-result

Now, select the new scene, and select the tab bar area at the bottom. Double-click the title to change it to “Tickets”, and if you wish, choose an icon. (You do not have to choose an icon, and you will not be graded on that. If you’re interested in learning more now about bar icons, this section in the iOS Human Interface Guidelines document will be useful.

On the tab bar controller, notice that the just-added “Tickets” tab is positioned as the right-most icon. Click-and-drag that to the left-most position:

tab-bar-drag-during . tab-bar-drag-result

Now, you need to add – by copying – objects to the new scene’s user interface.

The easiest way to do this is to open your existing “Air Seneca” project, and open the scene in the storyboard editor. Select all user interface objects with a click-and-drag action:

lab3-scene-select-objects

Copy (Command+C). Switch to your Tickets project, click on the new scene, and Paste (Command+V).

All of the items will be pasted, with all of them still selected, which is good. Click-and-drag the items to reposition them where they should be positioned.

Open the Assistant Editor for the scene, and re-connect the user interface objects to the controller outlets and actions.

Build/compile; there should be no errors.

.

Configure the app delegate to initialize and pass on the model object

You have learned how to configure the app delegate to initialize and pass on the model object to the controller that manages the scene in a Single View Application project.

This project is different, because the tab bar controller is the “root” view controller, from the perspective of the app delegate’s window property.

Therefore, we need to add to the app delegate code. Study this diagram to understand how a Tab Bar Controller works in your app, and how to initialize its controllers in the app delegate: (click to open full-size in a new tab/window)

app-delegate-story

Build/compile.

At this point, this new app should work the same as your original “Air Seneca” app.

.

Change the model class, and add new members (e.g. property, method)

In this section, you will make these changes to the Model class.

  1. Build a file system URL to the app’s “Documents” directory
  2. Build a file system URL to a data file that’s located in the app’s “Documents” directory
  3. Keep track of ‘buy tickets’ transactions
  4. In the init method, check for persisted data
  5. Add a method to handle “reset”

.

Build file system URLs

As noted above, the app will persist its data after every ‘buy tickets’ transaction. You were introduced to device storage in this recent document.

You will create two (2) NSURL objects: A file system URL to the app’s “Documents” directory, and a file system URL to a data file that’s located in that directory.

These NSURL objects should be private and usable only by the code that’s in the Model class. In other words, we do not want to create public variables or properties that are visible to users of the Model class. How do we do this?

The answer is to write a class extension at the top of the Model.m implementation, before the “@implementation…” statement. This is a best practice technique; use it whenever you want to create private objects and methods in any class.

Here’s how (click to view it full-size in its own tab/window):

class-extension

For more information, see the content titled “Use Class Extensions to Hide Private Information” in this document.

Next, use the techniques you learned recently to create the file system URLs. This will be the new first task in your Model class’ init method:

file-system-url-create

.

Keep track of ‘buy tickets’ transactions

The next task is to keep track of ‘buy tickets’ transactions. Let’s use a simple string for that. In Model.h, create a declared property, named “purchaseHistory”.

Next, modify the signature of the ‘buy tickets’ method, to add another parameter to accept the “buyMessage”:
… withMessage:(NSString *)buyMessage

The new full signature of the method will now look like this: 

– (void)buyTickets:(int)numberOfTickets onFlight:(int)flightNumber withMessage:(NSString *)buyMessage;

Build/compile. Errors will happen. (Why?) Fix them:

  • Fix the signature in the Model.m implementation
  • In the Tickets controller method that calls the Model object method, pass the buy result message along

Build/compile until there are no errors.

Go back to the Model.m implementation of the ‘buy tickets’ method. At the end of the method block, add some new statements that will do the following:

1. Update the purchaseHistory string, by prepending the incoming ‘buyMessage’ string to the existing purchaseHistory string, separated with two line feeds (\n).

2. Create a new NSDictionary object, with two key-value pairs:

Key is ‘purchaseHistory’, its Value is the string from the purchaseHistory property

Key is ‘seatsAvailable’, its Value is the array from the seatsAvailable property

3. Use the dictionary’s writeToURL: method to persist the data (using the _dataFile NSURL created earlier)

Tip: The return type of writeToURL: is BOOL. For this app, you do not have to use the returned value. In situations like this, you can cast the method call to void if you wish, e.g. (void)[myDictionary writeTo…

.

In the init method, check for persisted data

Edit the Model.m init method. You want to check for persisted data. Why?

If there is persisted data, you will use the data to initialize your seatsAvailable and purchaseHistory objects.

If not, you can assume that the app has been launched for the first time, or has been reset/reinitialized. In that situation, you will initialize these two objects as you did before.

Earlier, you configured file system URLs in the init method. After those statements, add this code block. You will have to provide the rest of the code to make it work correctly. Look at your existing code, and make changes as needed.

init-check-for-data-file

FYI – The NSFileManager class has a fileExistsAtPath: instance method, which requires a string parameter.

The NSURL class has a path method, which returns the string representation of an NSURL object.

.

Add a method to handle “reset”

Add a method named “resetAppData” to Model.h and Model.m to handle the “reset” task that you will configure later. It does not need any parameters.

Its implementation will do the following:

1. Get a reference to the file system manager.

2. Use its removeItemAtURL:error: method. You can specify “nil” as the error argument, as shown here:
[fs removeItemAtURL:_dataFile error:nil];

3. Remove all objects from the seatsAvailable array. Then, add new NSNumber objects (value 40), using the same technique that you used in the init method.

4. Set the purchase history string to an empty string.

.

Change the original “Tickets” controller so that it works better

Think about this scenario: You have been using the app for awhile, and several tickets have been purchased. Therefore, some routes will have less-than-forty seats.

What is the expected behaviour after the user resets the app’s data back to initial values? Well, you would expect that every flight destination will have its seats available reset, and that fact would be immediately visible when switching back to the app’s Tickets tab.

Well, the Tickets tab has not been told that a data change happened. It needs to detect the change. How? Here’s a suggestion:

Implement viewWillAppear: method (which is defined by the Tickets controller’s superclass, UIViewController). This method will be called by the runtime just before the view appears on the screen.

Then, you can check the length of the model object’s purchaseHistory string. If zero, then that means that 1) the app has been launched for the first time, or 2) its data has been reset. In either situation, you want to re-generate the data source for the picker view, and then reload its components.

This code block suggests the coding you would add. The first statement must be a call to the superclass’ method of the same name. The best practice for most situations is simply to call super and pass along the same arguments.

tickets-viewwillappear

.

Design a new scene for the ‘buy tickets’ transaction history, and write its controller code

In Project Navigator, delete the “FirstViewController” .h and .m files from your project. (Choose “Move to Trash”.)

Add a new controller to the project. Its name will be “History”, and it will be a subclass of “UIViewController”:

new-view-controller

It needs a declared property for the Model class.

In the app delegate, pass on the reference to the model object. This controller will be index 1 of the tab bar controller’s viewControllers collection.

On the storyboard, you can either 1) delete the existing “First View” scene, or 2) edit that scene.

If you delete the existing “First View” scene, then you will need to add and configure a new view controller scene from the Object Library, and add and configure a segue from the Tab Bar Controller to the new scene.

Alternatively, you can edit that scene. Delete the existing user interface objects. On the Identity Inspector, set the class to the History controller.

The scene needs 1) a title label, and 2) a text view (UITextView).

The text view needs an outlet in the controller (maybe named “purchaseHistory”). Also, on the text view’s Attributes Inspector, clear (un-check) its “Editable” property, because we do not want the keyboard to show if the user taps on the text view.

How does the purchase history string get into the text view?

Implement the viewWillAppear: method in the controller. This method will be called by the runtime just before the view appears on the screen. The first statement must be a call to the superclass’ method of the same name. The best practice for most situations is simply to call super and pass along the same arguments.

The following code block shows you the coding technique:

typical-viewwillappear

.

Design a new scene for the ‘reset’ functionality, and write its controller code

Add a new controller to the project. Its name will be “Reset”, and it will be a subclass of “UIViewController”. It will be a delegate for an alert or action sheet, so decide which one you will use, and add the protocol inheritance to the @interface… statement.

It needs a declared property for the Model class.

In the app delegate, pass on the reference to the model object. This controller will be index 2 of the tab bar controller’s viewControllers collection. 

Similar to above, on the storyboard, you can either 1) delete the existing “Second View” scene, or 2) edit that scene.

If you delete the existing “Second View” scene, then you will need to add and configure a new view controller scene from the Object Library, and add and configure a segue from the Tab Bar Controller to the new scene.

Alternatively, you can edit that scene. Delete the existing user interface objects. On the Identity Inspector, set the class to the Reset controller.

The scene needs 1) a title label, 2) a button, and 3) a label.

The button needs an action. The action will create an alert (or action sheet), and offer the user only two choices: Cancel, or Yes, reset.

In the delegate method that handles the alert (or action sheet) event, do the right thing: If the user chooses to reset, simply call the method in the model object that resets the values, and display a suitable message in the label, described next.

The label needs an outlet in the controller (maybe named “resetResults”), which should inform the user that the values were reset. Suggestion: Implement viewWillAppear, and set the label’s text to an empty string. The idea is that the label text will be visible immediately after a successful reset, but if the user switches tabs then returns, the message will not be visible.

.

Submitting your work

Follow these instructions to send me your work:

  1. Make sure your project works correctly
  2. Locate your Tickets project folder in Finder
  3. Right-click the folder, and choose Compress “Tickets”, which creates a zip file (make sure the zip file is fairly small, around 500KB or less)
  4. Login to My.Seneca, and in this course’s Assignments area, look for the upload link, and submit your work there

.

.

.

.

.

.

.

.

.

.

.

.

.

Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: