my code is this. And the value of ret is always NO, I think the write path is not allowed.
But where I can store my information on MAC OS X for my app? Can you help me to find the right path to store my app's setting? Thank you very much~ :)
NSString* writePath = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:#"Brain.plist"];
NSLog(#"%#",writePath);
NSMutableDictionary* dictForRet = [[NSMutableDictionary alloc]init];
NSNumber* applicationNumber = [[NSNumber alloc]initWithInt:0];
NSMutableDictionary* root = [[NSMutableDictionary alloc]init];
NSArray* propertyArray = [[NSArray alloc]initWithObjects:kPropertyArrayApplicationPath,kPropertyArrayApplicationCS, nil];
NSMutableDictionary* brain = [[NSMutableDictionary alloc]init];
[root setObject:propertyArray forKey:kPropertyArrayName];
[dictForRet setObject:applicationNumber forKey:kPropertyKeyApplicationNumber];
[dictForRet setObject:root forKey:kPropertyArrayDictName];
[dictForRet setObject:brain forKey:kPropertyDictBrainName];
NSLog(#"%#",dictForRet);
ret = [dictForRet writeToFile:writePath atomically:YES];
NSLog(#"%d",ret);
The resource path refers to the Resources/ directory in your app bundle. You do not want to write to this path, which is usually not allowed (especially in case of a sandboxed app).
To get paths to standard locations, like the Application Support directory use NSSearchPathForDirectoriesInDomains.
You're probably looking for something like:
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
This will return an (array of size 1) Application Support URL, automatically picking the right path if your app is sandboxed.
You can't write to the current app's bundle.
Instead, you might stuff it in your sandbox, in ~/Library/Application Support/YOUR_APP/, or in a user-specified location.
Related
The App Sandbox design guide says:
The related items feature of App Sandbox lets your app access files
that have the same name as a user-chosen file, but a different
extension. This feature consists of two parts: a list of related
extensions in the application’s Info.plist file and code to tell the
sandbox what you’re doing.
My Info.plist defines a document type for .pnd files (the user-chosen file), as well as a document type for .bak files. The entry for the .bak files has, among other properties, the property NSIsRelatedItemType = YES.
I am trying to use Related Items to move an existing file to a backup file (change .pnd suffix to .bak suffix) when the user writes a new version of the .pnd file. The application is sandboxed. I am not proficient with sandboxing.
I am using PasteurOrgManager as the NSFilePresenter class for both the original and backup files:
#interface PasteurOrgData : NSObject <NSFilePresenter>
. . . .
#property (readonly, copy) NSURL *primaryPresentedItemURL;
#property (readonly, copy) NSURL *presentedItemURL;
#property (readwrite) NSOperationQueue *presentedItemOperationQueue;
#property (readwrite) NSFileCoordinator *fileCoordinator;
. . . .
- (void) doBackupOf: (NSString*) path;
. . . .
#end
The doBackupOf: method is as follows. Notice that it also sets the NSFilePresenter properties:
- (void) doBackupOf: (NSString*) path
{
NSError *error = nil;
NSString *appSuffix = #".pnd";
NSURL *const pathAsURL = [NSURL URLWithString: [NSString stringWithFormat: #"file://%#", path]];
NSString *const baseName = [pathAsURL lastPathComponent];
NSString *const prefixToBasename = [path substringToIndex: [path length] - [baseName length] - 1];
NSString *const baseNameWithoutExtension = [baseName substringToIndex: [baseName length] - [appSuffix length]];
NSString *backupPath = [NSString stringWithFormat: #"%#/%#.bak", prefixToBasename, baseNameWithoutExtension];
NSURL *const backupURL = [NSURL URLWithString: [NSString stringWithFormat: #"file://%#", backupPath]];
// Move backup to trash — I am sure this will be my next challenge
// (it's a no-op now because there is no pre-existing .bak file)
[[NSFileManager defaultManager] trashItemAtURL: backupURL
resultingItemURL: nil
error: &error];
// Move file to backup
primaryPresentedItemURL = pathAsURL;
presentedItemURL = backupURL;
presentedItemOperationQueue = [NSOperationQueue mainQueue];
[NSFileCoordinator addFilePresenter: self];
fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter: self]; // error here
[self backupItemWithCoordinationFrom: pathAsURL
to: backupURL];
[NSFileCoordinator removeFilePresenter: self];
fileCoordinator = nil;
}
The backupItemWithCoordinationFrom: method does the heavy lifting, basically:
[fileCoordinator coordinateWritingItemAtURL: from
options: NSFileCoordinatorWritingForMoving
error: &error
byAccessor: ^(NSURL *oldURL) {
[self.fileCoordinator itemAtURL: oldURL willMoveToURL: to];
[[NSFileManager defaultManager] moveItemAtURL: oldURL
toURL: to
error: &error];
[self.fileCoordinator itemAtURL: oldURL didMoveToURL: to];
}
but the code doesn't make it that far. I have traced the code and the URL variables are as I expect, and are reasonable. At the point of "error here" in the above code, where I allocate the File Presenter, I get:
NSFileSandboxingRequestRelatedItemExtension: an error was received from pboxd instead of a token. Domain: NSPOSIXErrorDomain, code: 1
[presenter] +[NSFileCoordinator addFilePresenter:] could not get a sandbox extension. primaryPresentedItemURL: file:///Users/cope/Me.pnd, presentedItemURL: file:///Users/cope/Me.bak
Any help is appreciated.
(I have read related posts Where can a sandboxed Mac app save files? and Why do NSFilePresenter protocol methods never get called?. I have taken note of several other sandboxing-related posts that don't seem relevant to this issue.)
MacBook Pro, MacOS 10.13.5, XCode Version 9.3 (9E145)
do not read too much about avoiding sandboxing. Most explenations go too far out of the most obvious problem. Instead of explaining the pitfalls that rightfully triggers sandboxing they explain mostly how to avoid the Sandbox at all. Which is not a solution - it is a thread!
So the most obvious problem is exposing a URL to pasteboard that still needs properly escaped characters in the string before you transform to NSURL.
So your NSString beginning with "file://" should use something like..
NSString *encodeStringForURL = [yourstring stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
before you transform to NSURL with
NSURL *fileurl = [NSURL URLWithString:encodeStringForURL];
NString *output = fileurl.absoluteString;
My iOS app wants to play a local audio file. So, in xCode, I’ve added a file "audio.m4a" to my project. It resides on the top level of the file tree, but
NSURL *audioFileURL = [[NSBundle mainBundle]
URLForResource:#"audio"
withExtension:#"m4a"];
returns nil. There must be a stupid oversight. What am I doing wrong?
NSBundle *mainBundle = [NSBundle mainBundle];
NSString *myFile = [mainBundle pathForResource: #"audio" ofType: #"m4a"];
NSURL *audioFileURL = [NSURL URLWithString:myFile];
and check whether that file present in Build Phases" -> "copy bundle Resources"
Swift
Since neither answer is complete, and the solution is disseminated in the comments, let me offer a Swift alternative. and a step-by-step response.
The original Objective-C code is correct:
NSURL *audioFileURL = [[NSBundle mainBundle]
URLForResource:#"audio"
withExtension:#"m4a"];
Swift
let audioFileURL = NSBundle.mainBundle().URLForResource(
"audio",
withExtension: "m4a")
Target Membership
This was probably an oversight when originally adding the resource to the project. You must select adequate targets when adding these files:
Regardless of the language, ensure that your file is Project > Build Phases > Copy Bundle Resources. You need not to do that by hand. Instead, use the File Inspector. Select your resource (on the left panel) and verify it's target membership (on the right panel):
/* Use this code to play an audio file */
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"test" ofType:#"m4a"];
NSURL *soundFileURL = [NSURL fileURLWithPath:soundFilePath];
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:nil];
player.numberOfLoops = -1; //Infinite
[player play];
I am making a OX Cocoa app and I want to be able to read and write text files using the app on button press. These text files should be saved in /Library/Application Support/AppName but i can't get my app to reading anything from there. It can write to the folder, but not read what it has written there, even though I can see the file sitting there in finder.
Here is the code I am using the successfully write to the folder.
NSString *text = editor.string;
NSString *path = #"/Library/Application Support/";
NSMutableString *mu = [[NSMutableString stringWithString:path] init];
[mu insertString:FileName.stringValue atIndex:mu.length];
[mu insertString:#".txt" atIndex:mu.length];
path = [mu copy];
[text writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:NULL];
Here is the code I am using (and failing) to read from the text files.
NSArray *path = [[NSBundle mainBundle] pathsForResourcesOfType:#"txt" inDirectory:#"/Library/Application Support/"];
NSString *output = #"";
NSMutableString *mu = [[NSMutableString stringWithString:output] init];
for (int i = 0; i < [path count]; i++) {
NSString *text = [NSString stringWithContentsOfFile:path[i] encoding:NSUTF8StringEncoding error:NULL];
[mu insertString:text atIndex:mu.length];
[mu insertString:#"\n" atIndex:mu.length];
}
[textView setString:mu];
Any tips on what I can correct would be super helpful, I'm a bit stuck here.
Edit: Using your input I have updated my code to this:
NSString *fileLocation = #"~/Library/Application Support/";
NSArray *text = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:fileLocation error:nil];
NSString *output = #"";
NSMutableString *mu = [[NSMutableString stringWithString:output] init];
for (int i = 0; i < [text count]; i++) {
[mu insertString:text[i] atIndex:mu.length];
[mu insertString:#"\n" atIndex:mu.length];
}
[textView setString:mu];
However the text from the files is still not appearing.
Most hard-coded paths will fail when you sandbox your app. Even if you get away with this one, or you don't plan to sandbox this app, it's a bad habit that's worth getting out of.
Moreover, are you sure you want /Library and not ~/Library? The former is often not writable by the user. The latter is in the user's Home directory (or your container when sandboxed).
To get the Application Support directory, or the Caches directory, or any other directory that you may want to create things in and later retrieve them from, ask a file manager for it.
/Library/Application Support is not in your bundle. The paths you get using [[NSBundle mainBundle] pathsForResourcesOfType:…] are only useful for accessing files inside your application itself (images, sounds, etc that you included when you built the app).
You want to use [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:error] to get a list of files in a directory outside your application.
Matt Gallagher has a great example of a fault-tolerant method of locating the path to your application support directory at Cocoa With Love. I would recommend using it over hardcoding the /Library/Application Support path.
NSError *error = nil;
NSArray *text = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:fileLocation error:&error];
if (!text) {
NSLog( #"Error reading contents of application support folder at %#.\n%#", applicationSupportFolder, [error userInfo] );
}
You're trying to get the path using NSBundle from the main bundle of the app. But the file is not in the bundle, you should specify the path manually. You could hardcode the path, store previously written paths somewhere, or use NSFileManager to get directory contents and analyze it. For example, -[NSFileManager contentsOfDirectoryAtPath:error:].
this is my code to play mp3 file from the directory of the application, and for some reason it's not working. lease find out what's wrong with this code !
-(IBAction)PlayLesson:(id)sender;
{
NSString *folderAndFile = #"/Users/alaaalfadhel/Library/Application Support/iPhone Simulator/5.1/Applications/1021CF5B-F664-4123-B9CB-529217225B74/Documents/file.mp3";
NSString *audioFilePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:folderAndFile];
NSURL *url = [NSURL fileURLWithPath:audioFilePath];
ofType:#"mp3"]];
AVAudioPlayer *click = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[click play];
}
Since the NSSearchPathForDirectoriesInDomains supplies everything up to the Documents directory, you don't need to give it a folder/file combination but just the file.
You really want your URL to be /Users/alaaalfadhel/Library/Application Support/iPhone Simulator/5.1/Applications/5515AFBA-1E7D-4B06-A62E-F6FDFD7DD7C7/Documents/file.mp3 instead of the one displayed in your log. This is necessary because the long hex value will change any time you remove the app from the simulator and then launch it again.
I'm trying to read ~/Library/Preferences/com.apple.mail.plist (on Snow Leopard) to get the email address and other information to enter into the about dialog. I'm using the following code, which is obviously wrong:
NSBundle* bundle;
bundle = [NSBundle mainBundle];
NSString *plistPath = [bundle pathForResource:#"~/Library/Preferences/com.apple.mail.plist" ofType:#"plist"];
NSDictionary *plistData = [NSDictionary dictionaryWithContentsOfFile:plistPath];
NSString *item = [plistData valueForKeyPath:#"MailAccounts.Item 2.AccountName"];
NSLog(#"Result = %#", item);
Moreover, the value I need to read is MailAcounts -> Item 2 -> AccountName and I am not sure I am doing this correctly (due to the space in the Item 2 key).
I tried reading Apple's developer guide to plist files but no help there.
How can I read a plist and extract the values as an NSString?
Thanks.
The first level is an array, so you need to use "MailAccounts.AccountName" and treat it as NSArray*:
NSString *plistPath = [#"~/Library/Preferences/com.apple.mail.plist" stringByExpandingTildeInPath];
NSDictionary *plistData = [NSDictionary dictionaryWithContentsOfFile:plistPath];
NSArray *item = [plistData valueForKeyPath:#"MailAccounts.AccountName"];
NSLog(#"Account: %#", [item objectAtIndex:2]);
Alternatively you can go by keys and pull the array from "MailAccounts" first using valueForKey: (which will yield NSArray*) and then objectAtIndex: to get the dictionary of that particular account (useful if you need more than the name).
Two things:
You don't want or need to use NSBundle to get the path to the file. The file lies outside of the app bundle. So you should just have
NSString *plistPath = #"~/Library/Preferences/com.apple.mail.plist";
You have to expand the tilde in the path to the user directory. NSString has a method for this. Use something like
NSString *plistPath = [#"~/Library/Preferences/com.apple.mail.plist" stringByExpandingTildeInPath];