How to use hardcoded file path names with sandbox - macos

Ok, yes I know now that you can not use hardcoded paths with sandbox. Up to this point I have not delt with sandbox, so I never encountered it.
I have a Coredata App (Mac OSx) and I used the default save code and the default path location (user/...../applicationsupport/... This, of coarse, is not acceptable in the sandbox.
Without requiring the user to manually open the data file each time the program is launched, is there another way to deal with this?
I would appreciate any input/suggestions.
Thanks You

Sandbox doesn't mean there isn't any access to files and folders without user selection. As it said in App Sandbox in Depth article there's container directory you still having access to.
For taking a path to your Application Support-directory you should use the same code whenever you use Sandboxing or not.
+ (NSString *)executableName
{
NSString *executableName = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleExecutable"];
if(!executableName || executableName.length==0)
return nil;
return executableName;
}
- (NSString *)findOrCreateDirectory:(NSSearchPathDirectory)searchPathDirectory
inDomain:(NSSearchPathDomainMask)domainMask
appendPathComponent:(NSString *)appendComponent
error:(NSError **)errorOut
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPathDirectory,domainMask,YES);
if ([paths count]==0)
return nil;
NSString *resolvedPath = [paths objectAtIndex:0];
if (appendComponent)
resolvedPath = [resolvedPath stringByAppendingPathComponent:appendComponent];
NSError *error;
BOOL success = [self createDirectoryAtPath:resolvedPath withIntermediateDirectories:YES attributes:nil error:&error];
if (!success)
{
if (errorOut)
*errorOut = error;
return nil;
}
return resolvedPath;
}
- (NSString *)applicationSupportDirectory
{
NSError *error;
NSString *result = [self findOrCreateDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask
appendPathComponent:[self executableName] error:&error];
if (error)
return nil;
return result;
}

Related

non-persistence of object written to documentsDirectory - is

