1. Trang chủ
  2. » Công Nghệ Thông Tin

more iphone 3 development phần 2 docx

57 471 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề More iPhone 3 Development Phần 2
Trường học Apress
Chuyên ngành iPhone 3 Development
Thể loại sách hướng dẫn
Năm xuất bản 2009
Định dạng
Số trang 57
Dung lượng 1,06 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Application Architecture As you can see from Figure 3-1, we’re going to create an application with both a tab bar and a navigation controller.. We need to know, for example, whether our

Trang 1

41

A Super Start: Adding,

Displaying, and Deleting

Data

Well, if that last chapter didn’t scare you off, then you’re ready to dive in and move

beyond the basic template we explored in Chapter 2

In this chapter, we’re going to create an application designed to track some superhero

data Our application will be based on the Window-based Application template We’ll

use the data model editor to design our superhero entity And then we’ll create a new

controller class, derived from UIViewController, that will allow us to add, display, and

delete superheroes In Chapter 4, we’ll extend our application further and add code to

allow the user to edit their superhero data

Take a look at Figure 3-1 to get a sense of what our app will look like when it runs

Looks a lot like the template app The major differences lie in the entity at the heart of

the application and in the addition of a tab bar at the bottom of the screen Let’s get

to work

3

Trang 2

Figure 3-1 The SuperDB application as it will look once we’ve finished this chapter

Setting up the Xcode Project

Time to get our hands dirty Launch Xcode if it’s not open, and type N to bring up our old friend, the new project assistant (Figure 3-2)

Figure 3-2 Our dear old friend, Xcode’s new project assistant

Trang 3

In the last chapter, we started with the Navigation-based Application template When

you create your own navigation applications, that’s a good template to use, as it gives

you a lot of the code you’re likely to need in your application However, to make it easier

to explain where to add or modify code and also to reinforce your understanding of how

applications are constructed, we’re going to build the SuperDB application from scratch,

just as we did throughout most of Beginning iPhone 3 Development (Apress, 2009)

Select Window-based Application, and make sure that the Use Core Data for storage

check box is checked When prompted for a project name, type in SuperDB

When the project window appears, expand both the Classes and the Resources groups

to make it easier to get to the main files with which we’ll be working

Application Architecture

As you can see from Figure 3-1, we’re going to create an application with both a tab bar

and a navigation controller Before we start writing code, we need to put a little thought

into our application’s structure We need to know, for example, whether our

application’s root view controller will be a navigation controller, tab bar controller, or

something else entirely

There’s not a single right architecture for every application One obvious approach

would be to make the application’s root view controller a UITabBarController, and then

add a separate navigation controller for each tab In a situation where each tab

corresponds to a completely different view showing different types of data, that

approach would make perfect sense In Beginning iPhone 3 Development, in Chapter 7,

we used that exact approach because every single tab corresponded to a different view

controller with different outlets and different actions

In our case, however, we’re going to implement two tabs (with more to be added in later

chapters), but each tab will show exactly the same data, just ordered differently When

one tab is selected, the table will be ordered by the superhero’s name If the other tab is

selected, the same data will be shown, ordered by the superhero’s secret identity

Regardless of which tab is selected, tapping a row on the table will do the same thing:

drill down to a new view where you can edit the information about the superhero you

selected (which we will add in the next chapter) Regardless of which tab is selected,

tapping the add button will add a new instance of the same entity When you drill down

to another view to view or edit a hero, the tabs are no longer relevant

For our application, the tab bar is just modifying the way the data in a single table is

presented There’s no need for it to actually swap in and out other view controllers Why

have multiple navigation controller instances all managing identical sets of data and

responding the same way to touches? Why not just use one table controller, and have it

change the way it presents the data based on which tab is selected? That’s the

approach we’re going to take in this application As a result, we won’t be using

UITabBarController at all

Trang 4

Our root view controller will be a navigation controller, and we’ll use a tab bar purely to receive input from the user The end result that is shown to the user will be identical to what they’d see if we created separate navigation controllers and table view controllers for each tab, but behind the scenes, we’ll be using less memory and won’t have to worry about keeping the different navigation controllers in sync with each other

Our application’s root view controller will be an instance of UINavigationController We’ll create our own custom view controller class, HeroListViewController, to act as the root view controller for this UINavigationController HeroListViewController will display the list of superheroes along with the tabs that control how the heroes are displayed and ordered

Here’s how the app will work When the application starts, the UINavigationController instance is created from the nib file and the navigation controller’s view is added as a subview to the application’s window so it can be seen The rest of the window will be taken up by a content pane for its subcontroller views Next, the instance of

