📜 ⬆️ ⬇️

We disassemble iPhone Core Data Recipes. Part 1

Introduction


The purpose of this article is to help a novice iOS developer to understand how to work correctly with a SQLite database using Core Data using the example of iPhone Core Data Recipes. In the first part of a series of articles, the interaction of the application and the database will be discussed, as well as work with related records (Relationships).

Prerequisites


For self-study of the source code of this application, you need a standard set of tools:


This set will allow you to view, change and run the application on the simulator. In case, if you want to try to run it on a real iPhone, participation in the iOS Developer Program is required.
')
And also, which is quite important, you need a basic understanding of the structure of the Objective-C language and the application.

Links to the materials and tools used are provided in the References section.

What is iPhone Core Data Recipes?

Xcode is a project from Apple developers that gives you a general idea of ​​how to use view controllers, table views and Core Data in iPhone applications. Of course, this project is also relevant for the iPad, but it is necessary to make interface changes to it for correct display on the iPad.

iPhone Core Data Recipes Screenshots

The screenshots below give an overview of the application interface. In the main window "Recipes" - lists the finished products. Clicking on one of them, the application displays information on how to prepare this product, its category, how much time it takes, what ingredients are needed and in what quantity, as well as a button that displays text cooking instructions (the screenshot is not shown). Also in the application there is the possibility of converting values ​​between grams, pounds and ounces, as well as a table of correspondence of temperatures (celsius and fahrenheit).



Database structure


A general idea of ​​the structure of the database can be obtained from the screenshot below:

More structurally, the structure can be studied by opening the Recipes.xcdatamodel file in Xcode.

Description of the tables

The Recipe table contains the following attributes:

The Ingredient table contains the following attributes:

The RecipeType table contains the following attributes:

The Image table contains the following attributes:

I think with standard types everything is clear. But what kind of Transformable? Core Data supports a variety of standard types, such as string, date, or number. But sometimes it is necessary to create an attribute whose value is not directly supported. For example, store a picture or color that are instances of the NSImage and NSColor classes. To do this, you need a type of Transformable, a detailed description of this type, you can read on the iOS Developer Library website in the Non-Standard Persistent Attributes section (see the link in the References section).

Relationships between tables (Relationships)

In the database structure, there is the main Recipe table, which has links of various types, with three others. For example, the Recipe table has a one-to-many relationship with the Ingredient table. The type and direction of communication can be understood by the arrows and their number. For example, the Recipe table has a one-to-one relationship with the RecipeType table, but the RecipeType table has a one-to-many relationship with the Recipe table, i.e. two-way communication is not of the same type. This means that a record from the Recipe table can be associated with only one record from the RecipeType table, but a record from the RecipeType table can be associated with many records in the Recipe table. In this particular example, to put it in simple terms, we can say that the product can only be in one category, while different products can also be in the same category, for example, in the Dessert category.

Core Data automatically manages connections, i.e. you only need to build them correctly in your model.

The type of relationship is specified in the Relationship properties in the Data Model Inspector (see example below).

When designing the structure of your database, it is important not to forget about the Delete Rule in the connection properties. In this example, removing the product removes all of its ingredients.

Classes

After you have completed the database design, you need to generate Objective-C classes for your model. In our case, we have classes for the Recipe and Ingredient tables.

Recipe.h
@interface ImageToDataTransformer : NSValueTransformer { } @end @interface Recipe : NSManagedObject { } @property (nonatomic, retain) NSString *instructions; @property (nonatomic, retain) NSString *name; @property (nonatomic, retain) NSString *overview; @property (nonatomic, retain) NSString *prepTime; @property (nonatomic, retain) NSSet *ingredients; @property (nonatomic, retain) UIImage *thumbnailImage; @property (nonatomic, retain) NSManagedObject *image; @property (nonatomic, retain) NSManagedObject *type; @end @interface Recipe (CoreDataGeneratedAccessors) - (void)addIngredientsObject:(NSManagedObject *)value; - (void)removeIngredientsObject:(NSManagedObject *)value; - (void)addIngredients:(NSSet *)value; - (void)removeIngredients:(NSSet *)value; @end 


Ingredient.h
 @class Recipe; @interface Ingredient : NSManagedObject { } @property (nonatomic, retain) NSString *name; @property (nonatomic, retain) NSString *amount; @property (nonatomic, retain) Recipe *recipe; @property (nonatomic, retain) NSNumber *displayOrder; @end 

Source code


We start to study the source when the interaction of the application and database.

The RecipesAppDelegate.h file contains declarations of the NSManagedObjectModel, NSManagedObjectContext and NSPersistentStoreCoordinator properties required for working with the database:
 @class RecipeListTableViewController; @interface RecipesAppDelegate : NSObject <UIApplicationDelegate> { NSManagedObjectModel *managedObjectModel; NSManagedObjectContext *managedObjectContext; NSPersistentStoreCoordinator *persistentStoreCoordinator; } @property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel; @property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext; @property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator; - (NSString *)applicationDocumentsDirectory; @end 


