I have UIDocument in iCloud ubiquitous container and I need to append data to file while saving document. I override readFromURL:: and writeContents::::: method according UIDocument documentation:
-(BOOL) writeContents:(id)contents toURL:(NSURL*)url forSaveOperation:(UIDocumentSaveOperation)saveOperation originalContentsURL:(NSURL*)originalContentsURL error:(NSError *__autoreleasing *)outError
{
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error = nil;
[coordinator coordinateWritingItemAtURL:url options:0 error:&error byAccessor:^(NSURL *newURL) {
NSData* data = contents; //correct, non-empty NSData
///[data writeToFile:newURL :] works, but overwrite original file
NSOutputStream* stream =[[NSOutputStream alloc] initWithURL:newURL append:YES];
if (stream)
{
NSInteger written = [stream write:data.bytes maxLength:data.length];
if (written != data.length)
{
//failed here, written == -1
NSLog(#"Write data to UIDocument failed: %#, error: %#", newURL, stream.streamError);
}
}
else
{
NSLog(#"Write data to iCloudDocument failed: %#", newURL);
}
}];
if (error)
{
NSLog(#"Coordinated write failed %#, error: %#", url, error);
*outError = error;
}
return error == nil;
}
Accessor block has different newURL, for example:
url: file:///private/var/mobile/Library/Mobile%20Documents/XXXXXXX~com~test~test/test.doc
newURL: file:///private/var/mobile/Applications/5631D484-7661-4E9E-A342-B25297FC0E18/tmp/(A%20Document%20Being%20Saved%20By%20test%20)/test.doc.
[stream write::] failed, because newURL file dosn't exists and I can't append data, only create file with all document's content.
Document editing code:
NSURL* url = [self.containerURL URLByAppendingPathComponent:kCloudDocumentName];
MyDocument* document = [[MyDocument alloc] initWithFileURL:url];
[document openWithCompletionHandler:^(BOOL success) {
if (success)
{
//update some document data
[self updateData:document completion:nil];
[document closeWithCompletionHandler:^(BOOL success) {
//failed here!
}];
}
}];
MyDocument exists in ubiquitous container at url and document has Normal state.
How I can do incremental writing in this case? Whats wrong?
After changing data in your UIDocumnet you can use:
-[UIDocumnet saveToURL:UIDocumnet.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:nil];
Not sure, it's right or not but it works in my app. If it's wrong, correct me please somebody.
You may need to overwrite this method in your subclass of UIDocumnet:
- (id) contentsForType:(NSString *)typeName error:(NSError **)outError
Related
I'm having problems with this and read now several tutorials and also examples in one book, which however suggested more a deep copy which is too complicated for me. This iCloud stuff is anyway confusing. Did I get it right with this:
User enabling iCloud in app:
- Try to copy local store to icloud
- Deleting local store
User disabling iCloud in app:
- Try to copy store to local
- Remove ubiquitous content
- Remove local copy of icloud
Do I miss anything?
Extra questions: How can I "block" the app during copying and where should I place this "blocking" code?
This would be my code:
- (bool)moveStoreToICloud {
NSLog(<#NSString * _Nonnull format, ...#>)(#" called");
return [self moveStoreFileToICloud:self.store delete:YES backup:YES];
}
- (bool)moveStoreFileToICloud:(NSURL*)fileURL delete:(bool)shouldDelete backup:(bool)shouldBackup {
NSLog(#" called");
// Always make a backup of the local store before migrating to iCloud
if (shouldBackup)
[self backupLocalStore];
NSPersistentStoreCoordinator *migrationPSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.model];
// Open the existing local store using the original options
NSDictionary *options =
#{
NSMigratePersistentStoresAutomaticallyOption:#YES
,NSInferMappingModelAutomaticallyOption:#YES
,NSPersistentStoreUbiquitousContentNameKey:ubiquitousContentNameKey
//,NSPersistentStoreUbiquitousContentURLKey:#"ChangeLogs" // Optional since iOS7
};
id sourceStore = [migrationPSC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:fileURL options:options error:nil];
if (!sourceStore) {
NSLog(#" failed to add old store");
return FALSE;
} else {
NSLog(#" Successfully added store to migrate");
bool moveSuccess = NO;
NSError *error;
NSLog(#" About to migrate the store...");
// Now migrate the store using the iCloud options
id migrationSuccess = [migrationPSC migratePersistentStore:sourceStore toURL:[_iCloudStore URL] options:options withType:NSSQLiteStoreType error:&error];
if (migrationSuccess) {
moveSuccess = YES;
NSLog(#"store successfully migrated");
// Now delete the local file
if (shouldDelete) {
NSLog(#" deleting local store");
[self destroyAllLocalDataForThisApplication];
} else {
NSLog(#" not deleting local store");
}
[self resetCoreData];
[self setupCoreData];
[[NSNotificationCenter defaultCenter] postNotificationName:#"SomethingChanged"
object:nil];
return TRUE;
}
else {
NSLog(#"Failed to migrate store: %#, %#", error, error.userInfo);
return FALSE;
}
}
return FALSE;
}
/*! Moves an iCloud store to local by migrating the iCloud store to a new local store and then removes the store from iCloud.
Note that even if it fails to remove the iCloud files it deletes the local copy. User may need to clean up orphaned iCloud files using a Mac!
#return Returns YES of file was migrated or NO if not.
*/
- (bool)moveStoreToLocal {
NSLog(#"moveStoreToLocal called");
// Lets use the existing PSC
NSPersistentStoreCoordinator *migrationPSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.model];
// Open the store
NSDictionary *options =
#{
NSMigratePersistentStoresAutomaticallyOption:#YES
,NSInferMappingModelAutomaticallyOption:#YES
,NSPersistentStoreUbiquitousContentNameKey:ubiquitousContentNameKey
//,NSPersistentStoreUbiquitousContentURLKey:#"ChangeLogs" // Optional since iOS7
};
id sourceStore = [migrationPSC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[_iCloudStore URL] options:options error:nil];
if (!sourceStore) {
NSLog(#" failed to add old store");
return FALSE;
} else {
NSLog(#" Successfully added store to migrate");
bool moveSuccess = NO;
NSError *error;
NSLog(#" About to migrate the store...");
id migrationSuccess = [migrationPSC migratePersistentStore:sourceStore toURL:[_store URL] options:options withType:NSSQLiteStoreType error:&error];
if (migrationSuccess) {
moveSuccess = YES;
NSLog(#"store successfully migrated");
// Now delete the local file
[self destroyAlliCloudDataForThisApplication];
[self resetCoreData];
[self setupCoreData];
[[NSNotificationCenter defaultCenter] postNotificationName:#"SomethingChanged"
object:nil];
return TRUE;
}
else {
NSLog(#"Failed to migrate store: %#, %#", error, error.userInfo);
return FALSE;
}
}
return TRUE;
}
#pragma mark - ICLOUD RESET
- (void)destroyAllLocalDataForThisApplication {
if (![[NSFileManager defaultManager] fileExistsAtPath:[[_store URL] path]]) {
NSLog(#"Skipped destroying content, _store.URL is %#",[[_store URL] path]);
return;
}
NSLog(#"\n\n\n\n\n **** Destroying ALL local content for this application, this could take a while... **** \n\n\n\n\n\n");
[self removeAllStoresFromCoordinator:_coordinator];
[self removeAllStoresFromCoordinator:_seedCoordinator];
_coordinator = nil;
_seedCoordinator = nil;
NSError *error;
if([[NSFileManager defaultManager] removeItemAtURL:_storeURL error:&error]){
NSLog(#"Local store successfully removed");
} else {
NSLog(#"\n\n **** FAILED to destroy this application's iCloud content at URL (%#) **** \n%#\n",[_store URL],error);
}
}
- (void)destroyAlliCloudDataForThisApplication {
if (![[NSFileManager defaultManager] fileExistsAtPath:[[_iCloudStore URL] path]]) {
NSLog(#"Skipped destroying iCloud content, _iCloudStore.URL is %#",[[_iCloudStore URL] path]);
return;
}
NSLog(#"\n\n\n\n\n **** Destroying ALL iCloud content for this application, this could take a while... **** \n\n\n\n\n\n");
[self removeAllStoresFromCoordinator:_coordinator];
[self removeAllStoresFromCoordinator:_seedCoordinator];
_coordinator = nil;
_seedCoordinator = nil;
NSDictionary *options =
#{
NSPersistentStoreUbiquitousContentNameKey:ubiquitousContentNameKey
//,NSPersistentStoreUbiquitousContentURLKey:#"ChangeLogs" // Optional since iOS7
};
NSError *error;
if ([NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:[_iCloudStore URL]
options:options
error:&error]) {
NSLog(#"\n\n\n\n\n");
NSLog(#"* This application's iCloud content has been destroyed *");
NSLog(#"* On ALL devices, please delete any reference to this application in *");
NSLog(#"* Settings > iCloud > Storage & Backup > Manage Storage > Show All *");
NSLog(#"\n\n\n\n\n");
[[NSFileManager defaultManager] removeItemAtURL:[_iCloudStore URL] error:&error]
} else {
NSLog(#"\n\n **** FAILED to destroy this application's iCloud content at URL (%#) **** \n%#\n",[_iCloudStore URL],error);
}
}
If this is a new app, rethink your approach. Use CloudKit, or Azure, or Firebase.
iCloud Core Data never worked reliably. Thankfully Apple now realizes this. NSPersistentStoreUbiquitousContentNameKey is marked as deprecated as of iOS 10/macOS 12.12, as are the other iCloud Core Data symbols.
I tried to download file from google drive API since 3 day without success. I used this https://developers.google.com/drive/ios/devguide/files#reading_files.
But I can't understand what I need to put in *drive and *file?
I tried :
GTLDriveFile *file = #"fileText.txt"; (or I tried the url of my file on google drive...) The guide don't explain... And I didn't find real example.
GTLServiceDrive *drive = ...;
GTLDriveFile *file = ...;
NSString *url = [NSString stringWithFormat:#"https://www.googleapis.com/drive/v3/files/%#?alt=media",
file.identifier]
GTMSessionFetcher *fetcher = [drive.fetcherService fetcherWithURLString:url];
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
if (error == nil) {
NSLog(#"Retrieved file content");
// Do something with data
} else {
NSLog(#"An error occurred: %#", error);
}
}];
So I had search other code like but no one explain what I need to put in drive and file:
how to download file from google drive using objective c? (just this say it's url)
Google drive api download file for iOS
IOS: How to Download Google Docs files using ios google drive sdk API?
SOLUTION :
I had a problem of authorization with my scope, solved by total access to drive. I changed the scope (in quickstart code, look : "- (GTMOAuth2ViewControllerTouch *)createAuthController...")
-->NSArray *scopes = [NSArray arrayWithObjects:kGTLAuthScopeDrive, nil];
For download (inspired by quickstart example) :
// self.service is my GTLServiceDrive
// When the view appears, ensure that the Drive API service is authorized, and perform API calls.
- (void)viewDidAppear:(BOOL)animated {
if (!self.service.authorizer.canAuthorize) {
// Not yet authorized, request authorization by pushing the login UI onto the UI stack.
[self presentViewController:[self createAuthController] animated:YES completion:nil];
} else {
NSString *urltest = [NSString stringWithFormat:#"https://www.googleapis.com/drive/v3/files/%#?alt=media", identifier_file]; //the ID of my file in a string identifier_file
GTMSessionFetcher *fetcher = [self.service.fetcherService fetcherWithURLString:urltest]; // the request
// receive response and play it in web view:
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *errorrr) {
if (errorrr == nil) {
NSLog(#"Retrieved file content");
[webView_screen loadData:data MIMEType:#"application/pdf" textEncodingName:#"UTF-8" baseURL:nil]; //my file is a pdf
[webView_screen reload];
} else {
NSLog(#"An error occurred: %#", errorrr);
}
}];
}
}
If you want to save on the phone, you can look the Bala's code.
First fetch the file from Drive
driveFiles = [[NSMutableArray alloc] init];
for (GTLDriveFile *file in files.items) {
if ([file.mimeType isEqualToString:#"application/vnd.google-apps.folder"]) {
} else {
NSString *fileExtension = file.fileExtension;
if (fileExtension) {
if ([fileExtension isEqualToString:#"pdf"]) {
[driveFiles addObject:file];
}
}
}
}
And GTLDriveFile pass the object that you have in the array
GTLDriveFile *file=[driveFiles objectAtIndex:indexPath.row];
This is the code for download the file
NSString *link;
if (file.webContentLink) {
link = file.webContentLink;
} else if (file.embedLink) {
link = file.embedLink;
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"ERROR" message:#"File has no downloadable link" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[alert show];
}
if (link) {
NSString *downloadUrl = file.downloadUrl;
GTMHTTPFetcher *fetcher = [self.driveService.fetcherService fetcherWithURLString:downloadUrl];
//async call to download the file data
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
if (error == nil) {
if (data) {
NSString *dirPath = [self directoryPathForSavingFile];
NSString *filePath = [dirPath stringByAppendingPathComponent:file.title];
[self saveFileJSONData:data forFileName:filePath withCompletionHandler:^(BOOL successStatus) {
// Adding skip attribute to avoid data sinking in iCloud
BOOL path = [[NSFileManager defaultManager] fileExistsAtPath:filePath];
if (path) {
NSLog(#"filePath %#", filePath);
}
}];
}
} else {
NSLog(#"An error occurred: %#", error);
}
}];
}
Code for Directory path for save the file
- (NSString *)directoryPathForSavingFile:(NSString *)directoryName {
NSString *applicationDirectory = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
applicationDirectory = [applicationDirectory stringByAppendingPathComponent:directoryName];
return applicationDirectory;
}
I have an app that I'm making and it uses a UIManagedDocument to handle core data and iCloud. At the start of my app I Open the file and initialize the UIManagedDocument with the iCloud directory.
This works fine the first time I load the program and subsequent times but It is my understanding that because I use iCloud I should be able to delete the app and reload it and have it reload the data from the cloud.
When I delete the app and reload it its able to get the url and initialize the UIManagedDocument but once it gets to [document openWithCompletionHandler:^(BOOL success){}] the thread that was supposed to access the document comes back with EXC_BAD_ACCESS.
This is the code I use to setup the document with core data and the iCloud url is good. This is my first experience with iCloud. What am I doing wrong?
- (void)setupDocument
{
NSURL *url = [self iCloudDocumentsDirectory];
url = [url URLByAppendingPathComponent:#"ZJournalData"];
UIManagedDocument *document = [[ZManagedDocument alloc] initWithFileURL:url];
NSLog(#"%#", document);
[document.persistentStoreOptions setValue:[document.fileURL lastPathComponent] forKey:NSPersistentStoreUbiquitousContentNameKey];
[document.persistentStoreOptions setValue:[[self iCloudDirectory] URLByAppendingPathComponent:#"CoreDataLogs"] forKey:NSPersistentStoreUbiquitousContentURLKey];
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
[document saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
self.managedObjectContext = document.managedObjectContext;
}
}];
} else if (document.documentState == UIDocumentStateClosed) {
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
self.managedObjectContext = document.managedObjectContext;
}
}];
} else {
NSLog(#"%#", document);
self.managedObjectContext = document.managedObjectContext;
}
}
The problem
What I have so far is code that creates a new local file and deletes the iCloud file.
Is it possible to rename an iCloud document, so that it stays in iCloud?
GarageBand can do it. It's possible to rename an iCloud song. After the rename is done, the song is still in iCloud. However GarageBand is an Apple app, so it may use private apis.
My current code:
- (void)moveFrom:(NSURL*)sourceURL
moveTo:(NSString*)destinationName
completion:(void (^)())completion
{
MyDocument *document = [[MyDocument alloc] initWithFileURL:sourceURL];
[document openWithCompletionHandler:^(BOOL success)
{
NSURL *fileURL = [self.localRoot URLByAppendingPathComponent:destinationName];
DLog(#"Create %#", fileURL);
[document saveToURL:fileURL
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success)
{
NSLog(#"Saved %#", fileURL);
[document closeWithCompletionHandler:^(BOOL success) {
// Delete the old document from a secondary thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^()
{
NSFileCoordinator* fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[fileCoordinator coordinateWritingItemAtURL:sourceURL
options:NSFileCoordinatorWritingForDeleting
error:nil
byAccessor:^(NSURL* writingURL) {
NSFileManager* fileManager = [[NSFileManager alloc] init];
[fileManager removeItemAtURL:writingURL error:nil];
DLog(#"Deleted %#", sourceURL);
completion();
}];
});
}];
}];
}];
}
Update: Still no luck
I have found out that -setUbiquitous:itemAtURL:destinationURL:error: cannot be used for renaming documents.
If I invoke [setUbiquitous:NO itemAtURL:oldLocalURL destinationURL:newLocalURL error:&error] on an already local file, then:
Error Domain=NSCocoaErrorDomain Code=512 "The operation couldn’t be
completed. (Cocoa error 512.)" UserInfo=0x1fdf6730
{NSURL=file://localhost/var/mobile/Applications/4BABA000-B100-49FC-B928-B0F403FC75FF/Documents/LocalDrawing.td2/,
NSUnderlyingError=0x20940e80 "The operation couldn’t be completed.
(LibrarianErrorDomain error 2 - Cannot disable syncing on a unsynced
item.)"}
If I invoke [setUbiquitous:YES itemAtURL:oldCloudURL destinationURL:newCloudURL error:&error] on an already cloud file, then:
Error Domain=NSCocoaErrorDomain Code=512 "The operation couldn’t be
completed. (Cocoa error 512.)" UserInfo=0x208e9820
{NSURL=file://localhost/var/mobile/Library/Mobile%20Documents/22DR89XVRF~com~opcoders~triangle-draw/Documents/CloudDrawing.td2/,
NSUnderlyingError=0x208d45b0 "The operation couldn’t be completed.
(LibrarianErrorDomain error 2 - Cannot enable syncing on a synced
item.)"}
Thus -setUbiquitous:itemAtURL:destinationURL:error: cannot be used for renaming documents.
I have finally solved it. This is my code so far:
- (void)_moveURL:(NSURL*)sourceURL
destURL:(NSURL*)destinationURL
success:(void (^)())successBlock
failure:(void (^)(NSError *))failureBlock
{
NSParameterAssert(sourceURL);
NSParameterAssert(destinationURL);
NSParameterAssert(successBlock);
NSParameterAssert(failureBlock);
// Do the actual renaming
__block NSError *moveError = nil;
__block BOOL moveSuccess = NO;
void (^accessor)(NSURL*, NSURL*) = ^(NSURL *newURL1, NSURL *newURL2) {
NSFileManager *fileManager = [[NSFileManager alloc] init];
moveSuccess = [fileManager moveItemAtURL:sourceURL toURL:destinationURL error:&moveError];
};
// Coordinate renaming
NSError *coordinatorError = nil;
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[coordinator coordinateWritingItemAtURL:sourceURL
options:NSFileCoordinatorWritingForMoving
writingItemAtURL:destinationURL
options:NSFileCoordinatorWritingForReplacing
error:&coordinatorError
byAccessor:accessor];
if (moveSuccess) {
successBlock();
return;
}
if (moveError) {
failureBlock(moveError);
return;
}
if (coordinatorError) {
failureBlock(coordinatorError);
return;
}
NSAssert(NO, #"should not happen");
}
There is an easier way to rename item at any given URL, including iCloud URLs.
url.setResourceValue(newName, forKey: NSURLNameKey)
I am using this in my cocoa app's production code.
Edit:
In Swift 5, this looks a little different, but still works well:
var resourceValues = URLResourceValues()
resourceValues.name = newName
try previousFileURL.setResourceValues(resourceValues)
I have an app that is using MagicalRecord for its Core Data handling and this works nice. However I have different users that can login in the app and when another user logs in, the core data database must be emptied so that the different user can have his own data. The database can be emptied completely as the data is also stored on a webservice and therefore can always be synced again after logging in again the first user.
So far I cannot seem to find a helper method (that works) for this purpose. I have tried
[MagicalRecord cleanUp];
whenever the user is logging out, but this does not do the trick.
This is how I did it. It is essential to have this line: [MagicalRecord cleanup]. Without it, [self setupDB] won't work.
UPDATE: Deletes the -wal and -shm files. #thattyson pointed out an issue in iOS 9. Also, see the answer of #onmyway133.
- (void)setupDB
{
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:[self dbStore]];
}
- (NSString *)dbStore
{
NSString *bundleID = (NSString *)[[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleIdentifierKey];
return [NSString stringWithFormat:#"%#.sqlite", bundleID];
}
- (void)cleanAndResetupDB
{
NSString *dbStore = [self dbStore];
NSError *error1 = nil;
NSError *error2 = nil;
NSError *error3 = nil;
NSURL *storeURL = [NSPersistentStore MR_urlForStoreName:dbStore];
NSURL *walURL = [[storeURL URLByDeletingPathExtension] URLByAppendingPathExtension:#"sqlite-wal"];
NSURL *shmURL = [[storeURL URLByDeletingPathExtension] URLByAppendingPathExtension:#"sqlite-shm"];
[MagicalRecord cleanUp];
if([[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error1] && [[NSFileManager defaultManager] removeItemAtURL:walURL error:&error2] && [[NSFileManager defaultManager] removeItemAtURL:shmURL error:&error3]){
[self setupDB];
}
else{
NSLog(#"An error has occurred while deleting %#", dbStore);
NSLog(#"Error1 description: %#", error1.description);
NSLog(#"Error2 description: %#", error2.description);
NSLog(#"Error3 description: %#", error3.description);
}
}
Here's the Swift version:
func setupDB() {
MagicalRecord.setupCoreDataStackWithAutoMigratingSqliteStoreNamed(self.dbStore())
}
func dbStore() -> String {
return "\(self.bundleID()).sqlite"
}
func bundleID() -> String {
return NSBundle.mainBundle().bundleIdentifier!
}
func cleanAndResetupDB() {
let dbStore = self.dbStore()
let url = NSPersistentStore.MR_urlForStoreName(dbStore)
let walURL = url.URLByDeletingPathExtension?.URLByAppendingPathExtension("sqlite-wal")
let shmURL = url.URLByDeletingPathExtension?.URLByAppendingPathExtension("sqlite-shm")
var removeError: NSError?
MagicalRecord.cleanUp()
//Swift 1
//let deleteSuccess = NSFileManager.defaultManager().removeItemAtURL(url, error: &removeError)
//Swift 2
let deleteSuccess: Bool
do {
try NSFileManager.defaultManager().removeItemAtURL(url)
try NSFileManager.defaultManager().removeItemAtURL(walURL!)
try NSFileManager.defaultManager().removeItemAtURL(shmURL!)
deleteSuccess = true
} catch let error as NSError {
removeError = error
deleteSuccess = false
}
if deleteSuccess {
self.setupDB()
} else {
println("An error has occured while deleting \(dbStore)")
println("Error description: \(removeError?.description)")
}
}
To expand on #yoninja 's answer, this will make reset CoreData stack explicitly, plus dealing with wal and shm files
- (void)setupDB
{
[MagicalRecord setDefaultModelNamed:#"Model.momd"];
[MagicalRecord setupCoreDataStack];
}
- (void)cleanAndResetupDB
{
[MagicalRecord cleanUp];
NSString *dbStore = [MagicalRecord defaultStoreName];
NSURL *storeURL = [NSPersistentStore MR_urlForStoreName:dbStore];
NSURL *walURL = [[storeURL URLByDeletingPathExtension] URLByAppendingPathExtension:#"sqlite-wal"];
NSURL *shmURL = [[storeURL URLByDeletingPathExtension] URLByAppendingPathExtension:#"sqlite-shm"];
NSError *error = nil;
BOOL result = YES;
for (NSURL *url in #[storeURL, walURL, shmURL]) {
if ([[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
result = [[NSFileManager defaultManager] removeItemAtURL:url error:&error];
}
}
if (result) {
[self setupDB];
} else {
NSLog(#"An error has occurred while deleting %# error %#", dbStore, error);
}
}
MagicalRecord does not provide this functionality for you. The cleanUp method is provided for you to reinitialize your CoreData stack in memory and cleaning up any contexts, queues and other related objects. However, it is not that difficult to do yourself given that MagicalRecord does provide a handy method to get the path for your library.
Check out the -[NSPersistentStore MR_urlForStoreName:] method. This will give you the file url for your store. You can then delete it with an NSFileManager instance. Be careful to do this before you set up the Core Data stack or you'll crash when you save because you'd have yanked out the store from under a properly initialized stack.
The following will completely delete the MagicalRecord CoreData sqlite files, as well as the -wal and -shm files. MagicalRecord puts them all in the Library folder; this will simply remove all files from the folder. This will not work if you have other data you need to persist in the Library folder, I did not:
- (void)resetCoreDataDB
{
[MagicalRecord cleanUp];
[self deleteFilesInLibrary];
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:#"YourDBName.sqlite"];
}
- (void)deleteFilesInLibraryDirectory
{
NSString* folderPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSError *error = nil;
for (NSString *file in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:folderPath error:&error])
{
[[NSFileManager defaultManager] removeItemAtPath:[folderPath stringByAppendingPathComponent:file] error:&error];
if(error)
{
NSLog(#"Delete error: %#", error.description);
}
}
}
If you are using the iOS Simulator and deleted the database file, you may probably notice that the data is still there. However, if tested on an actual device (which should be), the file is deleted and the context is reset as should be.
[MagicalRecord cleanUp];
// delete database file
NSError *error;
NSURL *fileURL = [NSPersistentStore MR_urlForStoreName:#"db.sqlite"];
[[NSFileManager defaultManager] removeItemAtURL:fileURL error:&error];
if(error) {
// Hanldle error
}
// reset setup.
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:#"db.sqlite"];
A bit rewritten answer of #yoninja for Swift 4:
private var dbStore : String? {
get {
if let bundleId = Bundle.main.bundleIdentifier {
return bundleId + ".sqlite"
}
return MagicalRecord.defaultStoreName()
}
}
func setupCoreDataStack() {
MagicalRecord.setupCoreDataStack(withAutoMigratingSqliteStoreNamed: self.dbStore)
}
func cleanUp() {
MagicalRecord.cleanUp()
var removeError: NSError?
let deleteSuccess: Bool
do {
guard let url = NSPersistentStore.mr_url(forStoreName: self.dbStore) else {
return
}
let walUrl = url.deletingPathExtension().appendingPathExtension("sqlite-wal")
let shmUrl = url.deletingPathExtension().appendingPathExtension("sqlite-shm")
try FileManager.default.removeItem(at: url)
try FileManager.default.removeItem(at: walUrl)
try FileManager.default.removeItem(at: shmUrl)
deleteSuccess = true
} catch let error as NSError {
removeError = error
deleteSuccess = false
}
if deleteSuccess {
self.setupCoreDataStack()
} else {
print("An error has occured while deleting \(self.dbStore)")
print("Error description: \(removeError.debugDescription)")
}
}