HeroListViewController will be loaded from the nib, and the view from its associated nib file will be added as a subview to the navigation controller’s content pane This view (the one associated with HeroListViewController) contains our tab bar and our

superhero table view

In Chapter 4, we’ll add a table view controller into the mix that implements a detail superhero view When the user taps on a superhero in the superhero list, this detail controller will be pushed onto the navigation stack and its view will temporarily replace the HeroListViewController’s view in the UINavigationController’s content view No need to worry about the detail view now, we just wanted you to see what’s coming

Modifying the Application Delegate Interface

Given our approach, we need to declare an outlet to our application’s root view

controller on our application delegate Single-click on SuperDBAppDelegate.h and add

the code in bold:

@interface SuperDBAppDelegate : NSObject <UIApplicationDelegate> {

@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;

@property (nonatomic, retain, readonly) NSManagedObjectContext

Trang 5

@property (nonatomic, retain) IBOutlet UINavigationController *navController;

- (NSString *)applicationDocumentsDirectory;

@end

As you probably realized, the navController outlet will point to an instance of

UINavigationController that will act as our application’s root view controller Other view

controllers will be pushed onto the navigation stack when they need to be displayed,

and will be popped off of the stack when they are done

Adding to the Application Delegate Implementation

Before we head over to Interface Builder, let’s quickly finish up with our Application

delegate by adding the following code at the beginning of SuperDBAppDelegate.m:

// Override point for customization after app launch

[window insertSubview:navController.view atIndex:0];

[window makeKeyAndVisible];

}

There shouldn’t be too much there that’s unfamiliar to you We synthesize our new

property, just as we always do In our applicationDidFinishLaunching: method, we add

the view property from navController, our application’s root view controller, as a

subview of contentView so that it will be displayed to the user

Now, scroll down to the bottom of SuperDBAppDelegate.m We need to add a few lines

to the dealloc method to make sure we’re being good memory citizens Make the

following additions at the bottom of the file:

Trang 6

@end

Make sure you save both SuperDBAppDelegate.h and SuperDBAppDelegate.m before

continuing

Creating the Table View Controller

Our application’s root view controller is going to be a stock UINavigationController, so

we don’t need to define a class for the app’s root view controller, but we do need to create a controller class to display the list of heroes and act as the root of the navigation controllers’ stack Even though we will be using a table to display the list of heroes, we’re not going to subclass UITableViewController Because we also need to add a tab bar to our interface, we’re going to create a subclass of UIViewController and create our interface in Interface Builder The table that will display the list of heroes will be a subview of our view controller’s content pane

Single-click the Classes folder in the Groups & Files pane, then type N to bring up the

new file assistant or select New File… from the File menu

When the new file assistant pops up (Figure 3-3), select Cocoa Touch Class from under the iPhone OS heading in the upper-left pane, then select UIViewController subclass from the upper-right pane Now make sure the UITableViewController subclass check box is not checked, but the check box labeled With XIB for user interface check box is checked since, unlike with most table-based views, we will need a nib file With that done, click the Next button

Figure 3-3 Selecting the Objective-C subclass template in the new file assistant

Trang 7

When prompted for a filename, type in HeroListViewController.m and make sure the

check box labeled Also create “HeroListViewController.h” is checked Press return to

add the files to your project After the files are created, click and drag

HeroListViewController.xib from the Classes folder, where Xcode created it, to the

Resources folder where it belongs

For now, that’s all we need in this controller class In order to create an instance of the

class in Interface Builder, we first needed the class definition to exist in Xcode

Setting up MainWindow.xib

Interface Builder should now be open and should look something like Figure 3-4 You’re

probably well-acquainted with Interface Builder by now, but let’s just quickly review the

names of the various windows so that we’re all on the same page The top-left window,

the one with MainWindow.xib in the title bar, is the nib file’s main window Below that,

the window with the imaginative name of Window represents our application’s one and

only instance of UIWindow Double-clicking the Window icon in the nib file’s main window

will reopen this if it gets closed

The window with the small title bar to the right of the nib’s main window is the

context-sensitive Inspector where you can change the attributes of whatever item is currently

selected in the active window And finally, the right-most window is the Library, which

contains pre-configured items that you can add to a nib

Figure 3-4 MainWindow.xib in Interface Builder

Trang 8

In the library, select the Controllers folder in the top-most pane (inside Library, then inside Cocoa Touch) With Controllers selected in the top pane, look in the middle pane for the Navigation Controller icon (Figure 3-5) Drag one of these to your nib file’s main

