📜 ⬆️ ⬇️

Core Data for iOS. Chapter number 1. Practical part

Good day, good day!
Today I want to start writing a series of lectures with practical tasks on the book by Michael Privat and Robert Warner "Pro Core Data for iOS", which you can buy from this link. Each chapter will contain a theoretical and practical part.



Content:

')


Practical part


Since this is the first chapter and it can be considered introductory, then as a practical task we will choose the creation of a regular social application that will display a list of our friends from the VC and use Core Data to store data about them.
Approximately (in the process we decide what to add / exclude), this is how our application will look like after several hours (and maybe minutes) of persistent programming:
image
image


As you might have guessed, we will use Vkontakte iOS SDK v2.0 .
By the way, I ask you to forgive me for the fact that in the practical part not only XCode will be used, but also AppCode (thanks to the guys from JB for the product!). All that can be done in the AppCode will be done there.

Go…

Creating an empty project

Create an empty project without Core Data - Single View Application.
image
image

The application successfully started:
image

Add and configure UITableView

Open ASAViewController.h and add the following property:
@property (nonatomic, strong) UITableView *tableView; 

Full view of ASAViewController.h:
 #import <UIKit/UIKit.h> @interface ASAViewController : UIViewController @property (nonatomic, strong) UITableView *tableView; @end 

Open ASAViewController.m and add the UITableView table creation lines to the viewDidLoad method:
  CGRect frame = [[UIScreen mainScreen] bounds]; _tableView = [[UITableView alloc] initWithFrame:frame style:UITableViewStylePlain]; [self.view addSubview:_tableView]; 

Full view of ASAViewController.m:
 #import "ASAViewController.h" @implementation ASAViewController - (void)viewDidLoad { CGRect frame = [[UIScreen mainScreen] bounds]; _tableView = [[UITableView alloc] initWithFrame:frame style:UITableViewStylePlain]; [_tableView setDelegate:self]; [_tableView setDataSource:self]; [self.view addSubview:_tableView]; } @end 


Run:
image

It remains to implement the delegate methods UITableViewDelegate and UITableViewDataSource.
We add protocols in ASAViewController.h:
 @interface ASAViewController : UIViewController <UITableViewDataSource, UITableViewDelegate> 

We open ASAViewController.m and implement two methods (one to return the number of friends in the list, and the second to create a filled cell with user data):
 #pragma mark - UITableViewDelegate & UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [_userFriends count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *cellID = @"friendID"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; if(nil == cell){ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID]; } // setting default image while main photo is loading cell.imageView.image = [UIImage imageNamed:@"default.png"]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ NSString* imgPath = _userFriends[(NSUInteger)indexPath.row][@"photo"]; NSData* img = [NSData dataWithContentsOfURL:[NSURL URLWithString:imgPath]]; dispatch_async(dispatch_get_main_queue(), ^{ cell.imageView.image = [UIImage imageWithData:img]; }); }); NSString* firstName = _userFriends[(NSUInteger)indexPath.row][@"first_name"]; NSString* lastName = _userFriends[(NSUInteger)indexPath.row][@"last_name"]; NSString* fullName = [NSString stringWithFormat:@"%@ %@", firstName, lastName]; cell.textLabel.text = fullName; NSString* status = _userFriends[(NSUInteger)indexPath.row][@"status"]; cell.detailTextLabel.text = status; return cell; } 


The _userFriends variable is a property of ASAViewController:
 @property (nonatomic, strong) NSMutableArray *userFriends; 


The final view of ASAViewController.h and ASAViewController.m:
 #import <UIKit/UIKit.h> @interface ASAViewController : UIViewController <UITableViewDataSource, UITableViewDelegate> @property (nonatomic, strong) UITableView *tableView; @property (nonatomic, strong) NSMutableArray *userFriends; @end 

 #import "ASAViewController.h" @implementation ASAViewController - (void)viewDidLoad { _userFriends = [[NSMutableArray alloc] init]; CGRect frame = [[UIScreen mainScreen] bounds]; _tableView = [[UITableView alloc] initWithFrame:frame style:UITableViewStylePlain]; [_tableView setDelegate:self]; [_tableView setDataSource:self]; [self.view addSubview:_tableView]; } #pragma mark - UITableViewDelegate & UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [_userFriends count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *cellID = @"friendID"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; if(nil == cell){ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID]; } // setting default image while main photo is loading cell.imageView.image = [UIImage imageNamed:@"default.png"]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ NSString* imgPath = _userFriends[(NSUInteger)indexPath.row][@"photo"]; NSData* img = [NSData dataWithContentsOfURL:[NSURL URLWithString:imgPath]]; dispatch_async(dispatch_get_main_queue(), ^{ cell.imageView.image = [UIImage imageWithData:img]; }); }); NSString* firstName = _userFriends[(NSUInteger)indexPath.row][@"first_name"]; NSString* lastName = _userFriends[(NSUInteger)indexPath.row][@"last_name"]; NSString* fullName = [NSString stringWithFormat:@"%@ %@", firstName, lastName]; cell.textLabel.text = fullName; NSString* status = _userFriends[(NSUInteger)indexPath.row][@"status"]; cell.detailTextLabel.text = status; return cell; } @end 