-- a question about how to make an object that is saved to the documents directory persist on the drive and be recoverable after the iDevice is rebooted.
Here's my problem. I make a data object with NSCoding and fill it with data. I write it to the documentsDirectory each time the data in the object are updated. I stop the app and start the app again, and my data object persists, with all of its data. But if I reboot the iPhone the code I wrote to recover and read the data object fails.
The code I wrote originally used only a NSString for the file path. It worked well under ios7 but it fails under ios8.
Reading up on things, I found this clue from the Apple documentation:
"Important: Although they are safe to use while your app is running, file reference URLs are not safe to store and reuse between launches of your app because a file’s ID may change if the system is rebooted. If you want to store the location of a file persistently between launches of your app, create a bookmark as described in Locating Files Using Bookmarks."
So I rewrote my ios7 file open and file close methods so they no longer use strings or urls but get their strings and urls from a bookmark that is saved using NSUserDefaults. Same problem: everything works fine so long as I do not power off the phone, but all is lost once I do. I am not able to solve this.
Here is my current series of steps. First I either determine (or if it already exists in NSUsrDefaults, I recover) the absolute path to the documentsDirectory, using a bookmark:
+ (NSString*) getGeoModelAbsolutePath
{
NSString *path;
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSURL *documentsDirectoryBookmarkURL;
NSData* documentsDirectoryBookmark = [userDefaults objectForKey:#"documentDirectoryBookmark"];
if(documentsDirectoryBookmark == nil)
{
documentsDirectoryBookmarkURL = [self getDocumentsDirectoryURL];
documentsDirectoryBookmark = [self bookmarkForURL:documentsDirectoryBookmarkURL];
}
documentsDirectoryBookmarkURL = [self urlForBookmark:documentsDirectoryBookmark];
path = documentsDirectoryBookmarkURL.path;
path = [path stringByAppendingString:#"/Model.mod"];
return path;
}
using methods modified from my ios7 code (which used only the getDocumentsDirectory method):
+ (NSString *)getDocumentsDirectory
{
NSURL *directory = [self getDocumentsDirectoryURL];
NSString * documentsDirectory = directory.path;
return documentsDirectory;
}
And
+ (NSURL *)getDocumentsDirectoryURL
{
NSURL *directory = [[[NSFileManager defaultManager]
URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask]
lastObject];
return directory;
}
And
+ (NSData*)bookmarkForURL:(NSURL*)url {
NSError* theError = nil;
NSData* bookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationSuitableForBookmarkFile
includingResourceValuesForKeys:nil
relativeToURL:nil
error:&theError];
if (theError || (bookmark == nil)) {
// Handle any errors.
return nil;
}
return bookmark;
}
So now I have a NSString path with the model filename that I can use to get to the GeoModel
- (GeoModel*) openGeoModel
{
GeoModel *geoModel;
NSString* documentsDirectoryGeoModel =[FileManager getGeoModelAbsolutePath];
if([FileManager fileExistsAtAbsolutePath:documentsDirectoryGeoModel])
{
NSData* data = [NSData dataWithContentsOfFile: documentsDirectoryGeoModel]; //]documentsDirectoryGeoModel];
geoModel = [NSKeyedUnarchiver unarchiveObjectWithData: data];
NSString *unarchivedGeoModelVersion = geoModel.geoModel_VersionID;
if(![unarchivedGeoModelVersion isEqual: currentGeoModelVersion])
{
[FileManager deleteFile:documentsDirectoryGeoModel];
geoModel = [GeoModel geoModelInit];
[Utilities setGeoProjectCounter:0];
}
}
else
{
geoModel = [GeoModel geoModelInit];
}
[FileManager saveGeoModel];
return geoModel;
}
Which I then can save to the documentsDirectory as follows:
+ (BOOL)saveGeoModel
{
NSError *error = nil;
NSString *path = [self getGeoModelAbsolutePath];
[NSKeyedArchiver archiveRootObject:appDelegate.currentGeoModel toFile:path];
NSData* encodedData = [NSKeyedArchiver archivedDataWithRootObject: appDelegate.currentGeoModel];
BOOL success = [encodedData writeToFile: path options:NSDataWritingAtomic error:&error];
return success;
}
Which is always successful -- but is persistent only if I do not turn off the device! I am not making any progress with this: Any help would be much appreciated!
Thanks in advance
Tim Redfield
There. I think it is answered -- unless someone else has a comment on how to improve the above listings, they DO work as they ought to!

Cant save image locally IOS8

i have this code working on IOS7, its check if an image exist in device and if not, it download locally.
Now on IOS8 doesnt save nothing, could someone help me?
//folder where save
NSString *ImagesPath = [[NSString alloc] initWithString:[[[[NSBundle mainBundle] resourcePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:#"Documents"]];
// check if image exist
NSString* foofile = [ImagesPath stringByAppendingPathComponent:nombreImagenLocal];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:foofile];
// check if image exist locally
if (!fileExists){
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:rutaCompletaLogo]];
//if not, i save it
if (data) {
// url where is saved
NSString *cachedImagePath = [ImagesPath stringByAppendingPathComponent:nombreImagenLocal];
if ([data writeToFile:cachedImagePath atomically:YES]) {
NSLog(#"Downloaded file saved to: %#", cachedImagePath);
}// end
The path to 'Documents' folder has changed from iOS8. Check the Apple tech note
Please, make sure you don't use hardcoded values. Use the methods provided by the API:
NSString *resourcePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
OR (as stated in the previous link)
// Returns the URL to the application's Documents directory.
- (NSURL *)applicationDocumentsDirectory{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
Hope this helps!

How to make an OSX app use a sqlite database in the bundle?

I have an app that is intended to ship with a pre-populated sqlite database. I will include that database on the bundle. How do I modify the delegate to use that database instead of generating a new one?
This is my first serious app on OSX.
The current methods on the delegate is like this right now:
// Returns the directory the application uses to store the Core Data store file. This code uses a directory named "com.addfone.LoteriaMac" in the user's Application Support directory.
- (NSURL *)applicationFilesDirectory
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *appSupportURL = [[fileManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
return [appSupportURL URLByAppendingPathComponent:#"com.myApp.BotMax"];
}
// Creates if necessary and returns the managed object model for the application.
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"BotMax" withExtension:#"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
// Returns the persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. (The directory for the store is created, if necessary.)
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator) {
return _persistentStoreCoordinator;
}
NSManagedObjectModel *mom = [self managedObjectModel];
if (!mom) {
NSLog(#"%#:%# No model to generate a store from", [self class], NSStringFromSelector(_cmd));
return nil;
}
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *applicationFilesDirectory = [self applicationFilesDirectory];
NSError *error = nil;
NSDictionary *properties = [applicationFilesDirectory resourceValuesForKeys:#[NSURLIsDirectoryKey] error:&error];
if (!properties) {
BOOL ok = NO;
if ([error code] == NSFileReadNoSuchFileError) {
ok = [fileManager createDirectoryAtPath:[applicationFilesDirectory path] withIntermediateDirectories:YES attributes:nil error:&error];
}
if (!ok) {
[[NSApplication sharedApplication] presentError:error];
return nil;
}
} else {
if (![properties[NSURLIsDirectoryKey] boolValue]) {
// Customize and localize this error.
NSString *failureDescription = [NSString stringWithFormat:#"Expected a folder to store application data, found a file (%#).", [applicationFilesDirectory path]];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setValue:failureDescription forKey:NSLocalizedDescriptionKey];
error = [NSError errorWithDomain:#"YOUR_ERROR_DOMAIN" code:101 userInfo:dict];
[[NSApplication sharedApplication] presentError:error];
return nil;
}
}
NSURL *url = [applicationFilesDirectory URLByAppendingPathComponent:#"BotMax.storedata"];
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
if (![coordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:nil error:&error]) {
[[NSApplication sharedApplication] presentError:error];
return nil;
}
_persistentStoreCoordinator = coordinator;
return _persistentStoreCoordinator;
}
The database file is named like BotMax.sqlite
thanks.
The simplest way to ship a pre-filled SQLite DB would be to copy the .sqlite file into the Application Support folder before the persistentStoreCoordinator is initialised.
The below code just copies the bundled DB. There are some things to consider before using that approach:
This simple approach only copies the initial DB once.
It does not consider merging or migrating existing stores (e.g when you ship an update with a changed model)
Core Data is using a new journal_mode since iOS 7 & OS X 10.9. This mode creates an additional file when saving the DB. (*.sqlite-wal). If the program that creates your pre-filled SQLite file was linked against OS X 10.9/iOS 7 or later SDKs, you'll also have to ship the .sqlite-wal file.
Alternatively, you can disable the WAL journal mode in the app that creates your pre-filled DB by passing #{ NSSQLitePragmasOption : #{ #"journal_mode" : #"DELETE" } }; as option dict to addPersistentStoreWithType
The following code is based on the standard Xcode template for non-document based Core Data apps:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator) {
return _persistentStoreCoordinator;
}
NSString* dbFilename = #"BotMax.sqlite";
NSURL* initialDBURL = [[[NSBundle mainBundle] resourceURL] URLByAppendingPathComponent:dbFilename];
NSURL* workingDBURL = [[self applicationFilesDirectory] URLByAppendingPathComponent:dbFilename];
NSFileManager* fileManager = [NSFileManager defaultManager];
NSURL* applicationFilesDirectory = [self applicationFilesDirectory];
NSError* error = nil;
if(![fileManager fileExistsAtPath:[workingDBURL path]])
{
BOOL result = [fileManager copyItemAtURL:initialDBURL toURL:workingDBURL error:&error];
if(!result)
{
[[NSApplication sharedApplication] presentError:error];
return nil;
}
}
...
}
objc.io issue #4 has a section about shipping pre-filled Core Data stores: http://www.objc.io/issue-4/importing-large-data-sets-into-core-data.html

A method for 'Save As...' in Core Data app

CD's been an enormous learning curve for me and there's still a bit for me to go, but any help on the following could enable me to lift the current weight on my shoulders!
I'm trying to write a method that implements a "Save As.." for the user in my CD app.
So far I've got:
[saveAsPanel beginSheetModalForWindow:window completionHandler:^(NSInteger userResult)
{
if (userResult == NSOKButton) {
NSPersistentStoreCoordinator *psc = [self persistentStoreCoordinator];
NSURL *oldURL = [self URLOfInternalStore]; //returns the current store's URL
NSURL *newURL = [saveAsPanel URL];
NSError *error = nil;
NSPersistentStore *oldStore = [psc persistentStoreForURL:oldURL];
NSPersistentStore *sqLiteStore = [psc migratePersistentStore:oldStore
toURL:newURL
options:nil
withType:NSXMLStoreType
error:&error];
}
}];
Unfortunately, I just get the error:
Object's persistent store is not reachable from this NSManagedObjectContext's coordinator.
Should I 'remove' and then 'addPersistentStore...' to update it to the new URL? The doc's seem to suggest that all will be handled with in the 'migrate' method.
Thanks in advance!
Edit:
Ok, well, I've come up with my own 'dirty' method. I can imagine that this isn't an approved way of doing things, but there's no error thrown up and the app works as expected at all times (not often I can say that, either!):
-(IBAction)saveAsAction:(id)sender
{
NSSavePanel *saveAsPanel = [NSSavePanel savePanel];
[saveAsPanel beginSheetModalForWindow:window completionHandler:^(NSInteger userResult)
{
if (userResult == NSOKButton) {
[self saveAction:#"saveAsCalling"];
NSURL *newURL = [saveAsPanel URL];
NSError *error = nil;
[[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:internalStore] toURL:newURL error:&error];
//internalStore is a hard-wired NSString that holds the path to the bundle's database
}
}];
}
-(IBAction)loadAction:(id)sender
{
NSOpenPanel *loadPanel = [NSOpenPanel openPanel];
[loadPanel beginSheetModalForWindow:window completionHandler:^(NSInteger userResult)
{
if (userResult == NSOKButton) {
[self saveAction:#"loadCalling"];
NSURL *newURL = [loadPanel URL];
NSURL *oldURL = [NSURL fileURLWithPath:internalStore];
NSError *error = nil;
NSPersistentStoreCoordinator *psc = [SELF_MOC persistentStoreCoordinator];
[psc removePersistentStore:[[self persistentStoreCoordinator] persistentStoreForURL:oldURL] error:&error];
[psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:newURL options:nil error:&error];
[[NSFileManager defaultManager] removeItemAtURL:oldURL error:&error];
[[NSFileManager defaultManager] copyItemAtURL:newURL toURL:oldURL error:&error];
[psc removePersistentStore:[[self persistentStoreCoordinator] persistentStoreForURL:newURL] error:&error];
[psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:oldURL options:nil error:&error];
}
}];
}
The basic reasoning is this: to do a 'SaveAs...' I simply copy out the SQLLite store file in the mainBundle to wherever the user selects and rename it to what they want - as per TechZen's suggestion.
To do a 'Load' then I first removePersistentStore from the bundle's file, add the one that the user's just chosen. Delete the bundle store (which in theory isn't now being used) and then copy the user's choice back into the bundle. Finally, the two operations of remove and addPersistentStore are performed to point the app back to it's bundle's file which is now the user's choice.
Hope that makes sense. If anyone has any thoughts on just how unprofessional a methodology this is then please - be kind as I'm fairly new - let me know. I can't find anything that is more elegant.
I know Apple don't like you using removePersistentStore and addPersistentStore but, as I say no errors are reported (in my actual code I scattered NSLog lines throughout to report what error is holding).
You only use a SaveAs... in a document based app. If you use Core Data as your model, you need to use NSPersistentDocument to save your data. It provide the SaveAs... functionality you seek.
Straight Core Data is used for more database-like apps in which the entire app operates from one data set (more or less.)

How to redirect the nslog output to file instead of console

I have cocoa application running on OS X. I have used NSLog for debugging purpose. Now I want to redirect the log statements to file instead of console.
I have used this method but it results logging in Console as well as in file.
- (BOOL)redirectNSLog
{
// Create log file
[#"" writeToFile:#"/NSLog.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil];
id fileHandle = [NSFileHandle fileHandleForWritingAtPath:#"/NSLog.txt"];
if (!fileHandle) return NSLog(#"Opening log failed"), NO;
[fileHandle retain];
// Redirect stderr
int err = dup2([fileHandle fileDescriptor], STDERR_FILENO);
if (!err) return NSLog(#"Couldn't redirect stderr"), NO;
return YES;
}
Is it possible to not have log statement in console but only in file ??
Step 1: Include following function in AppDelegate:
- (void) redirectConsoleLogToDocumentFolder
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *logPath = [documentsDirectory stringByAppendingPathComponent:#"console.log"];
freopen([logPath fileSystemRepresentation],"a+",stderr);
}
Step 2: Call this function at the start of function applicationDidFinishLaunchingWithOptions...
Thats it, Every NSLog() will now get redirected to this console.log file, which you can find in the documents directory.
Recently i have faced similar requirement and this is how i have done it.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
[self redirectConsoleLogToDocumentFolder];
return YES;
}
- (void) redirectConsoleLogToDocumentFolder
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *logPath = [documentsDirectory stringByAppendingPathComponent:#"console.txt"];
freopen([logPath fileSystemRepresentation],"a+",stderr);
}
And Now if you want to this console to user
-(void)displayLog{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths firstObject];
NSString *logPath = [documentsDirectory stringByAppendingPathComponent:#"console.txt"];
NSError *err = nil;
NSString *fileContents = [NSString stringWithContentsOfFile:logPath
encoding:NSUTF8StringEncoding
error:&err];
if (fileContents == nil) {
NSLog(#"Error reading %#: %#", logPath, err);
} else {
self.textView.text = fileContents;
}
}
You may be interested in CocoaLumberjack. It is a very flexible logging framework for both Mac OS X and iOS. One logging statement can be sent not only to the console but to a file simultaneously. Plus it is actually faster then NSLog. I use it in a project that has common code for both OS X and iOS.
NSLog is made to log into the console. You need to define your own function MyLog or whatever, and replace all occurrences of NSLog into MyLog.

Resources