window Once you do that, your nib’s main window will gain an additional icon called

Navigation Controller (or Navigation Co… if you’re in icon view mode, which truncates

longer names), and a new window should have just popped up (Figure 3-6)

Figure 3-5 The Navigation Controller icon Depending on the version of Interface Builder you are using, the

Library may default to displaying items in one of two ways You might see just the icon (left), or the icon and a short description (right) You can change how the library items are displayed by right-clicking on the middle pane

Figure 3-6 Adding a Navigation Controller to your nib causes a new window to pop up

The new window has a grey rounded rectangle with a dashed outline labeled View and a title of Root View Controller This is Interface Builder’s way of reminding us that a

navigation controller needs at least one child view controller in order to function We can

Trang 9

set the root view controller right here in Interface Builder The easiest way to do this is to

put our nib’s main window in list mode by clicking the middle of the three View Mode

icons (Figure 3-7)

Figure 3-7 The nib’s main window in list view mode

With your nib in list view mode, you should notice that Navigation Controller has a

disclosure triangle next to it That means it has sub-items of some form Different items

can contain different types of sub-items Instances of view classes, for example, can

contain subviews View controller classes generally have either the views they control or

the subordinate view controllers they’re responsible for managing (or both) Expand

Navigation Controller by single-clicking its disclosure triangle Underneath it, you’ll find a

navigation bar instance, and a view controller with the rather long and unwieldy name of

View Controller (Root View Controller) The view controller represents the navigation

controller’s root view controller As we said earlier, HeroListViewController was

designed to act as the navigation controller’s root view controller We need to change

the class of the root view controller to HeroListViewController

Single-click View Controller (Root View Controller) and press 4 to bring up the

identity inspector (Figure 3-8) Change the underlying class in the identity inspector

from UIViewController to HeroListViewController This will cause a single instance of

HeroListViewController to get created when our application launches

Trang 10

Figure 3-8 The identity inspector allows us to change the underlying class for the navigation controller’s root

view controller to our custom controller class

Now we’ve got a navigation controller and an instance of our custom controller class in our nib

Connecting the Outlets

Earlier we created an outlet in our application delegate for the navigation controller We’ve added an instance of UINavigationController to our nib, so let’s connect the

outlet Control-drag from SuperDB App Delegate in the nib’s main window to the

Navigation Controller also in the nib’s main window When the black menu pops up,

select the outlet called navController to connect that outlet

And with that, we have received final clearance to land our nib Save and head on back

to Xcode to pick up your luggage

Designing the Data Model

As we discussed in Chapter 2, Xcode’s data model editor is where you design your

application’s data model In your project window’s Resources group, single-click on

SuperDB.xcdatamodel This should bring up the data model editor (Figure 3-9)

Trang 11

Figure 3-9 The empty data model editor awaiting your application’s data model

Unlike the template we used in Chapter 2, this template provides us with a completely

empty data model, so we can just dive right in and start building without deleting

anything The first thing we need to add to our data model is an entity Remember,

entities are like class definitions Although they don’t store any data themselves, without

at least one entity in your data model, your application won’t be able to store any data

Adding an Entity

Since the purpose of our application is to track information about superheroes, it seems

logical that we’re going to need an entity to represent a hero We’re going to start off

simple in this chapter and track only a few pieces of data about each hero: their name,

secret identity, date of birth, and sex We’ll add more data elements in future chapters,

but this will give us a basic foundation upon which to build

Trang 12

In the entity pane, which is the upper-left pane of the data model editor, you should notice buttons with a plus and a minus icon in the lower-left corner (Figure 3-10) As you might have guessed, the button with the plus icon adds a new entity to the data model, and the button with the minus icon removes the currently selected one Since there’s

no entity to delete, the minus button is disabled Click the plus button now to add a new entity

Figure 3-10 The plus and minus buttons in the entity pane allow you to add and remove entities from the data

model

As soon as you click the plus button, a new entity, named Entity, should appear in the

entity pane This entity should be selected for you automatically, which means that the detail pane in the upper-right corner of the data model editor lists details about this new entity and the entity will be selected in the editing pane at the bottom of the data model editor (Figure 3-11)

Editing the New Entity

Now that you’ve now added an entity to your data model, you’ll need to change its name The easiest way to do that is to change it in the detail pane Conveniently enough,

the Name text field in the detail pane is highlighted and has the focus, so you can just start typing the new name to change the entity’s name Type Hero

Below the Name field in the detail pane is a text field called Class Leave this at the default value of NSManagedObject In Chapter 6, you’ll see how to use this field to