Everything should be started with a bang. Go to the next step.

Integrating VKontakte iOS SDK v2.0

Pick up the source for this link.

Connecting QuartzCore.framework
image

Adding Vkontakte iOS SDK
image

We add two protocols to ASAAppDelegate.h:
 @interface ASAAppDelegate : UIResponder <UIApplicationDelegate, VKConnectorDelegate, VKRequestDelegate> 


Open the ASAAppDelegate.m implementation file and insert the following lines into the - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions :
  [[VKConnector sharedInstance] setDelegate:self]; [[VKConnector sharedInstance] startWithAppID:@"3541027" permissons:@[@"friends"]]; 

This code when launching the application will show a pop-up window to the user for authorization in the social network VKontakte.
image

In ASAAppDelegate.m, we implement two more methods:
 #pragma mark - VKConnectorDelegate - (void) VKConnector:(VKConnector *)connector accessTokenRenewalSucceeded:(VKAccessToken *)accessToken { // now we can make request [[VKUser currentUser] setDelegate:self]; [[VKUser currentUser] friendsGet:@{ @"uid" : @([VKUser currentUser].accessToken.userID), @"fields" : @"first_name,last_name,photo,status" }]; } #pragma mark - VKRequestDelegate - (void)VKRequest:(VKRequest *)request response:(id)response { ASAViewController *controller = (ASAViewController *)self.window.rootViewController; controller.userFriends = response[@"response"]; [controller.tableView reloadData]; } 

The final view of ASAAppDelegate.h and ASAAppDelegate.m at this stage:
 #import <UIKit/UIKit.h> #import "VKConnector.h" #import "VKRequest.h" @class ASAViewController; @interface ASAAppDelegate : UIResponder <UIApplicationDelegate, VKConnectorDelegate, VKRequestDelegate> @property (strong, nonatomic) UIWindow *window; @property (strong, nonatomic) ASAViewController *viewController; @end 

 #import "ASAAppDelegate.h" #import "ASAViewController.h" #import "VKUser.h" #import "VKAccessToken.h" @implementation ASAAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. self.viewController = [[ASAViewController alloc] initWithNibName:@"ASAViewController" bundle:nil]; self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; [[VKConnector sharedInstance] setDelegate:self]; [[VKConnector sharedInstance] startWithAppID:@"3541027" permissons:@[@"friends"]]; return YES; } #pragma mark - VKConnectorDelegate - (void) VKConnector:(VKConnector *)connector accessTokenRenewalSucceeded:(VKAccessToken *)accessToken { // now we can make request [[VKUser currentUser] setDelegate:self]; [[VKUser currentUser] friendsGet:@{ @"uid" : @([VKUser currentUser].accessToken.userID), @"fields" : @"first_name,last_name,photo,status" }]; } #pragma mark - VKRequestDelegate - (void)VKRequest:(VKRequest *)request response:(id)response { ASAViewController *controller = (ASAViewController *)self.window.rootViewController; controller.userFriends = response[@"response"]; [controller.tableView reloadData]; } @end 

We start the application and see something like the following (do not forget that in the example above, query caching is not used intentionally):
image
image

Dessert from Core Data

Here we come to the most interesting and exciting! I hope you have not lost the desire to finish the practical part;) Take a break, drink tea with dryers, nibble candy, stretch yourself, pull yourself up.

Why do we need Core Data here? We will proceed as follows: at the first request to the VKontakte server, we will receive a list of friends and the requested fields (status, photo, name, last name), save this information in the local storage using Core Data, and then launch the application and disable the Internet during the request and display A list of friends that were saved locally during the first request. Is it going Then let's get started.

To handle the fact that there is no Internet connection, we will use the following method from the VKRequestDelegate protocol:
 - (void)VKRequest:(VKRequest *)request connectionErrorOccured:(NSError *)error { // TODO } 

We will write the body of the method a little later.

Oh yeah, I completely forgot! We connect CoreData.framework .
image
Add three of our favorite properties to ASAAppDelegate.h:
 @property (nonatomic, strong) NSManagedObjectModel *managedObjectModel; @property (nonatomic, strong) NSPersistentStoreCoordinator *coordinator; @property (nonatomic, strong) NSManagedObjectContext *managedObjectContext; 


Now we are going to ASAAppDelegate.m in order to implement explicit getters for all three properties.
Managed Object Model:
 - (NSManagedObjectModel *)managedObjectModel { if(nil != _managedObjectModel) return _managedObjectModel; _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil]; return _managedObjectModel; } 

Persistent Store Coordinator:
 - (NSPersistentStoreCoordinator *)coordinator { if(nil != _coordinator) return _coordinator; NSURL *storeURL = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:@"BasicApplication.sqlite"]; _coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel]; NSError *error = nil; if(![_coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]){ NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return _coordinator; } 

