显示标签为“Core Data”的博文。显示所有博文
显示标签为“Core Data”的博文。显示所有博文

2011年12月30日星期五

iPhone Development – core data relationships tutorial part 1

iPhone Development – core data relationships tutorial part 1:
I’m going to start a short series on Core Data relationships and maybe throw in some general Core Data stuff too. Here in part one we’re just going to set our app up with core data and add two entities with a simple one to one relationship between them. A one to one relationship means that for every Fruit there will be one source and in our case here the reverse it true too, for every source there is one fruit.

Core Data Relationships



1.) Create a new Tab Bar Application named CoreDataRelationshipsTutorial.

2.) Change FirstView.xib so it looks similar to this.

FirstView.xib

3.) Add the core data framework to the app.

Add Core Data Framework

4.) Right click on Supporting Files and select New File, then choose Core Data select Data Model and hit Next. I just accepted the default name of Model and clicked save.

Add Data Model

5.) Select Model.xcdatamodeld and the visual editor will open. Click Add Entity and name it Fruit. Add an Attribute named fruitName of type String. Add another Entity named Source with an Attribute sourceName, which will also be of type String.

6.) Select Fruit and then click the plus symbol under Relationships. Name the relationship fruitSource. Set the destination to Source, there will be no inverse yet. In the relationship data model inspector uncheck the Optional checkbox. In the delete rule select Cascade.

Edit Data Model

7.) Now select Source and add a relationship named sourceFruit. Destination should be Fruit and set the inverse to artistCareer. Uncheck the Optional checkbox again.

Set up relationships

8.) Select Fruit under ENTITIES and then go under the file menu up top and select New File. Choose Core Data and NSManagedObject subclass,, click Next. Keep the default location and click Create.

Create Managed Objects

Repeat this same process after selecting Source under ENTITIES.

You should now see your new objects listed under Supporting Files.

xcode view

9.) Open up CoreDataRelationshipsTutorial-Prefix.pch and add an import for CoreDate. This saves us from having to import it into every file that will use it.

#import

#ifndef __IPHONE_3_0
#warning "This project uses features only available in iPhone SDK 3.0 and later."
#endif

#ifdef __OBJC__
#import
#import
#import
#endif

10.) Now let’s add all the necessary Core Data code to the app delegate files.

First the header file. Import our FirstViewController, then declare private instance variables for our NSManagedObjectContext, NSManagedObjectModel and NSPersistentStoreCoordinator. Create an IBOutlet with out FirstViewController, and declare two methods that we’ll implement.

#import
#import "FirstViewController.h"

@interface CoreDataRelationshipsTutorialAppDelegate : NSObject
{

@private
NSManagedObjectContext *managedObjectContext;
NSManagedObjectModel *managedObjectModel;
NSPersistentStoreCoordinator *persistentStoreCoordinator;

}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;

@property (nonatomic, retain) IBOutlet FirstViewController *firstViewController;

@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (NSURL *)applicationDocumentsDirectory;
- (void)saveContext;

@end

11.) Now open the app delegate implementation file. Synthesize our firstViewController, then set it’s managedObjectContext to the one created in the app delegate. You may see an error on the line that sets the managedObjectContext because we haven’t set that up in FirstViewController yet.

#import "CoreDataRelationshipsTutorialAppDelegate.h"

@implementation CoreDataRelationshipsTutorialAppDelegate

@synthesize window=_window;
@synthesize tabBarController=_tabBarController;
@synthesize firstViewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
firstViewController.managedObjectContext = self.managedObjectContext;

self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}

Implement all these methods.

/**
Returns the URL to the application's Documents directory.
*/
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