The RecipesAppDelegate.m file controls these properties and also ensures that the data is consistent.
 ... - (void)applicationDidFinishLaunching:(UIApplication *)application { recipeListController.managedObjectContext = self.managedObjectContext; ... } //   ,  ,    - (void)applicationWillTerminate:(UIApplication *)application { NSError *error; if (managedObjectContext != nil) { if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } } } //  NSManagedObjectContext,      - (NSManagedObjectContext *)managedObjectContext { if (managedObjectContext != nil) { return managedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (coordinator != nil) { managedObjectContext = [NSManagedObjectContext new]; [managedObjectContext setPersistentStoreCoordinator: coordinator]; } return managedObjectContext; } //  NSManagedObjectModel,      - (NSManagedObjectModel *)managedObjectModel { if (managedObjectModel != nil) { return managedObjectModel; } managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain]; return managedObjectModel; } //   NSPersistentStoreCoordinator,     . NSPersistentStoreCoordinator       - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if (persistentStoreCoordinator != nil) { return persistentStoreCoordinator; } NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"Recipes.sqlite"]; /*        ,  ,     .        . */ NSFileManager *fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:storePath]) { NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"Recipes" ofType:@"sqlite"]; if (defaultStorePath) { [fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL]; } } NSURL *storeUrl = [NSURL fileURLWithPath:storePath]; NSError *error; 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; } //    Documents  - (NSString *)applicationDocumentsDirectory { return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; } ... 


Now, we need to display a list of products in the main application window. The RecipeListTableViewController.m file (RecipeListTableViewController interface) contains the necessary code
 ... //     Recipe - (NSFetchedResultsController *)fetchedResultsController { if (fetchedResultsController == nil) { NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Recipe" inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; //     NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"]; aFetchedResultsController.delegate = self; self.fetchedResultsController = aFetchedResultsController; [aFetchedResultsController release]; [fetchRequest release]; [sortDescriptor release]; [sortDescriptors release]; } return fetchedResultsController; } ... - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { //  RecipeTableViewCell      . RecipeTableViewCell      UITableViewCell static NSString *RecipeCellIdentifier = @"RecipeCellIdentifier"; RecipeTableViewCell *recipeCell = (RecipeTableViewCell *)[tableView dequeueReusableCellWithIdentifier:RecipeCellIdentifier]; if (recipeCell == nil) { recipeCell = [[[RecipeTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:RecipeCellIdentifier] autorelease]; recipeCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } [self configureCell:recipeCell atIndexPath:indexPath]; return recipeCell; } - (void)configureCell:(RecipeTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath { //   Recipe *recipe = (Recipe *)[fetchedResultsController objectAtIndexPath:indexPath]; cell.recipe = recipe; } ... 

All this code is enough to display a list of products in the main window. Of course, we should not forget that here I did not bring all the code, but only excerpts from it. For example, by opening the RecipeListTableViewController.m file, you can learn how the application monitors changes (didChangeSection, didChangeObject, controllerWillChangeContent, commitEditingStyle, etc.).

We proceed to display the list of ingredients. To do this, we have the RecipeDetailViewController interface, which, in turn, has the following properties

 Recipe *recipe; NSMutableArray *ingredients; @property (nonatomic, retain) Recipe *recipe; @property (nonatomic, retain) NSMutableArray *ingredients; 


Passing the current recipe from the RecipeListTableViewController interface to the RecipeDetailViewController interface
 ... //    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { Recipe *recipe = (Recipe *)[fetchedResultsController objectAtIndexPath:indexPath]; [self showRecipe:recipe animated:YES]; } ... - (void)showRecipe:(Recipe *)recipe animated:(BOOL)animated { RecipeDetailViewController *detailViewController = [[RecipeDetailViewController alloc] initWithStyle:UITableViewStyleGrouped]; //  detailViewController.recipe = recipe; [self.navigationController pushViewController:detailViewController animated:animated]; [detailViewController release]; } ... 


Recruit a recipe
 - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [photoButton setImage:recipe.thumbnailImage forState:UIControlStateNormal]; self.navigationItem.title = recipe.name; nameTextField.text = recipe.name; overviewTextField.text = recipe.overview; prepTimeTextField.text = recipe.prepTime; [self updatePhotoButton]; //  NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"displayOrder" ascending:YES]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:&sortDescriptor count:1]; NSMutableArray *sortedIngredients = [[NSMutableArray alloc] initWithArray:[recipe.ingredients allObjects]]; [sortedIngredients sortUsingDescriptors:sortDescriptors]; self.ingredients = sortedIngredients; [sortDescriptor release]; [sortDescriptors release]; [sortedIngredients release]; [self.tableView reloadData]; } 


In tableView cellForRowAtIndexPath create a cell to display the recipe
 static NSString *IngredientsCellIdentifier = @"IngredientsCell"; cell = [tableView dequeueReusableCellWithIdentifier:IngredientsCellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:IngredientsCellIdentifier] autorelease]; cell.accessoryType = UITableViewCellAccessoryNone; } 

All this code is enough to display a list of ingredients. Its meaning is that from the main window, we pass a Recipe object, to the window that displays the list of ingredients. At the same time, in order to get the list of ingredients from the database, you do not need to create a new NSEntityDescription, simply call the property recipe.ingredients and Core Data will automatically return the necessary ingredients from the database.

Conclusion


Of course, this article is not a complete description of this project. If it will be interesting to you, dear readers, I am ready to continue a series of articles and, for example, in the following, describe how objects in the database are added, deleted and modified in this project. Thank you all for your attention and for your patience.

References


Source: https://habr.com/ru/post/136319/


All Articles