create custom subclasses of NSManagedObject to add functionality

Below that is a pop-up menu labeled Parent Within a data model, you have the ability to

specify a parent entity, which is very similar to subclassing in Objective-C When you

specify another entity as your parent, the new entity receives all the properties of Parent

along with any additional ones that you specify

Trang 13

Figure 3-11 After clicking the plus button in the entity pane, the entity pane gets a new selected row called

Entity, the diagram view shows the new entity as a rounded rectangle, and the detail pane shows information

about the selected entity

Below the Parent pop-up menu is a check box called Abstract This check box allows

you to create an entity that cannot be used to create managed objects at runtime The

reason you might create an abstract entity is if you have several properties that are

common to multiple entities In that case, you might create an abstract entity to hold the

common fields and then make every entity that uses those common fields a child of that

abstract entity Doing that would mean that if you needed to change those common

fields, you’d only need to do it in one place

Leave the parenting pop-up set to No Parent Entity and leave the Abstract check box

unchecked

Trang 14

NOTE: You may be wondering about the button bar in the upper-left of the detail pane These

buttons give you access to more advanced configuration parameters that are only rarely used

We won’t be changing any of the configuration options except those visible when the General

button is selected (the default button, the one we’re on now)

If you’re interested in finding out more about these advanced options, you can read more about them in the Core Data Programming Guide at http://developer.apple.com/

documentation/Cocoa/Conceptual/CoreData/ and the Core Data Model Versioning and Data Migration Guide at http://devworld.apple.com/documentation/

Cocoa/Conceptual/CoreDataVersioning/index.html

Adding Attributes to the New Entity

Now that we have an entity, we have to give it attributes in order for managed objects based on this entity to be able to store any data For this chapter, we need four

attributes: name, secret identity, birth date, and sex

In the data model editor, to the right of the entity pane is the property pane This is where you can add properties, including attributes, to the currently selected entity In the lower-left of the property pane, you should see buttons similar to the ones in the lower-left of the entity pane Because there is more than one type of property, the button with the plus on it also has a little triangle on it as well This indicates that when you click the button, you will get a pop-up menu asking you to select exactly which type of property you want to add Let’s add our four attributes now

Adding the Name Attribute

Single-click on the plus button in the property pane Once you click on it, you will be presented with a drop-down menu that looks like Figure 3-12 Since we want to add an attribute, select Add Attribute from the menu

Figure 3-12 Clicking the plus button in the property pane gives you a menu from which you can select the type

of property you wish to add

Trang 15

Editing the Attribute

The Hero entity should now have an attribute called newAttribute Just as when you

created a new entity, the newly added attribute has been automatically selected for you,

which also causes its information to be displayed in the detail pane Also just like before,

the Name field should have focus, so you can just type the new name for the attribute

Type name now so that your detail pane looks like Figure 3-13

Figure 3-13 The detail pane after typing the new attribute’s name

TIP: It’s not an accident that we chose to start our entity Hero with a capital H, but our attribute

name with a lowercase n This is the accepted naming convention for entities and properties

Entities begin with a capital letter, properties begin with a lowercase letter In both cases, if the

name of the entity or property consists of more than one word, the first letter of each new word

is capitalized

Below the Name field are three check boxes: Optional, Transient, and Indexed If

Optional is checked, then this entity can be saved even if this attribute has no value

assigned to it If we uncheck it, then any attempt to save a managed object based on

this entity when the name attribute is nil will result in a validation error that will prevent

the save In this particular case, name is the main attribute that we will use to identify a

given hero, so we probably want to require this attribute Single-click the Optional check

box to uncheck it, making this field required

The second check box, Transient, allows you to create attributes that are not saved in

the persistent store They can also be used to create custom attributes that store

non-standard data For now, don’t worry too much about Transient Just leave it unchecked

and we’ll revisit this check box in Chapter 6

The final check box, Indexed, tells the underlying data store to add an index on this

attribute Not all persistent stores support indices, but the default store (SQLite) does

The database uses an index to improve search speeds when searching or ordering

based on that field We will be ordering our superheroes by name, so lets check the

Indexed check box to tell SQLite to create an index on the column that will be used to

store this attributes data

Trang 16

CAUTION: Properly used, indices can greatly improve performance in a SQLite persistent store

Adding indices where they are not needed, however, can actually degrade performance If you

don’t have a reason for selecting Indexed, leave it unchecked

Attribute Types

Every attribute has a type, which identifies the kind of data that the attribute is capable