- (void)saveContext
{

NSError *error = nil;
NSManagedObjectContext *objectContext = self.managedObjectContext;
if (objectContext != nil)
{
if ([objectContext hasChanges] && ![objectContext save:&error])
{
// add error handling here
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
}

#pragma mark -
#pragma mark Core Data stack

/**
Returns the managed object context for the application.
If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
*/
- (NSManagedObjectContext *)managedObjectContext
{

if (managedObjectContext != nil)
{
return managedObjectContext;
}

NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return managedObjectContext;
}

/**
Returns the managed object model for the application.
If the model doesn't already exist, it is created from the application's model.
*/
- (NSManagedObjectModel *)managedObjectModel
{
if (managedObjectModel != nil)
{
return managedObjectModel;
}
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];

return managedObjectModel;
}

/**
Returns the persistent store coordinator for the application.
If the coordinator doesn't already exist, it is created and the application's store added to it.
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{

if (persistentStoreCoordinator != nil)
{
return persistentStoreCoordinator;
}

NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataTabBarTutorial.sqlite"];

NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}

return persistentStoreCoordinator;
}

12.) Open up FirstViewController.h and let’s set it up with the necessary code and instance variables.

#import

@interface FirstViewController : UIViewController
{

NSFetchedResultsController  *fetchedResultsController;
NSManagedObjectContext      *managedObjectContext;

}

@property (nonatomic, retain) NSString *fruitNameString;
@property (nonatomic, retain) NSString *fruitSourceString;

@property (nonatomic, retain) NSFetchedResultsController    *fetchedResultsController;
@property (nonatomic, retain) NSManagedObjectContext        *managedObjectContext;

- (IBAction) saveData;

@end

13.) Now for the implementation file. Let’s import our managed objects.

#import "FirstViewController.h"
#import "Fruit.h"
#import "Source.h"

Then synthesize the instance variables.

@synthesize fetchedResultsController, managedObjectContext;
@synthesize fruitNameString, fruitSourceString;

Let’s go ahead and set the values of those two strings in ViewDidLoad.

- (void)viewDidLoad
{
[super viewDidLoad];
fruitNameString = [[NSString alloc] initWithString:@"Apple"];
fruitSourceString = [[NSString alloc] initWithString:@"Apple Tree"];
}

14.) Implement the saveData method.

- (IBAction) saveData
{
NSLog(@"saveData");
Fruit *fruit = (Fruit *)[NSEntityDescription insertNewObjectForEntityForName:@"Fruit" inManagedObjectContext:managedObjectContext];
fruit.fruitName = fruitNameString;
Source *source = (Source *)[NSEntityDescription insertNewObjectForEntityForName:@"Source" inManagedObjectContext:managedObjectContext];
source.sourceName = fruitSourceString;

// Because we set the relationship fruitSource as not optional we must set the source here
fruit.fruitSource = source;

NSError *error;

// here's where the actual save happens, and if it doesn't we print something out to the console
if (![managedObjectContext save:&error])
{
NSLog(@"Problem saving: %@", [error localizedDescription]);
}

// **** log objects currently in database ****
// create fetch object, this object fetch's the objects out of the database
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Fruit" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];

for (NSManagedObject *info in fetchedObjects)
{
NSLog(@"Fruit name: %@", [info valueForKey:@"fruitName"]);
Source *tempSource = [info valueForKey:@"fruitSource"];
NSLog(@"Source name: %@", tempSource.sourceName);

}
[fetchRequest release];
}

15.) Release our objects in the dealloc method and set them to nil in viewDidUnload.

- (void)viewDidUnload
{
[super viewDidUnload];
fetchedResultsController = nil;
managedObjectContext = nil;
fruitNameString = nil;
fruitSourceString = nil;
}

- (void)dealloc
{
[fetchedResultsController release];
[managedObjectContext release];
[fruitNameString release];
[fruitSourceString release];

[super dealloc];
}

16.) Open up FirstView.xib and connect the UIButton to our saveData IBAction.

17.) Open up MainWindow.xib, select the app delegate and connect firstViewController outlet to FirstViewController under the Tab Bar Controller.

Link view controller

18.) Now you can run the app and hit the Save Data button. Look in the console to see the results of the fetch.

console

The important things to note from this tutorial are these.

When we created the relationship from Fruit to Source we made it so that it was not optional. Therefore during our saveData method we had to set the fruitSource to something.

Fruit *fruit = (Fruit *)[NSEntityDescription insertNewObjectForEntityForName:@"Fruit" inManagedObjectContext:managedObjectContext];
fruit.fruitName = fruitNameString;
Source *source = (Source *)[NSEntityDescription insertNewObjectForEntityForName:@"Source" inManagedObjectContext:managedObjectContext];
source.sourceName = fruitSourceString;

Try commenting out that last line

//    source.sourceName = fruitSourceString;

And then running it again. What happens? Crash and burn. Because the relationship is not optional you must set the sourceName.

You can also see from the block of code above that we use reuse the same managedObjectContext to create both of the managed objects. Then we set the values and just saved the context. Doing this saved both objects (entities in Core Data).

Another thing to take note of happens in the fetch process. You notice that we only fetch the Fruit entity. But because of the relationship between Fruit and Source we can access the Source entity. We don’t need to do a separate fetch on Source.

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Fruit" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];

for (NSManagedObject *info in fetchedObjects)
{
NSLog(@"Fruit name: %@", [info valueForKey:@"fruitName"]);
Source *tempSource = [info valueForKey:@"fruitSource"];
NSLog(@"Source name: %@", tempSource.sourceName);
[tempSource release];
}

Okay that does it for this tutorial. Next time we will look at a one to many relationship.

As always here’s the code.

iPhone App Development Tutorial – Core Data part 2 – One to Many Relationship

iPhone App Development Tutorial – Core Data part 2 – One to Many Relationship:
In this tutorial we will look at creating a one to many relationship in core data. It’s really very easy and not that much different than creating the one to one relationship. The difference comes in how the relationship allows the entities to interact with each other. In part one we had two entities, Fruit and Source. These two entities had a one to one relationship to each other. For each Fruit there was only one Source, and the inverse was true as well, for each Source there was only one Fruit. Now we are going to create two new entities, Artist and Album. Each artist can have multiple albums, but each album can have only one artist. We’ll just pretend that no artists ever collaborate on albums together for the sake of this tutorial :)

We’re going to use the app that I / we created in the Core Data duplicate entities tutorial. In that tutorial we set up an app that did not allow us to enter the same Artist twice. It seems like a good app to start with, only I’m even going to take care of a few more things for us To save time. I’ve set up the second tab with a UITableView and hooked it up with the view controller. This table view will hold the the list of artists that have been saved in the app. I’ve also created a new view with view controllers that will be used to enter Album information for an artist.

Screen Shot



What is going to happen is when we go to the second tab a search will return all Artists saved and display them in the UITableView. We can then click on an Artist and go to the enter Album informations view. The Artist object will be passed to this new view so that when we enter info for an album and save it, it will be saved with a relationship to the artist we selected. In this way we can create many albums for an artist, but an album can only have one artist. Thus the one to many relationship.

Here’s the code we’ll start with.

Let’s get going.

1.) If you run the app right now you can see the second tab with a hard coded string populating each table view cell.

Select An Artist Table Hardcoded

And though there’s no way to get to the new view yet, here is what it looks like.

Enter Album Info View

So that’s what we are starting with.

2.) The first thing we are going to do is a search in SecondViewController and populate our table with the Artists returned. Open up SecondViewController.h and add an NSMutableArray to hold our Artists and declare an ivar for NSMananagedObjectContext.

#import

@interface SecondViewController : UIViewController
{
NSMutableArray *artistsArray;
}

@property (nonatomic, retain) IBOutlet UITableView *artistTable;
@property (nonatomic, retain) NSManagedObjectContext        *managedObjectContext;

@end

Then go to the implementation file and synthesize the managedObjectContext ivar.

#import "SecondViewController.h"

@implementation SecondViewController

@synthesize artistTable;
@synthesize managedObjectContext;

3.) Go to app delegate header file and import SecondViewController, then declare an ivar of it and

#import
#import "FirstViewController.h"
#import "SecondViewController.h"

@interface CoreDataTutorial5AppDelegate : NSObject
{

@private
NSManagedObjectContext *managedObjectContext;
NSManagedObjectModel *managedObjectModel;
NSPersistentStoreCoordinator *persistentStoreCoordinator;

}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;

@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

@property (nonatomic, retain) IBOutlet FirstViewController *firstViewController;
@property (nonatomic, retain) IBOutlet SecondViewController *secondViewController;

- (NSURL *)applicationDocumentsDirectory;
- (void)saveContext;

@end

finally synthesize it in the app delegate implementation file.

#import "CoreDataTutorial5AppDelegate.h"

@implementation CoreDataTutorial5AppDelegate

@synthesize window=_window;
@synthesize tabBarController=_tabBarController;
@synthesize firstViewController, secondViewController;

Then open MainWindow.xib and connect the secondViewController ivar to the SecondViewController under Tab Bar Controller.

Now that they are connected let’s set the managed object context in secondViewController. Do this in the app delegate implementation file application:didFinishLaunchingWithOptions method.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.firstViewController.managedObjectContext = self.managedObjectContext;
self.secondViewController.managedObjectContext = self.managedObjectContext;
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}

4.) We’ve now passed the managedObjectContext to SecondViewController and we can implement the search method. We’re going to implement the viewWillAppear method and do our search there.

- (void)viewWillAppear:(BOOL)animated
{
NSLog(@"viewWillAppear");
NSError *error;

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Artist" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
artistsArray = [[NSMutableArray alloc] initWithArray:fetchedObjects];

[fetchRequest release];
[artistTable reloadData];

}

Then we’ll use our array to set the number of rows in the section.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [artistsArray count];
}

And finally we’ll populate our table view cells with the Artists name. But first import the Artist object.

#import "SecondViewController.h"
#import "Artist.h"

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}

// Configure the cell...
Artist *artist = [artistsArray objectAtIndex:indexPath.row];
cell.textLabel.text = artist.artistName;
return cell;
}

Run it and make sure everything is working up to this point.

Select an Artist Table

5.) Now that we have retrieved the Artists and are displaying them, let’s add the new view and pass the Artist to it when selecting a cell in the table view. We’ll need to import EnterAlbumInfoViewController to the SecondViewController header file and declare an ivar of it.

#import
#import "EnterAlbumInfoViewController.h"

@interface SecondViewController : UIViewController
{
NSMutableArray *artistsArray;
}

@property (nonatomic, retain) IBOutlet UITableView *artistTable;
@property (nonatomic, retain) NSManagedObjectContext        *managedObjectContext;

@property (nonatomic, retain) EnterAlbumInfoViewController *enterAlbumInfoViewController;

@end

Then synthesize it in the implementation file.

#import "SecondViewController.h"
#import "Artist.h"

@implementation SecondViewController

@synthesize artistTable;
@synthesize managedObjectContext;
@synthesize enterAlbumInfoViewController;

Now go to the didSelectRowAtIndexPath method and present the new view modally, at the same time we will pass the Artist to it along with the managedObjectContext.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.enterAlbumInfoViewController == nil)
{
EnterAlbumInfoViewController *temp = [[EnterAlbumInfoViewController alloc] initWithNibName:@"EnterAlbumInfoViewController" bundle:[NSBundle mainBundle]];
self.enterAlbumInfoViewController = temp;
[temp release];
}

Artist *artist = [artistsArray objectAtIndex:indexPath.row];
self.enterAlbumInfoViewController.artistNameString = artist.artistName;
self.enterAlbumInfoViewController.artist = artist;
self.enterAlbumInfoViewController.managedObjectContext = self.managedObjectContext;

[self presentModalViewController:self.enterAlbumInfoViewController animated:YES];
}

I forgot to implement a method for the cancel button so let’s do that real quick. This goes in EnterAlbumInfoViewController.

- (IBAction)cancelView
{
[self dismissModalViewControllerAnimated:YES];
}

6.) Before we go any further we need to add the Album Entity to our Data Model. Open up the visual data model editor by clicking on DataModel.xcdatamodel. Add an Entity named Album with three attributes. Here’s how it should look when done.

Album Entity

Now we need to create the relationships. Select Artist and add a relationship to Album named album. This relationship will be a To-Many Relationship so check that box. This means each artist can have many albums. Also make this Optional since an artist doesn’t need to have an album when we create the artist.

album relationship

Then select Album and create the inverse. This time uncheck the optional checkbox and make sure the To-Many Relationship box is not checked. Each Album will have just one artist.

artist relationship

When done create the Album NSManagedObject subclass, and recreate the Artist object. You’ll also need to change the name of the sqlite db on the back end of the Core Data. I just changed the name in this line of the persistentStoreCoordinator method of the app delegate.

NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataTutorial63.sqlite"];

After doing this you’ll need to re-enter the Artist info.

7.) Now we should be ready to save the Album info. Implement the saveAlbumInfo in EnterAlbumInfoViewController. You’ll need to import “Album.h” too.

- (IBAction)saveAlbumInfo
{
NSLog(@"saveAlbumInfo");

Album *album = (Album *)[NSEntityDescription insertNewObjectForEntityForName:@"Album" inManagedObjectContext:managedObjectContext];
album.albumName = self.albumNameTextField.text;
album.albumReleaseDate = self.albumReleaseDateTextField.text;
album.albumGenre = self.albumGenreTextField.text;
album.artist = self.artist;

NSError *error;

// here's where the actual save happens, and if it doesn't we print something out to the console
if (![managedObjectContext save:&error])
{
NSLog(@"Problem saving: %@", [error localizedDescription]);
}

// **** log objects currently in database ****
// create fetch object, this object fetch's the objects out of the database
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Album" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];

for (NSManagedObject *info in fetchedObjects)
{
NSLog(@"Album name: %@", [info valueForKey:@"albumName"]);
NSLog(@"Album age: %@", [info valueForKey:@"albumReleaseDate"]);
NSLog(@"Album gender: %@", [info valueForKey:@"albumGenre"]);

}
[fetchRequest release];

[self dismissModalViewControllerAnimated:YES];
}

8.) Go ahead and run the app and test it out to make sure everything is working up to this point.

9.) Now let’s add a third tab that will allow us to search for an artist and display all of their albums. Add a Tab Bar Item to the Tab bar and the view should look like this.

Search View

Where it has a UISearchBar and a UITableView.

10.) We’ll have to pass the managed object context to this view just as we did with the first and second view controllers. Go to the app delegate header file and import the new view controller. Then create an ivar for it.

#import
#import "FirstViewController.h"
#import "SecondViewController.h"
#import "SearchViewController.h"

@interface CoreDataTutorial5AppDelegate : NSObject
{

@private
NSManagedObjectContext *managedObjectContext;
NSManagedObjectModel *managedObjectModel;
NSPersistentStoreCoordinator *persistentStoreCoordinator;

}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;

@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

@property (nonatomic, retain) IBOutlet FirstViewController *firstViewController;
@property (nonatomic, retain) IBOutlet SecondViewController *secondViewController;
@property (nonatomic, retain) IBOutlet SearchViewController *searchViewController;

- (NSURL *)applicationDocumentsDirectory;
- (void)saveContext;

@end

11.) Synthesize our new ivar in the implementation file. and pass the managed object context in the didFinishLaunchingWithOptions method.

#import "CoreDataTutorial5AppDelegate.h"

@implementation CoreDataTutorial5AppDelegate

@synthesize window=_window;
@synthesize tabBarController=_tabBarController;
@synthesize firstViewController, secondViewController, searchViewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.firstViewController.managedObjectContext = self.managedObjectContext;
self.secondViewController.managedObjectContext = self.managedObjectContext;
self.searchViewController.managedObjectContext = self.managedObjectContext;
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}

12.) Open MainWindow.xib and connect the searchViewController outlet to the SearchViewController in the Tab Bar Controller.

13.) Open SearchViewController.h and declare two NSArray ivars. One to hold our fetchedObjects (the search results), and one to hold the list of albums from the artiste returned by our search. Declare an ivar for Artist and import Artist into the header file. Create IBOutlets fort our table view and search bar and finally create ivars for the fetchedResultsController and the managedObjectContext.

#import
#import "Artist.h"

@interface SearchViewController : UIViewController
{
NSArray *fetchedObjects;
NSArray *albumArray;
Artist *artist;
}

@property (nonatomic, retain) IBOutlet UITableView *searchResultsTableView;
@property (nonatomic, retain) IBOutlet UISearchBar *mySearchBar;
@property (nonatomic, retain) NSFetchedResultsController    *fetchedResultsController;
@property (nonatomic, retain) NSManagedObjectContext        *managedObjectContext;

@end

14.) Now open up SearchViewController.m and import both Artist and Album and synthesize all our ivars.

#import "SearchViewController.h"
#import "Artist.h"
#import "Album.h"

@implementation SearchViewController

@synthesize searchResultsTableView, mySearchBar, fetchedResultsController, managedObjectContext;

Then implement viewWillAppear. We’ll use this to clear our search bar.

- (void)viewWillAppear:(BOOL)animated
{
mySearchBar.text = @"";
}

15.) Now let’s implement the viewDidLoad method. We’ll set up our core data elements here.

- (void)viewDidLoad
{
[super viewDidLoad];

//    NSFetchRequest needed by the fetchedResultsController
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

//    NSSortDescriptor tells defines how to sort the fetched results
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"artistName" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];

//    fetchRequest needs to know what entity to fetch
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Artist" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
[sortDescriptors release];
[sortDescriptor release];

fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"];

[fetchRequest release];
}

16.) Next implement the searchBarButtonClicked method. This is where the search takes place and we’ll set the albumArray here.

- (void) searchBarSearchButtonClicked:(UISearchBar *)theSearchBar
{
NSLog(@"searchBarSearchButtonClicked");

NSError *error = nil;

// We use an NSPredicate combined with the fetchedResultsController to perform the search
if (self.mySearchBar.text !=nil)
{
NSPredicate *predicate =[NSPredicate predicateWithFormat:@"artistName  contains[cd] %@", self.mySearchBar.text];
[fetchedResultsController.fetchRequest setPredicate:predicate];
}

if (![[self fetchedResultsController] performFetch:&error])
{
// Handle error
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
exit(-1);  // Fail
}

// this array is just used to tell the table view how many rows to show
fetchedObjects = fetchedResultsController.fetchedObjects;

// Handle the case where search returns nothing
if ([fetchedObjects count] > 0)
{
artist = [fetchedObjects objectAtIndex:0];

NSSet *artistSet = artist.album;
albumArray = [artistSet allObjects];
}
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Search Results" message:@"Your search produced no results, please try again." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
[alert show];
[alert release];

}

// dismiss the search keyboard
[mySearchBar resignFirstResponder];

// reload the table view
[searchResultsTableView reloadData];
}

17.) Use the albumArray to set the numberRowsInSection.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [albumArray count];
}

18.) And finally implement the cellForRowAtIndexPath method like this to display the artist and their albums.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}

// Configure the cell...
cell.textLabel.text = artist.artistName;

NSSet *artistSet = artist.album;
NSArray *objectsArray = [artistSet allObjects];

for (int i = 0; i < [objectsArray count]; i++)
{
Album *album = [objectsArray objectAtIndex:indexPath.row];
cell.detailTextLabel.text = album.albumName;

}

return cell;
}

Here's what it looks like when done.

Finished Screen Shot

And here's the code.

2011年11月6日星期日

Simple Sqlite Database Interaction Using FMDB

Simple Sqlite Database Interaction Using FMDB:

Introduction


In the age where Core Data is king, the database that started it all is often overlooked. I’m talking of course about sqlite. As you may or may not know, prior to core data, sqlite was the preferred method of storing relational data on iOS devices.

Although, most developers don’t interact with sqlite directly, they still use it under the hood as the primary data store for core data. This is great and all, but there are often times when raw sqlite is still the preferred storage method.

A few of these might include:

  • Caching
  • Preferences
  • Simple objects
  • Portability
  • Cross platform applications

Recently, I have had to make heave use of raw sqlite as a caching strategy in a new project that I’m working on. Being that we are developing a framework for other developers to include in their projects, we can’t always assume that they have their environment set up to use core data. When I was but a n00b iOS developer I did all of the crazy sqlite management by hand. See This post series, but don’t spend too much time there because it’s embarrassing.

Gross right? Now, there is a much easier way to manage and interact with your sqlite databases. This library has been around for quite some time and I wish I had known about it earyly on.

FMDB


FMDB stands for Flying Meat Database. What a great name… This project aims to be a fully featured wrapper for sqlite.

You can clone their repository on their github.

This tutorial will give you a brief introduction to using FMDB to create a database, create a table, insert, fetch, and delete data.

Project Set Up


The first step is to download/clone fmdb from the url above. Once downloaded drag everything inside of the src folder into your project except fmdb.m. That file contains unit tests and a main, which will cause some conflicts in your project.

The next step is to link in the sqlite library. To do this:

  1. Click your project in the left column of XCode
  2. Click the main target in the middle column. In our case it’s “FMDBTest”
  3. Click the “Build Phases” tab in the third column
  4. Expand the arrow next to “Link Binary With Libraries”
  5. Click the “+” button
  6. Search for libsqlite3.0.dylib and double click it

When you are all done, it should look like this:

Screenshot


Now, that we have the library in place, let’s write some code.

Creating A Database


Obviously where you create your database is up to you, but we are going to do it in the appDelegate.

In addition to working with existing databases, fmdb can easily create any number of databases for you on the fly. After importing FMDatabase.h in our AppDelegate.m file, we can add the following code to the application:didFinishLaunching method.


NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *path = [docsPath stringByAppendingPathComponent:@"database.sqlite"];

FMDatabase *database = [FMDatabase databaseWithPath:path];


First, we resolve the path to the documents directory. Be careful, if you don’t need your database to be backed up, use the cache directory instead. When you send a path to the databaseWithPath method of fmdb, it first checks if the database exists, and if not, it creates it. Similarly, we could copy an existing database to the documents directory and source it the exact same way.

Opening The Database And Creating Tables


In order to perform any action on the database, it must first be opened. Here is the code to open the database and create a users table. Don’t worry about closing it right now, we will do that when we are all done.


[database open];
[database executeUpdate:@"create table user(name text primary key, age int)"];


Here we first call the open method of the database to open it. Next, we use the executeUpdate method to create the table. Make sure you use this method and not executeQuery when creating a table. This is a common error. The database should look like this after our update:

Screenshot


After we are all done here we close the database.

Inserting And Deleting Data


Inserting data using sqlite is very straight forward. You can either build your strings and pass them in directly OR use the sqlite format using “?’s” and letting fmdb do the work. Below is an example of each:


// Building the string ourself
NSString *query = [NSString stringWithFormat:@"insert into user values ('%@', %d)",
@"brandontreb", 25];
[database executeUpdate:query];

// Let fmdb do the work
[database executeUpdate:@"insert into user(name, age) values(?,?)",
@"cruffenach",[NSNumber numberWithInt:25],nil];


Generally, the second route is preferred as fmdb will do some of the sanitizing for your (such as add slashes to single quotes).

Now that we have some data in our database, let’s delete it. The following code will delete all users with an age of 25:


[database executeUpdate:@"delete from user where age = 25"];


And that should remove both of the records that we inserted.

Querying The Database


Querying the database is a bit more tricky than inserting and deleting. FMDB has some great utility methods for helping us out in certain circumstances, but for this tutorial, we will assume the don’t exist and show you how to fetch data out. If you are following along in a sample application, put this code before your delete code (so that we actually have some data to work with).

Below is an example of fetching all users from the database:


FMResultSet *results = [database executeQuery:@"select * from user"];
while([results next]) {
NSString *name = [results stringForColumn:@"name"];
NSInteger age  = [results intForColumn:@"age"];       
NSLog(@"User: %@ - %d",name, age);
}
[database close];


We first query the database using the executeQuery method. This returns to us an FMResultsSet. Next we start a while loop that continues while there are results to be retrieved and fetch out each of our data points. Finally, we just print out each user.

And finally, our database gets close…

Conclusion


This concludes our sqlite using fmdb tutorial. As always, if you have any questions, please leave them here or write me on twitter.

You can download the source for this tutorial here

Happy iCoding!