Managed Object Context:
 - (NSManagedObjectContext *)managedObjectContext { if(nil != _managedObjectContext) return _managedObjectContext; NSPersistentStoreCoordinator *storeCoordinator = self.coordinator; if(nil != storeCoordinator){ _managedObjectContext = [[NSManagedObjectContext alloc] init]; [_managedObjectContext setPersistentStoreCoordinator:storeCoordinator]; } return _managedObjectContext; } 


Build ... And ... and ... everything is fine.

Now we are going to create a model. By the way, I want to note that I do everything without insurance and, maybe at the end of something and do not dock, but we are brave programmers!
To create a model, we need the same Xcode.
Open our project in it, press Control + N and select Core Data -> Data Model:
image

Save the model called Friend :
image

We see already quite familiar screen:
image

Create a new entity called Friend and add 4 properties: last_name (String), first_name (String), status (String), photo (Binary Data).
image

Finish and close Xcode.

The next thing we need to do is save the user data after the request is made.
Open ASAAppDelegate.m, go down to the VKRequest: response: method and change it as follows:
 - (void)VKRequest:(VKRequest *)request response:(id)response { ASAViewController *controller = (ASAViewController *)self.window.rootViewController; controller.userFriends = response[@"response"]; [controller.tableView reloadData]; //    ,     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ for(NSDictionary *user in controller.userFriends){ NSManagedObject *friend = [NSEntityDescription insertNewObjectForEntityForName:@"Friend" inManagedObjectContext:self.managedObjectContext]; [friend setValue:user[@"first_name"] forKey:@"first_name"]; [friend setValue:user[@"last_name"] forKey:@"last_name"]; [friend setValue:[NSData dataWithContentsOfURL:[NSURL URLWithString:user[@"photo"]]] forKey:@"photo"]; [friend setValue:user[@"status"] forKey:@"status"]; NSLog(@"friend: %@", friend); } if([self.managedObjectContext hasChanges] && ![self.managedObjectContext save:nil]){ NSLog(@"Unresolved error!"); abort(); } }); } 

At each iteration we create a new object, set its fields and save. In the console, you can watch the eye-pleasing lines
image

Tax, it remains to modify the display of the table when the Internet connection is broken. All code will go to the method - (void)VKRequest:(VKRequest *)request connectionErrorOccured:(NSError *)error and will look like this:
 - (void)VKRequest:(VKRequest *)request connectionErrorOccured:(NSError *)error { //         NSMutableArray *data = [[NSMutableArray alloc] init]; //      NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Friend"]; NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"last_name" ascending:YES]; [fetchRequest setSortDescriptors:@[sortDescriptor]]; //   NSArray *tmpData = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil]; //   for(NSManagedObject *object in tmpData){ //    ,         -  :) if([object valueForKey:@"status"] == nil) continue; NSDictionary *tmp = @{ @"last_name": [object valueForKey:@"first_name"], @"first_name": [object valueForKey:@"last_name"], @"photo": [object valueForKey:@"photo"], @"status": [object valueForKey:@"status"] }; [data addObject:tmp]; } //   ""    ASAViewController *controller = (ASAViewController *)self.window.rootViewController; controller.userFriends = data; [controller.tableView reloadData]; } 


And small adjustments should be made to the method - (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
:
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *cellID = @"friendID"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; if(nil == cell){ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID]; } // setting default image while main photo is loading cell.imageView.image = [UIImage imageNamed:@"default.png"]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ NSData *img; if([_userFriends[(NSUInteger) indexPath.row][@"photo"] isKindOfClass:[NSData class]]){ img = _userFriends[(NSUInteger) indexPath.row][@"photo"]; } else { NSString* imgPath = _userFriends[(NSUInteger)indexPath.row][@"photo"]; img = [NSData dataWithContentsOfURL:[NSURL URLWithString:imgPath]]; } dispatch_async(dispatch_get_main_queue(), ^{ cell.imageView.image = [UIImage imageWithData:img]; }); }); NSString* firstName = _userFriends[(NSUInteger)indexPath.row][@"first_name"]; NSString* lastName = _userFriends[(NSUInteger)indexPath.row][@"last_name"]; NSString* fullName = [NSString stringWithFormat:@"%@ %@", firstName, lastName]; cell.textLabel.text = fullName; NSString* status = _userFriends[(NSUInteger)indexPath.row][@"status"]; cell.detailTextLabel.text = status; return cell; } 


Hooray! The application is complete and it displays friends from the local storage:
image

Tears of happiness

Finally, we finished our first, but not the last practical part. The entire project can be found at this link (it is in the archive).

I hope that the back and fingers are not tired.
I hope that you are pleased with the time spent in the company with Core Data.
Hope you want to see the sequels.

Note

Nothing can please the author, as a comment left, even if it is a critic;)

Thank you for attention!

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


All Articles