of storing If you single-click the Type drop-down (which should currently be set to

Undefined), you can see the various datatypes that Core Data supports out of the box

(Figure 3-14) These are all the types of data that you can store without having to

implement a custom attribute, like we’re going to do in Chapter 6 Each of the datatypes

correspond to an Objective-C class that is used to set or retrieve values and you must

make sure to use the correct object when setting values on managed objects

Figure 3-14 The datatypes supported by Core Data

The Integer Datatypes

Integer 16, Integer 32, and Integer 64 all hold signed integers (whole numbers) The only

difference between these three number types is the minimum and maximum size of the

values they are capable of storing In general, you should pick the smallest-size integer

that you are certain will work for your purposes For example, if you know your attribute

will never hold a number larger than a thousand, make sure to select Integer 16 rather

than Integer 32 or Integer 64 The minimum and maximum values that these three

datatypes are capable of storing is as follows:

Integer 32 2,147,483,648 2,147,483,647 Integer 64 9,223,372,036,854,775,808 9,223,372,036,854,775,807

Trang 17

At runtime, you set integer attributes of a managed object using instances of NSNumber

created using a factory method such as numberWithInt:, or numberWithLong:

The Decimal, Double, and Float Datatypes

The Decimal, Double, and Float datatypes all hold decimal numbers Double and Float

hold floating-point representations of decimal numbers similar to the C datatypes of

double and float, respectively Floating-point representations of decimal numbers are

always an approximation due to the fact that they use a fixed number of bytes to

represent data The larger the number to the left of the decimal point, the less bytes

there are available to hold the fractional part of the number The Double datatype uses

64 bits to store a single number while the Float datatype uses 32 bits of data to store a

single number For many purposes, these two datatypes will work just fine However,

when you have data, such as currency, where small rounding errors would be a

problem, Core Data provides the Decimal datatype, which is not subject to rounding

errors The Decimal type can hold numbers with up to 38 significant digits stored

internally using fixed-point numbers so that the stored value is not subject to the

rounding errors that can happen with floating-point numbers

At runtime, you set Double and Float attributes using instances of NSNumber created

using the NSNumber factory method numberWithFloat: or numberWithDouble: Decimal

attributes, on the other hand, must be set using an instance of the class

NSDecimalNumber

The String Datatype

The String datatype is one of the most common attribute types you will use String

attributes are capable of holding text in nearly any language or script since they are

stored internally using Unicode String attributes are set at runtime using instances of

NSString

The Boolean Datatype

Boolean values (YES or NO) can be stored using the Boolean datatype Boolean attributes

are set at runtime using instances of NSNumber created using numberWithBOOL:

The Date Datatype

Dates and timestamps can be stored in Core Data using the Date datatype At runtime,

Date attributes are set using instances of NSDate

The Binary Datatype

The Binary datatype is used to store any kind of binary data Binary attributes are set at

runtime using NSData instances Anything that can be put into an NSData instance can be

Trang 18

stored in a Binary attribute However, you generally can’t search or sort on binary

datatypes

The Transformable Datatype

The Transformable datatype is a special datatype that works along with something called a value transformer to let you create attributes based on any Objective-C class,

even those for which there is no corresponding Core Data datatype You would use Transformable datatypes to store a UIImage instance, for example, or to store a UIColor instance You’ll see how Transformable attributes work in Chapter 6

Setting the Name Attributes’s Type

A name, obviously, is text, so the obvious type for this attribute is String Select String from the Type drop-down After selecting it, a few new fields will appear in the detail

pane (Figure 3-15) Just like Interface Builder’s inspector, the detail pane in the data

model editor is context-sensitive Some attribute types, such as the String type, have

additional configuration options

Figure 3-15 The detail pane after selecting the String type

The Min Length: and Max Length: fields allow you to set a minimum and maximum

number of characters for this field If you enter a number into either field, any attempt to

save a managed object that has less characters than the Min Length: or more characters than Max Length: stored in this attribute will result in a validation error at save time

Note that this enforcement happens in the data model, not in the user interface Unless you specifically enforce limitations through your user interface, these validations won’t happen until you actually save the data model In most instances, if you enforce a minimum or maximum length, you should also take some steps to enforce that in your user interface Otherwise, the user won’t be informed of the error until they go to save, which could be quite a while after they’ve entered data into this field You’ll see an example of enforcing this in Chapter 6

The next field is labeled Reg Ex.: and that stands for regular expression This field

allows you to do further validation on the entered text using regular expressions, which

Trang 19

are special text strings that you can use to express patterns You could, for example,

use an attribute to store an IP address in text and then ensure that only valid numerical

