I have an app that uses a single UIManagedDocument as the data store which I access through a singleton using the method in Justin Driscoll's blog.
This works fine, but when I set my ubiquity keys for the persistentStoreOptions and change state notifications, the app only runs in the sandbox and there is no network activity.
Here is the execution flow ...
1. App opens to a landing screen where the singleton is created.
2. Once the UIManagedDocument is open I check for cloud permissions and if all is good to go and we have a ubiquity container, I overwrite the persistentStoreOptions with the cloud contentNameKey and contentUrlKey options.
Here is the code …
// DataStore.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
typedef void (^OnDocumentReady) (UIManagedDocument *appDataStore);
#interface DataStore : NSObject
#property (strong, nonatomic) UIManagedDocument *appDataStore;
+ (DataStore *)sharedDocumentHandler;
- (void)performWithDocument:(OnDocumentReady)onDocumentReady;
#end
// DataStore.m
#import "DataStore.h"
#import "HashDefines.h"
#interface DataStore ()
#property (nonatomic)BOOL preparingDocument;
#property (nonatomic, strong) NSFileCoordinator *coordinator;
#property (strong, nonatomic) NSString *localDocumentsPath;
#property (strong, nonatomic) NSURL *localDocumentsURL;
#property (strong, nonatomic) NSURL *localFileURL;
#end;
#implementation DataStore
#synthesize appDataStore = _appDataStore;
#synthesize localDocumentsPath = _localDocumentsPath;
#synthesize localDocumentsURL = _localDocumentsURL;
#synthesize localFileURL = _localFileURL;
static DataStore *_sharedInstance;
#synthesize coordinator = _coordinator;
#define LOCAL_DATA_STORE_FILE_NAME #“QR App DataStore"
#pragma mark - synthesiser overiders
+ (DataStore *)sharedDocumentHandler
{
static dispatch_once_t once;
dispatch_once(&once, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
#pragma mark - Key Paths
#pragma mark Local Documents
- (NSString *) localDocumentsPath
{
if (!_localDocumentsPath) {
_localDocumentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
}
return _localDocumentsPath;
}
- (NSURL *) localDocumentsURL
{
if (!_localDocumentsURL) {
_localDocumentsURL = [NSURL fileURLWithPath:self.localDocumentsPath];
}
return _localDocumentsURL;
}
#pragma mark - File URLs
- (NSURL *) localFileURL: (NSString *) filename
{
if (!filename) return nil;
NSURL *fileURL = [[self localDocumentsURL] URLByAppendingPathComponent:filename];
return fileURL;
}
#pragma mark - the juicy bits
#pragma mark - initialisers
- (id)init
{
self = [super init];
if (self) {
NSLog(#"appDataStore does NOT exist ... building one now");
[self initCoreDataInTheSandbox];
}
return self;
}
- (void)initCoreDataInTheSandbox
{
NSURL *localURL = [self localFileURL:LOCAL_DATA_STORE_FILE_NAME];
self.appDataStore = [[UIManagedDocument alloc] initWithFileURL:localURL];
// Set our document up for automatic migrations
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
self.appDataStore.persistentStoreOptions = options;
NSLog(#"1. DS persistentStoreOptions: %#", self.appDataStore.persistentStoreOptions);
}
- (void)performWithDocument:(OnDocumentReady)onDocumentReady
{
NSLog(#"1. DS Begin performWithDocument: %#", self.appDataStore);
void (^OnDocumentDidLoad)(BOOL) = ^(BOOL success) {
onDocumentReady(self.appDataStore);
NSLog(#"2. DS into the block ... onDocumentReady:");
self.preparingDocument = NO; // release in completion handler
};
if(!self.preparingDocument) {
NSLog(#"3. DS preparing document: dataStore.appDataStore: %#", self.appDataStore);
// "lock", so no one else enter here
self.preparingDocument = YES;
if(![[NSFileManager defaultManager] fileExistsAtPath:[self.appDataStore.fileURL path]]) {
NSLog(#"4. DS creating document: dataStore.appDataStore: %#", self.appDataStore);
[self.appDataStore saveToURL:self.appDataStore.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:OnDocumentDidLoad];
[self.appDataStore openWithCompletionHandler:OnDocumentDidLoad];
} else if (self.appDataStore.documentState == UIDocumentStateClosed) {
NSLog(#"5. DS dataStore.appDataStore.documentState: %d ... closed", self.appDataStore.documentState);
[self.appDataStore openWithCompletionHandler:OnDocumentDidLoad];
} else if (self.appDataStore.documentState == UIDocumentStateSavingError) {
NSLog(#"6. DS dataStore.appDataStore.documentState: %d ... saving error", self.appDataStore.documentState);
[self.appDataStore openWithCompletionHandler:OnDocumentDidLoad];
} else if (self.appDataStore.documentState == UIDocumentStateNormal) {
NSLog(#"7. DS dataStore.appDataStore.documentState: %d ... open", self.appDataStore.documentState);
OnDocumentDidLoad(YES);
}
} else {
// try until document is ready (opened or created by some other call)
NSLog(#"8. DS preparing document - NOT ... trying again");
[self performSelector:#selector(performWithDocument:) withObject:onDocumentReady afterDelay:0.5];
}
NSLog(#"9. DS Exiting performWithDocument: %#", self.appDataStore);
}
#end
// CloudConnector.h
#import "DataStore.h"
#interface CloudConnector : NSObject
- (void)hookUpCloudForDocument:(UIManagedDocument *)document;
(NSManagedObjectContext*)managedObjectContext;
- (NSString *) documentState: (int) state;
#end
#define ICLOUD_TOKEN_KEY #“com.apple.QR-App.UbiquityIdentityToken"
#define CLOUD_IS_UBIQUITOUS #"sweet ubiquity"
#define LOCAL_DATA_STORE_FILE_NAME #“QR App DataStore"
#define CLOUD_DATA_STORE_FILE_NAME #"com~app~QR-App~cloudstore"
//CloudConnector.m
#import "CloudConnector.h"
#import "HashDefines.h"
#interface CloudConnector ()
#property (strong, nonatomic) NSString *localDocumentsPath;
#property (strong, nonatomic) NSURL *localDocumentsURL;
#property (strong, nonatomic) NSURL *localFileURL;
#property (strong, nonatomic) NSURL *ubiquityDataFileURL;
#property (nonatomic)BOOL preparingDocument;
#property (nonatomic, strong) NSFileCoordinator *coordinator;
- (NSURL *) localFileURL: (NSString *) filename;
- (NSURL *) ubiquityDataFileURL: (NSString *) filename;
- (BOOL) isLocal: (NSString *) filename;
- (NSString *) documentState: (int) state;
#end;
#implementation CloudConnector
#synthesize coordinator = _coordinator;
#synthesize localDocumentsPath = _localDocumentsPath;
#synthesize localDocumentsURL = _localDocumentsURL;
#synthesize localFileURL = _localFileURL;
#synthesize ubiquityDataFileURL = _ubiquityDataFileURL;
#pragma mark - synthesiser overiders
#pragma mark - the juicy bits
- (void)checkUbiquityStatus
{
id currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
if (currentiCloudToken) {
NSData *newTokenData =
[NSKeyedArchiver archivedDataWithRootObject: currentiCloudToken];
[[NSUserDefaults standardUserDefaults]
setObject: newTokenData
forKey: ICLOUD_TOKEN_KEY];
} else {
[[NSUserDefaults standardUserDefaults]
removeObjectForKey: ICLOUD_TOKEN_KEY];
}
}
#pragma mark - Key Paths
#pragma mark Local Documents
- (NSString *) localDocumentsPath
{
if (!_localDocumentsPath) {
_localDocumentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
}
return _localDocumentsPath;
}
- (NSURL *) localDocumentsURL
{
if (!_localDocumentsURL) {
_localDocumentsURL = [NSURL fileURLWithPath:self.localDocumentsPath];
}
return _localDocumentsURL;
}
#pragma mark - File URLs
- (NSURL *) localFileURL: (NSString *) filename
{
if (!filename) return nil;
NSURL *fileURL = [[self localDocumentsURL] URLByAppendingPathComponent:filename];
return fileURL;
}
- (NSURL *) ubiquityDataFileURL: (NSString *) filename forContainer: (NSString *) container
{
if (!filename) return nil;
NSURL *fileURL = [[self ubiquityDataURLForContainer:container] URLByAppendingPathComponent:filename];
return fileURL;
}
- (NSURL *) ubiquityDataFileURL: (NSString *) filename
{
return [self ubiquityDataFileURL:filename forContainer:nil];
}
#pragma mark Ubiquity Data
- (NSURL *) ubiquityDataURLForContainer: (NSString *) container
{
return [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:container];
}
- (NSArray *) contentsOfUbiquityDataFolderForContainer: (NSString *) container
{
NSURL *targetURL = [self ubiquityDataURLForContainer:container];
if (!targetURL) return nil;
NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:targetURL.path error:nil];
return array;
}
- (BOOL) isLocal: (NSString *) filename
{
if (!filename) return NO;
NSURL *targetURL = [self localFileURL:filename];
if (!targetURL) return NO;
return [[NSFileManager defaultManager] fileExistsAtPath:targetURL.path];
}
- (NSString *) documentState: (int) state
{
if (!state) return #"Document state is normal";
NSMutableString *string = [NSMutableString string];
if ((state & UIDocumentStateClosed) != 0)
[string appendString:#"Document is closed\n"];
if ((state & UIDocumentStateInConflict) != 0)
[string appendString:#"Document is in conflict"];
if ((state & UIDocumentStateSavingError) != 0)
[string appendString:#"Document is experiencing saving error"];
if ((state & UIDocumentStateEditingDisabled) != 0)
[string appendString:#"Document editing is disbled" ];
return string;
}
#pragma mark - initialisers
- (void)hookUpCloudForDocument:(UIManagedDocument *)document
{
// checking for ubiquity
NSLog(#"checking for ubiquity");
[self checkUbiquityStatus];
// THE FOLLOWING CODE DOESN'T WORK
if ([[NSUserDefaults standardUserDefaults] objectForKey:ICLOUD_TOKEN_KEY]) {
// cloud permissions are good to go.
NSLog(#"cloud permissions are good to go.");
dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSURL *url = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier: nil];
NSLog(#"bikky url: %#", url);
if (url) { // != nil) {
// Your app can write to the ubiquity container
dispatch_async (dispatch_get_main_queue (), ^(void) {
// On the main thread, update UI and state as appropriate
[self setupCoreDataInTheCloudForDocument:document];
});
}
});
}
}
- (void) setupCoreDataInTheCloudForDocument:(UIManagedDocument *)document
{
NSLog(#"1. cc.setupCoreDataInTheCloudForDocument: %#", document);
NSURL *localURL = [self localFileURL:LOCAL_DATA_STORE_FILE_NAME];
NSURL *cloudURL = [self ubiquityDataFileURL:CLOUD_DATA_STORE_FILE_NAME];
// Set the persistent store options to point to the cloud
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
// [document.fileURL lastPathComponent], NSPersistentStoreUbiquitousContentNameKey,
localURL, NSPersistentStoreUbiquitousContentNameKey,
cloudURL, NSPersistentStoreUbiquitousContentURLKey,
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
document.persistentStoreOptions = options;
NSLog(#"2. cc.document.persistentStoreOptions: %#", document.persistentStoreOptions);
// Register as presenter
self.coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:document];
[NSFileCoordinator addFilePresenter:document];
// // THIS CODE IS INLINE FROM iOS 5 COOK BOOK
// // I EXECUTE THIS IN (void)performWithDocument:(OnDocumentReady)onDocumentReady
//
// // Check at the local sandbox
// if ([self isLocal:LOCAL_DATA_STORE_FILE_NAME])
// {
// NSLog(#"Attempting to open existing file");
// [document openWithCompletionHandler:^(BOOL success){
// if (!success) {NSLog(#"Error opening file"); return;}
// NSLog(#"File opened");
// }];
// }
// else
// {
// NSLog(#"Creating file.");
// // 1. save it out, 2. close it, 3. read it back in.
// [document saveToURL:localURL
// forSaveOperation:UIDocumentSaveForCreating
// completionHandler:^(BOOL success){
// if (!success) { NSLog(#"7. Error creating file"); return; }
// NSLog(#"File created");
// [document closeWithCompletionHandler:^(BOOL success){
// NSLog(#"Closed new file: %#", success ? #"Success" : #"Failure");
//
// [document openWithCompletionHandler:^(BOOL success){
// if (!success) {NSLog(#"Error opening file for reading."); return;}
// NSLog(#"File opened for reading.");
// }];
// }];
// }];
// }
//}
#end
I call the singleton as follows …
- (void)viewDidLoad
{
[super viewDidLoad];
if (!self.codeGeneratorDataStore) {
NSLog(#"MVC.viewDidLoad: Grab local instance of document from data store singleton");
[[DataStore sharedDocumentHandler] performWithDocument:^(UIManagedDocument *document) {
self.codeGeneratorDataStore = document;
// Do stuff with the document, set up a fetched results controller, whatever.
// NSLog(#"IN: _codeGeneratorDataStore.documentState : %u", _codeGeneratorDataStore.documentState);
if (![self.codeGeneratorDataStore.persistentStoreOptions objectForKey:NSPersistentStoreUbiquitousContentNameKey]) {
NSLog(#"MVC.viewDidLoad: We have a document, hooking up cloud now ... \n%#", document);
[self.cloudConnector hookUpCloudForDocument:self.codeGeneratorDataStore];
}
[self setupFetchedResultsController];
}];
NSLog(#"GVC.viewDidLoad: Subscribing to notifications");
[self subscribeToCloudNotifications];
}
[self checkDocumentStatus];
}
Then I check the document status …
- (void)checkDocumentStatus {
NSLog(#"MVC.checkDocumentStatus\n Document: %#\n persistentStoreOptions: %#", self.codeGeneratorDataStore, self.codeGeneratorDataStore.persistentStoreOptions);
if (![[
NSFileManager defaultManager] fileExistsAtPath:[self.codeGeneratorDataStore.fileURL path]]) {
[self.codeGeneratorDataStore saveToURL:self.codeGeneratorDataStore.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
NSLog(#"1. MVC self.codeGeneratorDataStore.documentState: %#", [NSString stringWithFormat:#"%u", self.codeGeneratorDataStore.documentState]);
}];
} else if (self.codeGeneratorDataStore.documentState == UIDocumentStateClosed) {
[self.codeGeneratorDataStore openWithCompletionHandler:^(BOOL succes) {
NSLog(#"2. MVC self.codeGeneratorDataStore.documentState: %#", [NSString stringWithFormat:#"%u", self.codeGeneratorDataStore.documentState]);
}];
} else if (self.codeGeneratorDataStore.documentState == UIDocumentStateNormal) {
NSLog(#"3. MVC self.codeGeneratorDataStore.documentState: %#", [NSString stringWithFormat:#"%u", self.codeGeneratorDataStore.documentState]);
}
}
Setup and register for notifications ...
- (void)subscribeToCloudNotifications
{
// subscribe to the NSPersistentStoreDidImportUbiquitousContentChangesNotification notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(documentStateChanged:)
name:UIDocumentStateChangedNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(documentContentsDidUpdate:)
name:NSPersistentStoreDidImportUbiquitousContentChangesNotification
object:nil];
NSLog(#"MVC.subscribeToCloudNotifications: notification subscriptions are good to go");
}
- (void) documentContentsDidUpdate: (NSNotification *) notification
{
NSLog(#"CMVC notification: documentContentsDidUpdate");
NSDictionary *userInfo = notification.userInfo;
[self.codeGeneratorDataStore.managedObjectContext performBlock:^{[self mergeiCloudChanges:userInfo forContext:self.codeGeneratorDataStore.managedObjectContext];}];
}
// When notified about a cloud update, start merging changes
- (void)documentStateChanged: (NSNotification *)notification
{
NSLog(#"CMVC notification: documentStateChanged");
// NSLog(#"Document state change: %#", [CloudHelper documentState:self.codeGeneratorDataStore.documentState]);
UIDocumentState documentState = self.codeGeneratorDataStore.documentState;
if (documentState & UIDocumentStateInConflict)
{
// This application uses a basic newest version wins conflict resolution strategy
NSURL *documentURL = self.codeGeneratorDataStore.fileURL;
NSArray *conflictVersions = [NSFileVersion unresolvedConflictVersionsOfItemAtURL:documentURL];
for (NSFileVersion *fileVersion in conflictVersions) {
fileVersion.resolved = YES;
}
[NSFileVersion removeOtherVersionsOfItemAtURL:documentURL error:nil];
}
}
// Merge the iCloud changes into the managed context
- (void)mergeiCloudChanges:(NSDictionary*)userInfo forContext:(NSManagedObjectContext*)managedObjectContext
{
#autoreleasepool
{
// NSLog(#"Merging changes from cloud");
NSMutableDictionary *localUserInfo = [NSMutableDictionary dictionary];
NSSet *allInvalidations = [userInfo objectForKey:NSInvalidatedAllObjectsKey];
NSString *materializeKeys[] = { NSDeletedObjectsKey, NSInsertedObjectsKey };
if (nil == allInvalidations)
{
// (1) we always materialize deletions to ensure delete propagation happens correctly, especially with
// more complex scenarios like merge conflicts and undo. Without this, future echoes may
// erroreously resurrect objects and cause dangling foreign keys
// (2) we always materialize insertions to make new entries visible to the UI
int c = (sizeof(materializeKeys) / sizeof(NSString *));
for (int i = 0; i < c; i++)
{
NSSet *set = [userInfo objectForKey:materializeKeys[i]];
if ([set count] > 0)
{
NSMutableSet *objectSet = [NSMutableSet set];
for (NSManagedObjectID *moid in set)
[objectSet addObject:[managedObjectContext objectWithID:moid]];
[localUserInfo setObject:objectSet forKey:materializeKeys[i]];
}
}
// (3) we do not materialize updates to objects we are not currently using
// (4) we do not materialize refreshes to objects we are not currently using
// (5) we do not materialize invalidations to objects we are not currently using
NSString *noMaterializeKeys[] = { NSUpdatedObjectsKey, NSRefreshedObjectsKey, NSInvalidatedObjectsKey };
c = (sizeof(noMaterializeKeys) / sizeof(NSString*));
for (int i = 0; i < 2; i++)
{
NSSet *set = [userInfo objectForKey:noMaterializeKeys[i]];
if ([set count] > 0)
{
NSMutableSet *objectSet = [NSMutableSet set];
for (NSManagedObjectID *moid in set)
{
NSManagedObject *realObj = [managedObjectContext objectRegisteredForID:moid];
if (realObj)
[objectSet addObject:realObj];
}
[localUserInfo setObject:objectSet forKey:noMaterializeKeys[i]];
}
}
NSNotification *fakeSave = [NSNotification notificationWithName:NSManagedObjectContextDidSaveNotification object:self userInfo:localUserInfo];
[managedObjectContext mergeChangesFromContextDidSaveNotification:fakeSave];
}
else
[localUserInfo setObject:allInvalidations forKey:NSInvalidatedAllObjectsKey];
[managedObjectContext processPendingChanges];
// [self performSelectorOnMainThread:#selector(performFetch) withObject:nil waitUntilDone:NO];
}
}
and we’re good to go - the app runs but with no network activity. Why aren't the transaction logs being uploaded?
Any assistance would be greatly appreciated here. Its been doing my head in for a week now and i’m at my wit’s end.
I answered this question previously in email, but I'm positing my answer here as well for completeness.
Is this an iOS 7-only app, or does it need to support iOS 5 or 6 as well?
I would not recommend mixing iCloud and Core Data in anything that still has to support iOS 5 and 6. However, the new support for iOS 7 seems pretty solid. I haven’t deployed a multi-million user app with it—so I wouldn’t say it’s battle tested, but it seems to be a lot more robust. In my preliminary testing, it handled everything I threw at it with no problems.
Also, is the sample code that you’re following up-to-date for iOS 7? It looks like it’s following some older patterns. This could cause a lot of problems, since the way the system operates has changed considerably in iOS 7.
For example, in iOS 7, we generally only need to set the NSPersistentStoreUbiquitousContentNameKey. Unless we’re trying to support a legacy core data stack, we don’t need to set the content URL key.
Also, there is a huge difference in how the core data stack is set up under iOS 7. When we first set up an iCloud persistent store, iOS 7 will create a local copy (the fallback store) for us automatically. We can use the store—but the changes will only be stored locally. When the iCloud version is ready, it will then send a NSPersistentStoreCoordinatorStoresWillChangeNotification. We’re supposed to save any changes and then reset our managed object context.
Again, it looks like your code is manually setting up its own fallback store—which may be part of the problem.
If you’re working from old examples, I’d actually recommend throwing that code away and starting over. Watch the “What’s New in Core Data and iCloud” video from WWDC 2013. It does a great rundown on the new technologies, and the current best practices.
My guess is that you’re getting the fallback store, but it’s never switching over to the iCloud store. Look in the console log when you run the app. You should see something like the following:
-[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](754): CoreData: p Ubiquity:
mobile~C9C8554A-BD44-43C3-AC54-603046EF0162:com~freelancemad p science~HealthBeat
Using local storage: 1
Here, “Using local storage: 1” means you’re using the fallback store. In a minute or two (or sometimes longer) you’ll see a similar message with “Using local storage: 0”. That means you’re now using the iCloud store.
Also, when you run the application, be sure to open the Debug Navigator in Xcode. It should have a profiler for iCloud, that shows you whenever data was uploaded to or downloaded from the cloud.
I hope that helps,
-Rich-
Related
I'm trying to do login page and logout page. I completed the api calling and login successfully, but i am trying to check whether the user is already logged in or not. if the user is logged in and kills the app, and after sometimes the user opens the app means the app should show the home page, but my app shows the sign in page. I tried the below code along with api calling.
NSString *mailID=mailTextField.text;
NSString *password=passwordTextField.text;
NSString *noteDataString = [NSString stringWithFormat:#"email=%#&password=%#",mailID,password];
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.HTTPAdditionalHeaders = #{#"language": #"en"};
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSURL *url = [NSURL URLWithString:#"https://qa-user.moneyceoapp.com/user/login"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSData *data = [noteDataString dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPBody=data;
request.HTTPMethod = #"POST";
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *httpResponse= (NSHTTPURLResponse *)response;
if(httpResponse.statusCode == 200)
{
NSDictionary *responseDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"The response is - %#",responseDictionary);
NSInteger status = [[responseDictionary objectForKey:#"status"] integerValue];
if(status == 1)
{
NSLog(#"Login SUCCESS");
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
[defs setObject:mailID forKey:#"email"];
[defs setObject:password forKey:#"password"];
[defs synchronize];
[[NSOperationQueue mainQueue] addOperationWithBlock:^
{
if(mailID && password)
{
HomePageVC *homepageVC = [[HomePageVC alloc] initWithNibName:#"HomePageVC" bundle:nil];
[self.navigationController pushViewController:homepageVC animated:YES];
}
}];
}
else
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^
{
NSLog(#"Login FAILURE");
[self alertView:#"Invalid user account"];
}];
}
}
else
{
NSLog(#"Error");
}
}];
[postDataTask resume];
I use class that uses NSUserDefaults to create a dictionary with the parameters of the user's login information. Also, I use Boolean values to check a value equivalently named joinedApp.
*DataModel.h*
//
#class Message;
// The main data model object
#interface DataModel : NSObject
// The complete history of messages this user has sent and received, in
// chronological order (oldest first).
#property (nonatomic, strong) NSMutableArray* messages;
// Loads the list of messages from a file.
- (void)loadMessages;
// Saves the list of messages to a file.
- (void)saveMessages;
// Adds a message that the user composed himself or that we received through
// a push notification. Returns the index of the new message in the list of
// messages.
- (int)addMessage:(Message*)message;
// Get and set the user's nickname.
- (NSString*)nickname;
- (void)setNickname:(NSString*)name;
// Get and set the secret code that the user is registered for.
- (NSString*)secretCode;
- (void)setSecretCode:(NSString*)string;
// Determines whether the user has successfully joined a chat.
- (BOOL)joinedChat;
- (void)setJoinedChat:(BOOL)value;
- (BOOL)joinedApp;
- (void)setJoinedApp:(BOOL)value;
- (NSString*)userId;
- (NSString*)deviceToken;
- (void)setDeviceToken:(NSString*)token;
#end
DataModel.m
#import "DataModel.h"
#import "Message.h"
#import "ViewController.h"
// We store our settings in the NSUserDefaults dictionary using these keys
static NSString* const NicknameKey = #"Nickname";
static NSString* const SecretCodeKey = #"SecretCode";
static NSString* const JoinedChatKey = #"JoinedChat";
static NSString* const JoinedAppKey = #"JoinedApp";
static NSString* const DeviceTokenKey = #"DeviceToken";
static NSString* const UserId = #"UserId";
#implementation DataModel
+ (void)initialize
{
if (self == [DataModel class])
{
// Register default values for our settings
[[NSUserDefaults standardUserDefaults] registerDefaults:
#{NicknameKey: #"",
SecretCodeKey: #"",
JoinedChatKey: #0,
JoinedAppKey: #0,
DeviceTokenKey: #"0",
UserId:#""}];
}
}
// Returns the path to the Messages.plist file in the app's Documents directory
- (NSString*)messagesPath
{
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentsDirectory = paths[0];
return [documentsDirectory stringByAppendingPathComponent:#"Messages.plist"];
}
- (void)loadMessages
{
NSString* path = [self messagesPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:path])
{
// We store the messages in a plist file inside the app's Documents
// directory. The Message object conforms to the NSCoding protocol,
// which means that it can "freeze" itself into a data structure that
// can be saved into a plist file. So can the NSMutableArray that holds
// these Message objects. When we load the plist back in, the array and
// its Messages "unfreeze" and are restored to their old state.
NSData* data = [[NSData alloc] initWithContentsOfFile:path];
NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
self.messages = [unarchiver decodeObjectForKey:#"Messages"];
[unarchiver finishDecoding];
}
else
{
self.messages = [NSMutableArray arrayWithCapacity:20];
}
}
- (void)saveMessages
{
NSMutableData* data = [[NSMutableData alloc] init];
NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:self.messages forKey:#"Messages"];
[archiver finishEncoding];
[data writeToFile:[self messagesPath] atomically:YES];
}
- (int)addMessage:(Message*)message
{
[self.messages addObject:message];
[self saveMessages];
return self.messages.count - 1;
}
- (NSString*)nickname
{
return [[NSUserDefaults standardUserDefaults] stringForKey:NicknameKey];
}
- (void)setNickname:(NSString*)name
{
[[NSUserDefaults standardUserDefaults] setObject:name forKey:NicknameKey];
}
- (NSString*)secretCode
{
return [[NSUserDefaults standardUserDefaults] stringForKey:SecretCodeKey];
}
- (void)setSecretCode:(NSString*)string
{
[[NSUserDefaults standardUserDefaults] setObject:string forKey:SecretCodeKey];
}
- (BOOL)joinedApp
{
return [[NSUserDefaults standardUserDefaults] boolForKey:JoinedAppKey];
}
- (void)setJoinedApp:(BOOL)value
{
[[NSUserDefaults standardUserDefaults] setBool:value forKey:JoinedAppKey];
}
- (BOOL)joinedChat
{
return [[NSUserDefaults standardUserDefaults] boolForKey:JoinedChatKey];
}
- (void)setJoinedChat:(BOOL)value
{
[[NSUserDefaults standardUserDefaults] setBool:value forKey:JoinedChatKey];
}
- (NSString*)userId
{
NSString *userId = [[NSUserDefaults standardUserDefaults] stringForKey:UserId];
if (userId == nil || userId.length == 0) {
userId = [[[NSUUID UUID] UUIDString] stringByReplacingOccurrencesOfString:#"-" withString:#""];
[[NSUserDefaults standardUserDefaults] setObject:userId forKey:UserId];
}
return userId;
}
- (NSString*)deviceToken //used in appdelegate
{
return [[NSUserDefaults standardUserDefaults] stringForKey:DeviceTokenKey];
}
- (void)setDeviceToken:(NSString*)token //used in app delegate
{
[[NSUserDefaults standardUserDefaults] setObject:token forKey:DeviceTokenKey];
}
#end
I then call the message in the viewIsLoaded method and print verification in the debugger window.
*ViewController.h*
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#class DataModel;
#interface ViewController : UIViewController
#property (nonatomic, strong, readonly) DataModel* dataModel;
#property (retain, strong) UIImage* Image;
#end
ViewController.m
#import "ViewController.h"
#import "DataModel.h"
#import "PushChatStarter-Swift.h"
#implementation ViewController
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
_dataModel = [[DataModel alloc] init];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
MockApiclient *client = [MockApiclient new];
[client executeRequest];
[self.dataModel setJoinedApp:YES];
if ([_dataModel joinedApp] == YES)
{
NSLog(#"hi from viewcontroller 1 ");//Here you know which button has pressed
}
...
}
NSUserDefaults:
An interface to the user’s defaults database, where you store key-value pairs persistently across launches of your app.
I also call joinedApp from the login method being used along with other messages.
I want to know whether my users are downloading my application for the first time or upgrading the old version.
How can I get that information when application is launched?
Option 1.
Save the bundle version somewhere and check if it differs from
[[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleVersion"]]
on each app startup.
Option 2.
Use a category on UIApplication that let's you see if the app was updated.
UIApplication+Versioning.h
#protocol UIApplicationDelegate<UIApplicationDelegate>
#optional
- (void)application:(UIApplication *)application
willUpdateToVersion: (NSString*) newVersion
fromVersion: (NSString*) previousVersion;
- (void)application:(UIApplication *)application
didUpdateToVersion: (NSString*) newVersion
fromVersion: (NSString*) previousVersion;
#end
#interface UIApplication (Versioning)
#end
UIApplication+Versioning.m
#import "UIApplication+Versioning.h"
#import <objc/message.h>
#import <objc/runtime.h>
static NSString* UIApplicationVersionFileName = #"app.version";
#implementation UIApplication (Versioning)
+ (void)load
{
original = class_getInstanceMethod(self, #selector(setDelegate:));
swizzled = class_getInstanceMethod(self, #selector(swizzled_setDelegate:));
method_exchangeImplementations(original, swizzled);
}
- (void)swizzled_setDelegate:(id<UIApplicationDelegate>)delegate
{
IMP implementation = class_getMethodImplementation([self class], #selector(swizzled_application:didFinishLaunchingWithOptions:));
class_addMethod([delegate class], #selector(swizzled_application:didFinishLaunchingWithOptions:), implementation, "B#:##");
original = class_getInstanceMethod([delegate class], #selector(application:didFinishLaunchingWithOptions:));
swizzled = class_getInstanceMethod([delegate class], #selector(swizzled_application:didFinishLaunchingWithOptions:));
method_exchangeImplementations(original, swizzled);
[self swizzled_setDelegate: delegate];
}
- (BOOL)swizzled_application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Check for a version change
NSError* error;
NSArray* directories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* versionFilePath = [[directories objectAtIndex: 0] stringByAppendingPathComponent:UIApplicationVersionFileName];
NSString* oldVersion = [NSString stringWithContentsOfFile:versionFilePath
encoding:NSUTF8StringEncoding
error:&error];
NSString* currentVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey: #"CFBundleVersion"];
switch (error.code)
{
case NSFileReadNoSuchFileError:
{
// Delegate methods will not be called first time
oldVersion = [currentVersion copy];
[currentVersion writeToFile: versionFilePath
atomically: YES
encoding: NSUTF8StringEncoding
error: &error];
break;
}
default:
{
NSLog(#"Warning: An error occured will loading the application version file -> Recreating file");
[[NSFileManager defaultManager] removeItemAtPath: versionFilePath
error: nil];
oldVersion = [currentVersion copy];
[currentVersion writeToFile: versionFilePath
atomically: YES
encoding: NSUTF8StringEncoding
error: &error];
break;
}
}
if( ![oldVersion isEqualToString: currentVersion] )
{
if ([[application delegate] respondsToSelector: #selector(application:willUpdateToVersion:fromVersion:)])
{
objc_msgSend([application delegate], #selector(application:willUpdateToVersion:fromVersion:), currentVersion, oldVersion);
}
[currentVersion writeToFile:versionFilePath
atomically:YES
encoding:NSUTF8StringEncoding
error:&error];
if ([[application delegate] respondsToSelector: #selector(application:didUpdateToVersion:fromVersion:)])
{
objc_msgSend([application delegate], #selector(application:willUpdateToVersion:fromVersion:), currentVersion, oldVersion);
}
}
SEL realSelector = #selector(swizzled_application:didFinishLaunchingWithOptions:);
return (BOOL) objc_msgSend([application delegate], realSelector, application, launchOptions);
}
#end
I have class for playing sound in app.
I implemented on/off switch(on GUI), for disabling and enablining sound play.
I am using BOOL property for that and this is working.
Now I am trying to implement saving that BOOL (is sound on/off) in file so that next time when app is started state is automatically restored.
For that I am using NSCoding protocol, archiving is working but I have problem with unarchiving.
My app will not start it will just show black screen.
This is my code, only part that I think it is important.
GTN_Sound.h
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h> // for playing sound
#interface GTN_Sound : NSObject <NSCoding>
#property(nonatomic, readwrite, unsafe_unretained) BOOL isSoundOn;
+ (id)sharedManager;
- (void)playWinSound;
- (void)playLoseSound;
#end
GTN_Sound.m
#pragma mark - NSCoding Methods
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeBool:self.isSoundOn forKey:#"isSoundOn"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [GTN_Sound sharedManager];
if (self) {
_isSoundOn = [aDecoder decodeBoolForKey:#"isSoundOn"];
}
return self;
}
I think that code is so far so good ?
Continuation of GTN_Sound.m
#pragma mark - itemArchivePath Method
- (NSString *)itemArchivePath
{
// Make sure that the first argument is NSDocumentDirectory
// and not NSDocumentationDirectory
NSArray *documentDirectories =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
// Get the one document directory from that list
NSString *documentDirectory = [documentDirectories firstObject];
return [documentDirectory stringByAppendingPathComponent:#"sound.archive"];
}
#pragma mark - custom seter Method
- (void)setIsSoundOn:(BOOL)theBoolean {
NSLog(#"My custom setter\n");
if(_isSoundOn != theBoolean){
_isSoundOn = theBoolean;
NSString *path = [self itemArchivePath];
[NSKeyedArchiver archiveRootObject:self toFile:path]; // this is doing save
}
}
It is done that for every time when switch on GUI is changed I do the savings.
This look fine from my side, because I am not expecting that user will change this many times.
Now the unarchiving comes and I thin that here are some problems.
#pragma mark - Singleton Methods
+ (id)sharedManager {
static GTN_Sound *sharedMyManager = nil;
// to be thread safe
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] initPrivate];
});
return sharedMyManager;
}
- (instancetype)init
{
#throw [NSException exceptionWithName:#"Singleton"
reason:#"Use +[GTN_Sound sharedManager]"
userInfo:nil];
return nil;
}
// Here is the real (secret) initializer
- (instancetype)initPrivate
{
self = [super init];
if (self) {
NSString *path = [self itemArchivePath]; // do as iVar, for futture
self = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
//_isSoundOn = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
}
return self;
}
I think that problem is in this line
self = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
But have no idea how to fix it.
I have read that when I am doing archiving, that I need to archive all properties of object.
Does this apply to private ivars also ?
Any help is appreciated.
Thanks
You can only archive NSObject inheriting objects. ie. NSNumber
[NSKeyedArchiver archiveRootObject:#YES toFile:path]; //to save
_isSoundOn = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] boolValue]; //to load
I am trying to load a csv file in core data when the application is ran for the first time. On another post on stackoverflow found here (What is the fastest way to load a large CSV file into core data), I found out how to do that.
I am using the same code form the provided function: populateDB, in my controller and calling the function if the data has never been loaded before (first run). However, xcode is giving me an error:
No visible #interface for ...Controller declares the selector persistentStoreCoordinator.
The function is:
-(void)populateDB{
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
NSManagedObjectContext *context;
if (coordinator != nil) {
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:coordinator];
}
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"input" ofType:#"txt"];
if (filePath) {
NSString * myText = [[NSString alloc]
initWithContentsOfFile:filePath
encoding:NSUTF8StringEncoding
error:nil];
if (myText) {
__block int count = 0;
[myText enumerateLinesUsingBlock:^(NSString * line, BOOL * stop) {
line=[line stringByReplacingOccurrencesOfString:#"\t" withString:#" "];
NSArray *lineComponents=[line componentsSeparatedByString:#" "];
if(lineComponents){
if([lineComponents count]==3){
float f=[[lineComponents objectAtIndex:0] floatValue];
NSNumber *number=[NSNumber numberWithFloat:f];
NSString *string1=[lineComponents objectAtIndex:1];
NSString *string2=[lineComponents objectAtIndex:2];
NSManagedObject *object=[NSEntityDescription insertNewObjectForEntityForName:#"Bigram" inManagedObjectContext:context];
[object setValue:number forKey:#"number"];
[object setValue:string1 forKey:#"string1"];
[object setValue:string2 forKey:#"string2"];
NSError *error;
count++;
if(count>=1000){
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
count=0;
}
}
}
}];
NSLog(#"done importing");
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
}
}
I am picking up iOS again after 3 years of absence and I have never dived into this part of the SDK before. I would greatly appreciate any help with this issue...
The code below shows you an example to load csv file in Core Data using the class CSVParser.hof Matt Gallagher and supposing an entity MyEntity
// CSV File input.csv and not input.txt separate by space #" "
CSVParser *csvParser = [[CSVParser alloc] initWithContentOfFile:[NSBundle mainBundle] pathForResource:#"input" ofType:#"csv" separator:#" "];
// Array with all your data from CSV
NSArray *data = [csvParser parseFile];
// Your entity from Core Data
MyEntity *myEntity = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
for (NSDictionary *dic in data)
{
fetchRequest.entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:context];
if (!entity)
entity = [NSEntityDescription insertNewObjectForEntityForName:#"MyEntity"
inManagedObjectContext:context];
[entity setValue:number forKey:#"number"];
[entity setValue:string1 forKey:#"string1"];
[entity setValue:string2 forKey:#"string2"];
}
// Save the context
[managedObjectContext save:nil];
CVSParser.h using ARC :
//
// CSVParser.h
// CSVImporter
//
// Created by Matt Gallagher on 2009/11/30.
// Copyright 2009 Matt Gallagher. All rights reserved.
//
// Permission is given to use this source code file, free of charge, in any
// project, commercial or otherwise, entirely at your risk, with the condition
// that any redistribution (in part or whole) of source code must retain
// this copyright and permission notice. Attribution in compiled projects is
// appreciated but not required.
//
// Source : http://cocoawithlove.com/2009/11/writing-parser-using-nsscanner-csv.html
#interface CSVParser : NSObject
{
NSString *csvString;
NSString *separator;
NSScanner *scanner;
BOOL hasHeader;
NSMutableArray *fieldNames;
id receiver;
SEL receiverSelector;
NSCharacterSet *endTextCharacterSet;
BOOL separatorIsSingleChar;
}
- (id)initWithString:(NSString *)aCSVString
separator:(NSString *)aSeparatorString;
- (id)initWithContentOfFile:(NSString *)aPath
separator:(NSString *)aSeparatorString;
- (id)initWithString:(NSString *)aCSVString
separator:(NSString *)aSeparatorString
hasHeader:(BOOL)header
fieldNames:(NSArray *)names;
- (id)initWithContentOfFile:(NSString *)aPath
separator:(NSString *)aSeparatorString
hasHeader:(BOOL)header
fieldNames:(NSArray *)names;
- (NSArray *)arrayOfParsedRows;
- (void)parseRowsForReceiver:(id)aReceiver selector:(SEL)aSelector;
- (NSArray *)parseFile;
- (NSMutableArray *)parseHeader;
- (NSDictionary *)parseRecord;
- (NSString *)parseName;
- (NSString *)parseField;
- (NSString *)parseEscaped;
- (NSString *)parseNonEscaped;
- (NSString *)parseDoubleQuote;
- (NSString *)parseSeparator;
- (NSString *)parseLineSeparator;
- (NSString *)parseTwoDoubleQuotes;
- (NSString *)parseTextData;
#end
CVSParser.m using ARC :
//
// CSVParser.m
// CSVImporter
//
// Created by Matt Gallagher on 2009/11/30.
// Copyright 2009 Matt Gallagher. All rights reserved.
//
// Permission is given to use this source code file, free of charge, in any
// project, commercial or otherwise, entirely at your risk, with the condition
// that any redistribution (in part or whole) of source code must retain
// this copyright and permission notice. Attribution in compiled projects is
// appreciated but not required.
//
#import "CSVParser.h"
#implementation CSVParser
//
// initWithString:separator:hasHeader:fieldNames:
//
// Parameters:
// aCSVString - the string that will be parsed
// aSeparatorString - the separator (normally "," or "\t")
// header - if YES, treats the first row as a list of field names
// names - a list of field names (will have no effect if header is YES)
//
// returns the initialized object (nil on failure)
//
- (id)initWithString:(NSString *)aCSVString
separator:(NSString *)aSeparatorString
hasHeader:(BOOL)header
fieldNames:(NSArray *)names
{
self = [super init];
if (self)
{
csvString = [aCSVString retain];
separator = [aSeparatorString retain];
NSAssert([separator length] > 0 &&
[separator rangeOfString:#"\""].location == NSNotFound &&
[separator rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]].location == NSNotFound,
#"CSV separator string must not be empty and must not contain the double quote character or newline characters.");
NSMutableCharacterSet *endTextMutableCharacterSet =
[[NSCharacterSet newlineCharacterSet] mutableCopy];
[endTextMutableCharacterSet addCharactersInString:#"\""];
[endTextMutableCharacterSet addCharactersInString:[separator substringToIndex:1]];
endTextCharacterSet = endTextMutableCharacterSet;
if ([separator length] == 1)
{
separatorIsSingleChar = YES;
}
hasHeader = header;
fieldNames = [names mutableCopy];
}
return self;
}
- (id)initWithString:(NSString *)aCSVString
separator:(NSString *)aSeparatorString
{
return [self initWithString:aCSVString
separator:aSeparatorString
hasHeader:YES
fieldNames:nil];
}
- (id)initWithContentOfFile:(NSString *)aPath
separator:(NSString *)aSeparatorString
{
return [self initWithString:[NSString stringWithContentsOfFile:aPath
encoding:NSUTF8StringEncoding
error:nil]
separator:aSeparatorString];
}
- (id)initWithContentOfFile:(NSString *)aPath
separator:(NSString *)aSeparatorString
hasHeader:(BOOL)header
fieldNames:(NSArray *)names
{
return [self initWithString:[NSString stringWithContentsOfFile:aPath
encoding:NSUTF8StringEncoding
error:nil]
separator:aSeparatorString
hasHeader:header
fieldNames:names];
}
//
// dealloc
//
// Releases instance memory.
//
- (void)dealloc
{
[csvString release];
[separator release];
[fieldNames release];
[endTextCharacterSet release];
[super dealloc];
}
//
// arrayOfParsedRows
//
// Performs a parsing of the csvString, returning the entire result.
//
// returns the array of all parsed row records
//
- (NSArray *)arrayOfParsedRows
{
scanner = [[NSScanner alloc] initWithString:csvString];
[scanner setCharactersToBeSkipped:[[[NSCharacterSet alloc] init] autorelease]];
NSArray *result = [self parseFile];
[scanner release];
scanner = nil;
return result;
}
//
// parseRowsForReceiver:selector:
//
// Performs a parsing of the csvString, sending the entries, 1 row at a time,
// to the receiver.
//
// Parameters:
// aReceiver - the target that will receive each row as it is parsed
// aSelector - the selector that will receive each row as it is parsed
// (should be a method that takes a single NSDictionary argument)
//
- (void)parseRowsForReceiver:(id)aReceiver selector:(SEL)aSelector
{
scanner = [[NSScanner alloc] initWithString:csvString];
[scanner setCharactersToBeSkipped:[[[NSCharacterSet alloc] init] autorelease]];
receiver = [aReceiver retain];
receiverSelector = aSelector;
[self parseFile];
[scanner release];
scanner = nil;
[receiver release];
receiver = nil;
}
//
// parseFile
//
// Attempts to parse a file from the current scan location.
//
// returns the parsed results if successful and receiver is nil, otherwise
// returns nil when done or on failure.
//
- (NSArray *)parseFile
{
scanner = [[NSScanner alloc] initWithString:csvString];
[scanner setCharactersToBeSkipped:[[[NSCharacterSet alloc] init] autorelease]];
if (hasHeader)
{
if (fieldNames)
{
[fieldNames release];
}
fieldNames = [[self parseHeader] retain];
if (!fieldNames || ![self parseLineSeparator])
{
return nil;
}
}
NSMutableArray *records = nil;
if (!receiver)
{
records = [NSMutableArray array];
}
NSDictionary *record = [[self parseRecord] retain];
if (!record)
{
return nil;
}
while (record)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (receiver)
{
[receiver performSelector:receiverSelector withObject:record];
}
else
{
[records addObject:record];
}
[record release];
if (![self parseLineSeparator])
{
break;
}
record = [[self parseRecord] retain];
[pool drain];
}
[scanner release];
scanner = nil;
return records;
}
//
// parseHeader
//
// Attempts to parse a header row from the current scan location.
//
// returns the array of parsed field names or nil on parse failure.
//
- (NSMutableArray *)parseHeader
{
NSString *name = [self parseName];
if (!name)
{
return nil;
}
NSMutableArray *names = [NSMutableArray array];
while (name)
{
[names addObject:name];
if (![self parseSeparator])
{
break;
}
name = [self parseName];
}
return names;
}
//
// parseRecord
//
// Attempts to parse a record from the current scan location. The record
// dictionary will use the fieldNames as keys, or FIELD_X for each column
// X-1 if no fieldName exists for a given column.
//
// returns the parsed record as a dictionary, or nil on failure.
//
- (NSDictionary *)parseRecord
{
//
// Special case: return nil if the line is blank. Without this special case,
// it would parse as a single blank field.
//
if ([self parseLineSeparator] || [scanner isAtEnd])
{
return nil;
}
NSString *field = [self parseField];
if (!field)
{
return nil;
}
NSInteger fieldNamesCount = [fieldNames count];
NSInteger fieldCount = 0;
NSMutableDictionary *record =
[NSMutableDictionary dictionaryWithCapacity:[fieldNames count]];
while (field)
{
NSString *fieldName;
if (fieldNamesCount > fieldCount)
{
fieldName = [fieldNames objectAtIndex:fieldCount];
}
else
{
fieldName = [NSString stringWithFormat:#"FIELD_%d", fieldCount + 1];
[fieldNames addObject:fieldName];
fieldNamesCount++;
}
[record setObject:field forKey:fieldName];
fieldCount++;
if (![self parseSeparator])
{
break;
}
field = [self parseField];
}
return record;
}
//
// parseName
//
// Attempts to parse a name from the current scan location.
//
// returns the name or nil.
//
- (NSString *)parseName
{
return [self parseField];
}
//
// parseField
//
// Attempts to parse a field from the current scan location.
//
// returns the field or nil
//
- (NSString *)parseField
{
NSString *escapedString = [self parseEscaped];
if (escapedString)
{
return escapedString;
}
NSString *nonEscapedString = [self parseNonEscaped];
if (nonEscapedString)
{
return nonEscapedString;
}
//
// Special case: if the current location is immediately
// followed by a separator, then the field is a valid, empty string.
//
NSInteger currentLocation = [scanner scanLocation];
if ([self parseSeparator] || [self parseLineSeparator] || [scanner isAtEnd])
{
[scanner setScanLocation:currentLocation];
return #"";
}
return nil;
}
//
// parseEscaped
//
// Attempts to parse an escaped field value from the current scan location.
//
// returns the field value or nil.
//
- (NSString *)parseEscaped
{
if (![self parseDoubleQuote])
{
return nil;
}
NSString *accumulatedData = [NSString string];
while (YES)
{
NSString *fragment = [self parseTextData];
if (!fragment)
{
fragment = [self parseSeparator];
if (!fragment)
{
fragment = [self parseLineSeparator];
if (!fragment)
{
if ([self parseTwoDoubleQuotes])
{
fragment = #"\"";
}
else
{
break;
}
}
}
}
accumulatedData = [accumulatedData stringByAppendingString:fragment];
}
if (![self parseDoubleQuote])
{
return nil;
}
return accumulatedData;
}
//
// parseNonEscaped
//
// Attempts to parse a non-escaped field value from the current scan location.
//
// returns the field value or nil.
//
- (NSString *)parseNonEscaped
{
return [self parseTextData];
}
//
// parseTwoDoubleQuotes
//
// Attempts to parse two double quotes from the current scan location.
//
// returns a string containing two double quotes or nil.
//
- (NSString *)parseTwoDoubleQuotes
{
if ([scanner scanString:#"\"\"" intoString:NULL])
{
return #"\"\"";
}
return nil;
}
//
// parseDoubleQuote
//
// Attempts to parse a double quote from the current scan location.
//
// returns #"\"" or nil.
//
- (NSString *)parseDoubleQuote
{
if ([scanner scanString:#"\"" intoString:NULL])
{
return #"\"";
}
return nil;
}
//
// parseSeparator
//
// Attempts to parse the separator string from the current scan location.
//
// returns the separator string or nil.
//
- (NSString *)parseSeparator
{
if ([scanner scanString:separator intoString:NULL])
{
return separator;
}
return nil;
}
//
// parseLineSeparator
//
// Attempts to parse newline characters from the current scan location.
//
// returns a string containing one or more newline characters or nil.
//
- (NSString *)parseLineSeparator
{
NSString *matchedNewlines = nil;
[scanner
scanCharactersFromSet:[NSCharacterSet newlineCharacterSet]
intoString:&matchedNewlines];
return matchedNewlines;
}
//
// parseTextData
//
// Attempts to parse text data from the current scan location.
//
// returns a non-zero length string or nil.
//
- (NSString *)parseTextData
{
NSString *accumulatedData = [NSString string];
while (YES)
{
NSString *fragment;
if ([scanner scanUpToCharactersFromSet:endTextCharacterSet intoString:&fragment])
{
accumulatedData = [accumulatedData stringByAppendingString:fragment];
}
//
// If the separator is just a single character (common case) then
// we know we've reached the end of parseable text
//
if (separatorIsSingleChar)
{
break;
}
//
// Otherwise, we need to consider the case where the first character
// of the separator is matched but we don't have the full separator.
//
NSUInteger location = [scanner scanLocation];
NSString *firstCharOfSeparator;
if ([scanner scanString:[separator substringToIndex:1] intoString:&firstCharOfSeparator])
{
if ([scanner scanString:[separator substringFromIndex:1] intoString:NULL])
{
[scanner setScanLocation:location];
break;
}
//
// We have the first char of the separator but not the whole
// separator, so just append the char and continue
//
accumulatedData = [accumulatedData stringByAppendingString:firstCharOfSeparator];
continue;
}
else
{
break;
}
}
if ([accumulatedData length] > 0)
{
return accumulatedData;
}
return nil;
}
#end
Thanks for everyone's assistance. I found the answer on stackoverflow, a 2-3 years old forum (Adding Core Data to existing iPhone project)...
So the issue it seems is that when I first created the project I didn't request using core data and only did that later on. Following the post I posted above:
Add the following to supporting files/projectName-Prefix.pch
#import <CoreData/CoreData.h>
Once I did, the persistenceCoordinator error disappeared...
WOW, big lesson learned there...
-(void)populateDB {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
NSManagedObjectContext *context;
[....]
}
The Problem might be [self persistentStoreCoordinator]... Do you have a function called "persistentStoreCoordinator" ? If not, you have to write the function.
[EDIT]
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
#synchronized (self)
{
if (__persistentStoreCoordinator != nil)
return __persistentStoreCoordinator;
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"myProject" ofType:#"sqlite"];
NSString *storePath = [[[self applicationDocumentsDirectory] path] stringByAppendingPathComponent: #"myProject.sqlite"];
NSError *error;
if (![[NSFileManager defaultManager] fileExistsAtPath:storePath])
{
if ([[NSFileManager defaultManager] copyItemAtPath:defaultStorePath toPath:storePath error:&error])
NSLog(#"Copied starting data to %#", storePath);
else
NSLog(#"Error copying default DB to %# (%#)", storePath, error);
}
NSURL *storeURL = [NSURL fileURLWithPath:storePath];
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
}
Is this Code in another File ? Maybe you forgot to import the .h-file, where persistentStoreCoordinator is declared in.
When I call the DBRestClient to download a file to a given path, the API does not call the loading functions.
For example:
- (void) downloadFiles:(NSMutableArray *)files
{
NSLog(#"%#", files);
itemsToBeDownloaded = [[NSMutableArray alloc] initWithArray:files];
restClient = [self restClient];
for (NSString *string in files)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"/Dropbox%#", string]];
[restClient loadFile:string intoPath:filePath];
}
}
Printing files returns (
"/Blank.pdf"
)
Printing string returns /Blank.pdf
Printing filePath returns /var/mobile/Applications/0C506400-7142-41E2-9F3D-0965985CED9E/Documents/Dropbox/Blank.pdf
So the function is called and knows the files and their path's to download.
However, when I call [restClient loadFile:string intoPath:filePath];, nothing happens. I have the delegate methods in:
- (void) restClient:(DBRestClient *)client loadedFile:(NSString *)destPath
{
NSLog(#"Called!");
}
- (void) restClient:(DBRestClient *)client loadedFile:(NSString *)destPath contentType:(NSString *)contentType
{
NSLog(#"Called!");
}
- (void) restClient:(DBRestClient *)client loadedFile:(NSString *)destPath contentType:(NSString *)contentType metadata:(DBMetadata *)metadata
{
NSLog(#"%#", destPath);
NSLog(#"%#", contentType);
NSLog(#"%#", metadata);
}
- (void) restClient:(DBRestClient *)client loadFileFailedWithError:(NSError *)error
{
NSLog(#"Error downloading file: %#", error);
}
No Called! statement is produced. It seems the RestClient is not downloading the data.
Another note: restClient = [self restClient]; does return a valid DBRestClient, so I know it is valid. However, the call to load the file is not being called.
Is there a specific reason the call to loadFile: is not being made? I have it loading Metadata just fine.
EDIT: a call to loadMetadata: at string does NOT call the loadMetadata delegate method.
EDIT 2: code below lists how the file's array is claimed:
- (void) downloadFiles
{
NSMutableArray *filesToDownload = [[NSMutableArray alloc] init];
for (int i = 0; i < [[itemsToDownload allKeys] count]; ++i)
{
for (NSString *string in [itemsToDownload objectForKey:[[itemsToDownload allKeys] objectAtIndex:i]])
{
[filesToDownload addObject:[NSString stringWithFormat:#"%#%#", [[itemsToDownload allKeys] objectAtIndex:i], string]];
}
}
[dropboxController downloadFiles:filesToDownload];
}
So I figured out the entire problem. Turns out, you can't call the rest client methods from the background thread, it's got to be called on a main thread.
First I thought you are calling wrong path in dropbox, but event then you should have receive error message.
If your restClient returns the delegate method even with wrong path your - (void)restClient:(DBRestClient*)client loadFileFailedWithError:(NSError*)error should have been called.
I doubt your restClient is initiated correctly.
in your .h file:
#import <DropboxSDK/DropboxSDK.h>
#property (nonatomic, strong) DBRestClient *restClient;
in.your.m file:
#import <DropboxSDK/DropboxSDK.h>
#synthesize restClient = _restClient;
- (DBRestClient *)restClient {
if (!_restClient) {
_restClient =
[[DBRestClient alloc] initWithSession:[DBSession sharedSession]];
_restClient.delegate = self;
}
return _restClient;
}
then try to call your
[[self restClient] loadFile:#"string" intoPath: #"pathstring" ];