IP addresses are entered by entering the regular expression \b\d{1,3}\.\d{1,3}\.\d{1,3}\

\d{1,3}\b We’re not going to use regular expressions for this attribute, so leave the Reg

Ex field blank

NOTE: Regular expressions are a very complex topic on which many full books have been

written Teaching regular expressions is way beyond the scope of this book, but if you’re

interested in using regular expressions to do data model-level validation, a good starting point is

the Wikipedia page on regular expressions at http://en.wikipedia.org/wiki/

Regular_expression, which covers the basic syntax and contains links to many regular

expression-related resources

Finally, you can use the field labeled Default Value: to, well, set a default value for this

property If you type a value into this field, any managed object based on this entity will

automatically have its corresponding property set to whatever value you type in here

So, in this case, if you were to type Untitled Hero into this field, any time you created a

new Hero managed object, the name property would automatically get set to Untitled

Hero Heck, that sounds like a good idea, so type Untitled Hero into this field Then, for

good measure, save

Adding the Rest of the Attributes

Our Hero entity needs three more attributes, so let’s add them now Click the plus

button in the properties pane again and select Add Attribute once more Give this one a

name of secretIdentity and a type of String Since, according to Mr Incredible, every

superhero has a secret identity, we’d better uncheck the Optional check box We will be

sorting and searching on secret identity, so check the Indexed box For Default Value:,

type in Unknown Because we’ve made the field mandatory by unchecking the Optional

check box, it’s a good idea to provide a default value Leave the rest of the fields as is

CAUTION: Be sure you enter default values for the name and secretIdentity attributes If

you don’t, the program will behave badly If your program crashes, check to make sure you’ve

saved your source code files and your nib files

Click the plus button a third time to add yet another attribute, giving it a name of

birthdate and a type of Date Leave the rest of the fields at their default values for this

attribute We may not know the birthdate for all of our superheroes, so we want to leave

this attribute as optional As far as we know now, we won’t be doing a lot of searching

or ordering on birthdate, so there’s no need to make this attribute indexed We could do

some additional validation here by setting a minimum, maximum, or default date, but

there really isn’t much need There’s no default value that would make sense, and

Trang 20

setting a minimum or maximum date would preclude the possibility of an immortal superhero or a time-traveling one, which we certainly don’t want to do!

That leaves us with one more attribute for this first iteration of our application: sex There are a number of ways that we could choose to store this particular piece of information For simplicity’s sake (and because it will help us show you a few helpful techniques in

Chapter 6), we’re just going to store a character string of either Male or Female Add another attribute and select a Type of String Let’s leave this as an optional setting—

there might just be an androgynous masked avenger or two out there We could use the

regular expression field to limit inputs to either Male or Female but, instead, we’re going

to enforce that in the user interface by presenting a selection list rather than enforcing it here in the data model

Guess what? You’ve now completed the data model for the first iteration of the SuperDB

application Save it and let’s go create our controller

Creating HeroListViewController

If you look back at Figure 3-1, you can see that our application displays a list of heroes, and it can sort that list by either name or secret identity As we discussed earlier in the chapter, we’re using a single controller to handle both of the sort options rather than using separate controllers for each one In order to retrieve the results from our

persistent store, we’re going to use a fetched results controller just as the template code

we looked at in the last chapter did However, we are not using a table view controller,

so we have to design our user interface in our nib Before we do that, though, we should declare the outlets that we’re going to need

Declaring the Fetched Results Controller

Single-click on HeroListViewController.h to bring up the header file for our class We

need to declare our property and instance variable for the fetched results controller, so make the following changes to your file:

@interface HeroListViewController : UIViewController

<UITableViewDelegate, UITableViewDataSource, UITabBarDelegate,

Trang 21

NSFetchedResultsController *_fetchedResultsController;

}

@property (nonatomic, retain) IBOutlet UITableView *tableView;

@property (nonatomic, retain) IBOutlet UITabBar *tabBar;

@property (nonatomic, readonly) NSFetchedResultsController

*fetchedResultsController;

- (void)addHero;

- (IBAction)toggleEdit;

@end

This looks just a little different than what we’ve done before, so let’s discuss what’s

going on here First, we define a constant that will be used as a key to store and retrieve

a preference value in the user defaults When our program launches, we want to take the

user back to the same tab they were on when they last used the program This constant

will be used to store that information in our application’s preferences

After that, we define an enumeration that gives us constants for the individual tabs used

in the tab bar, just to make our code a bit more readable The number 0 can mean lots

of different things in the context of our code, but the constant kByName makes it obvious

that this time, it’s referring to the tab called By Name

Next, we conform our class to a whole bunch of protocols Because we’re not

subclassing UITableViewController, we have to manually conform to

UITableViewDelegate and UITableViewDataSource We also conform to

UITabBarDelegate because we’re also going to act as the tab bar delegate Doing so will

cause us to be notified whenever the user selects a new tab without having to utilize

action methods

If we encounter a fatal error, we’re going to show the user an alert before quitting, so we

have to become the alert’s delegate in order to be notified when the alert is dismissed

That’s why we also need to conform to UIAlertViewDelegate The template code just

logs errors to the console and quits, but we’re going to be a little more user-friendly than

that and let the user know when something has gone wrong

Finally, we conform to NSFetchedResultsControllerDelegate because we’re going to be

using a fetched results controller and will need to be notified when its data changes

After that, we create instance variables to serve as outlets for the tab bar and table view

Then, we specify the @private keyword, which indicates that all instance variables that

follow have a private scope and cannot be accessed directly by other classes We then

create a private instance variable in which to store our fetched results controller Notice

that we’ve called the instance variable _fetchedResultsController, yet if you look down

a few lines later, the property is actually named fetchedResultsController, without the

underscore

By default, properties expect their underlying instance variable to have the same name

as the property However, that is just the default behavior and is not required When you

synthesize your property in the implementation file using the @synthesize keyword, you

can specify the name of the underlying instance variable to be used to store the

Trang 22

property’s data The specified name can be anything at all It doesn’t need to be related

to or similar to the property name at all

When we synthesize this property, we’ll use this line of code:

@synthesize fetchedResultsController=_fetchedResultsController;

The property name goes immediately after the @synthesize keyword, just as always, but

it is then followed by an equal sign and then the name of the instance variable to be used This particular convention of using the same name as the property but prefixed with an underscore is one you see a lot, even in Apple’s sample code Some

programmers use this naming convention for all of their properties We tend to use it only when there’s a specific reason to not want other objects mucking with an instance variable

This naming convention should prevent us from accidentally confusing the property and instance variable in our code By using different names for each, we are far less likely to access the instance variable directly when we intend to use the property

You might remember from the last chapter that our fetchedResultsController was lazily loaded As a result, it is critical that references to the fetchedResultsController be done through the accessor, since the accessor will make sure that our

fetchedResultsController was properly loaded We’re going to be doing the same thing

in this chapter This naming convention and the use of the @private keyword will help prevent unintentional direct access to the instance variable that could cause problems if the fetched results controller hasn’t been loaded by an earlier use of the accessor

NOTE: You may hear developers claim that using the underscore prefix is reserved by Apple and

that you shouldn’t use it This is a misconception Apple does, indeed, reserve the underscore prefix for the names of methods It does not make any similar reservation when it comes to the names of instance variables You can read Apple’s naming convention for instance variables, which makes no restriction on the use of the underscore, here:

http://developer.apple.com/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingIvarsAndTypes.html

Notice that the fetchedResultsController property is declared with the readonly

keyword We will be lazily loading the fetched results controller in the accessor method

We do not want other classes to be able to set fetchedResultsController, so we declare it readonly to prevent that from happening

SYNTHESIZED INSTANCE VARIABLES

There’s a new feature of the Objective-C 2.0 runtime that hasn’t been talked about much and that we

haven’t had you use at all It’s called synthesized instance variables The Objective-C 2.0 runtime will

actually create instance variables for you if you declare a property and don’t give it an underlying instance variable So, for example, this is a perfectly valid class interface:

Trang 23

Notice that there's no instance variable declaration for testString; it's not needed Well, that is, it’s not

needed if you’re compiling 64-bit Cocoa applications or iPhone applications

Unfortunately, though, the iPhone simulator is a 32-bit Mac application, and 32-bit Mac applications

cannot take advantage of instance variable synthesis That means iPhone programs running in the

simulator cannot take advantage of this feature either In other words, the following class is a perfectly

valid class when compiled for the device, but will fail with errors when compiled against the simulator:

You could utilize this feature on the iPhone and still create programs that can compile on the simulator by

using platform macros, like this:

But, there's a catch Synthesized instance variables cannot be accessed directly, even from within your

class You have to use the accessor and mutator methods everywhere

So, what's the advantage of not declaring the underlying instance variable? On the iPhone, it doesn't save

you any typing unless you never, ever run your program in the simulator That means that for most iPhone

developers, there really is no advantage to using this feature right now In the future, it is possible, and

maybe even likely, that there will be compiler optimizations behind the scenes that your program will take

advantage of if you've let the runtime create your instance variables for you As of right now, however,

there’s no real advantage to using this feature unless you plan to always test on an iPhone, in which case,

it can save you a little typing

Drag Two Icons to Your Project

Before writing our implementation of HeroListViewController, we need to head over to

Interface Builder to design the interface and connect our outlets Before we do that,

however, you need to copy two image files into your Xcode project so that they’ll be

available to you in Interface Builder If you look in the project archive that accompanies

this book, in the 03 - SuperDB folder, you’ll find files called name_icon.png and

secret_icon.png These are the images that you will use on the two tabs Add them both

Trang 24

to your project in the Resources group Once you’ve done that, you can double-click

HeroListViewController.xib to open up Interface Builder

Designing the HeroListViewController Interface

When the nib file opens, the View window should show up If it doesn’t, double-click the

View icon in the nib’s main window to open it We need to add a tab bar and a table

view to our nib, and then make the connections

Let’s add the tab bar first Look in the Library for a tab bar (Figure 3-16) Make sure you’re grabbing a tab bar and not a tab bar controller We only want the user interface item

Figure 3-16 The tab bar in the Library

Drag a tab bar from the library to the window called View, and place it snugly in the bottom of the window, as we’ve done in Figure 3-17

Figure 3-17 The tab bar placed snugly against the bottom of the screen

Trang 25

The default tab bar has two tabs, which is exactly the number we want Let’s change the

icon and label for each With the tab bar still selected, click on the star above Favorites

and then press 1 to bring up the attribute inspector

If you’ve correctly selected the tab bar item, the inspector window should have the title

Tab Bar Item Attributes and the Identifier pop-up should say Favorites In the attribute

inspector, give this tab a Title of By Name, and an Image of name_icon.png (Figure

3-18) Now click on the three dots above the word More on the tab bar to select the right

tab Using the inspector, give this tab a Title of By Secret Identity and an Image of

secret_icon.png

Figure 3-18 Setting the attributes of the left tab

Back in the library, look for a Table View (Figure 3-19) Again, make sure you’re getting

the user interface element, not a Table View Controller Drag this to the space above the

tab bar It should resize automatically to fit the space available After you drop it, it

should look like Figure 3-20

Figure 3-19 The Table View in the library

Trang 26

Figure 3-20 The HeroListViewController interface after dropping the table on it

With the table in place, the HeroListViewController interface is complete, we just need

to make the outlet, delegate, and datasource connections Control-drag from File’s

Owner to the table view and select the tableView outlet, then control-drag again from File’s Owner to the tab bar and select the tabBar outlet That takes care of the outlet

connections Let’s move on to the delegate and datasource connections

Control-drag twice from the table view to File’s Owner, selecting the dataSource outlet one time, and the delegate outlet the other Then control-drag from the tab bar to File’s

Owner and select the delegate outlet Now our controller’s outlets are connected, and

our controller is the delegate for both the tab bar and table view, and is the data source for the table view as well Our job here is done Save the nib and go back to Xcode

Implementing the Hero View Controller

The implementation of HeroListViewController is going to look a bit like

RootViewController from the previous chapter, even though they have different

superclasses Replace the current contents of your HeroListViewController.m file with

the following code Once you’ve done that, we’ll talk about the new stuff it contains

#import "HeroListViewController.h"

#import "SuperDBAppDelegate.h"

@implementation HeroListViewController

Trang 27

#pragma mark Properties

[[self.fetchedResultsController fetchRequest] entity];

NSManagedObject *newManagedObject = [NSEntityDescription

insertNewObjectForEntityForName:[entity name]

inManagedObjectContext:context];

NSError *error;

if (![context save:&error])

NSLog(@"Error saving entity: %@", [error localizedDescription]);

// TODO: Instantiate detail editing controller and push onto stack

NSLocalizedString(@"Done", @"Done") : NSLocalizedString(@"Edit", @"Edit");

[self.tableView setEditing:editing animated:YES];

}

- (void)viewDidLoad {

[super viewDidLoad];

NSError *error = nil;

if (![[self fetchedResultsController] performFetch:&error]) {

UIAlertView *alert = [[UIAlertView alloc]

initWithTitle:NSLocalizedString(@"Error loading data",

@"Error loading data")

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

NSInteger selectedTab = [defaults integerForKey:kSelectedTabDefaultsKey];

UITabBarItem *item = [tabBar.items objectAtIndex:selectedTab];

Ngày đăng: 12/08/2014, 21:20