Embedding XML to iOS application - objective-c

I'm making my first game in iOS and I need to load some levels that are stored as XML. Level files are stored locally and everything works fine from emulator. However, since XML is loaded in the runtime when i try to test my game on an actual device it can't find the XML files since they are not actually part of the app.
I'm coming from Flash background so I might have a wrong idea how this is done on iOS but I need to somehow bundle those XML files with the app, or embed it in the code somehow? Is this possible?
Thanks a lot :)

Well The code to look up your app bundle for the specified file is
NSString *path = [[NSBundle mainBundle] pathForResource:#"fileName.xml" ofType:nil]
NSURL *xmlURL = [NSURL fileURLWithPath:path];
[[NSXMLParser alloc] initWithContentsOfURL: xmlURL]
Hope this helps you...

You could add the XML file to your project and run time read it up with something like this
NSArray *myPathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *myPath = [myPathList objectAtIndex:0];
NSError **err;
myPath = [myPath stringByAppendingPathComponent:fileName];
if([[NSFileManager defaultManager] fileExistsAtPath:myPath])
text = [NSString stringWithContentsOfFile:myPath encoding:NSUTF8StringEncoding error:err];
You could consider using JSON in instead of XML. At least to my knowledge the available XML parsers are not nearly as simply to use as SBJson for instance (https://github.com/stig/json-framework/)

Here is my solution in Swift 3.0. In addition to the code, you'll need to add your xml file as a data set in your Assets.xcassets. Do this by creating a new Data Set, giving it any name, and then dragging the xml file from the finder to your newly created data set. When you reference the file in your code, you'll use the file name of the file, and not the name of the data set.
var parseResults = false
if let path = Bundle.main.path(forResource: fileName, ofType: ".xml") {
let url = URL(fileURLWithPath: path)
if let xmlParser = XMLParser(contentsOf: urlToFile) {
xmlParser.delegate = self
parseResults = xmlParser.parse()
}
}

Related

Issues playing local files with AVPlayer?

Basically, I'm writing a video file to the Application Support directory and then saving it's path:
NSString *guid = [[NSUUID new] UUIDString];
NSString *outputFile = [NSString stringWithFormat:#"video_%#.mp4", guid];
NSString *outputDirectory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *tempPath = [outputDirectory stringByAppendingPathComponent:outputFile];
NSURL *fileURL = [NSURL fileURLWithPath:tempPath]
// code to write a video to fileURL
I save the string path itself by calling [fileURL path];
Now later when I try to create an AVAssetItem from it, I can't actually get it to play.
EDIT:
Ok, so it seems the issue is the space in Application Support. I tried saving the file to just the Library directory and it worked fine.
So the question becomes, how can I play a video I save in the Application Support directory. I wasn't able to create a valid NSURL without escaping the space (when I tried it would return a nil NSURL) but it seems that the space/escaping doesn't allow it to play correctly.
Assume the NSURL to NSString conversion is required (unless it really should be avoided).
Also, side note, but if anyone could give some insight as to why this question was down voted so I can improve the quality of my questions, that would be appreciated. I don't understand?
While I am not informed enough to opine about whether this would change if I had used matt's recommended methods: URLForDirectory:inDomain:appropriateForURL:create:error: and URLByAppendingPathComponent: the actual issue here isn't converting an NSURL to NSString
It's that I'm saving the full URL ([fileURL path]). This is because the [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0]; dynamically changes and the full path won't always point me to the appropriate file.
Instead I need to save just the name of my file (in my case I needed to persist outputFile) and then later dynamically build the the full path when I need it.
The same exact process:
NSString *outputDirectory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *tempPath = [outputDirectory stringByAppendingPathComponent:outputFile];
NSURL *fileURL = [NSURL fileURLWithPath:tempPath];
worked just fine.
TLDR: Saving a full path doesn't work

How to tell if a file exists always returns false but I can load it via image tag

In xcode, I have create a folder called IMAGES and copied all of the images I will be using into that folder.
I am updating a table using the following code to insert the image:
NSString *picName = [NSString stringWithFormat:#"%#.jpg", [[theemplyees objectAtIndex:indexPath.row] valueForKey:#"EmpNo"]];
cell.empPicture.image = [UIImage imageNamed:picName];
Note the no location needing supplied. What I want to do is, if a users picture does not exist in the directly, then set the image to a default image. Such as:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//Set image
NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:picName];
//Check if the image exists at a given path
BOOL imageExists = [[NSFileManager defaultManager] fileExistsAtPath:imagePath];
//If the image doesn't exists download it.
if (!imageExists)
{
picName = #"nopicture.jpg";
}
The problem is, using the above, it ALWAYS says the files don't exist. NONE! Grrr
So am i placing the files in the wrong location? I attempted to create a directory called Resources and place them there but still didn;t work.
Any help is greatly appreciated. Thanks in advance for any and all help.
Geo...
Nothing will be in the app's Documents directory unless you store something there during runtime. The Documents directory and the app's resource bundle are two completely different things.
If you created a folder in Xcode for your images, those images are still stored in the app's resource bundle, not the Documents folder.
The UIImage imageNamed: method only looks in the resource bundle.

LSCopyApplicationURLsForURL always returns null

When I try to log all available editors on my system for my temporary file (which is "toString" in this code) it always returns null, although I have many applications installed on my system.
NSArray *appUrls = (NSArray*)LSCopyApplicationURLsForURL((CFURLRef)[NSURL URLWithString:toString], kLSRolesViewer | kLSRolesEditor);
toString is containing the following file path:
/var/folders/pl/tcc5k3fd6tj2__9dprg9dm1m0000gp/T/tempFile
What should be the problem here?
[NSURL URLWithString:toString]
expects a complete URL string including scheme, such as "file://var/folders/...".
Use
[NSURL fileURLWithPath:toString]
instead to get a file URL with the specified path.
Another problem could be that your file name does not have any file extension (e.g. ".txt"), because Launch Services uses the extension (or file type/creator) to find a suitable application.
I was struggling with the this and I wanted to get all Bundles that could open a determined path/file extension.
If you have a file extension, you can get all bundles that can edit it by the following:
//All Bundle Ids
NSString *pathExtension = #"docx";
CFArrayRef utisRef = UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension,(__bridge CFStringRef) pathExtension,nil);
NSLog( #"UTI: utisRef %#", utisRef);
NSArray *utis = CFBridgingRelease(utisRef);
NSMutableSet *mutableSet = [[NSMutableSet alloc] init];
for (NSString *uti in utis) {
CFArrayRef bundleIDsRef = LSCopyAllRoleHandlersForContentType((__bridge CFStringRef) uti,kLSRolesEditor);
[mutableSet addObjectsFromArray:CFBridgingRelease(bundleIDsRef)];
}
NSLog( #"bundleIDs: %#", mutableSet);
If you have a path of file and you want to get all apps location that can edit it, you can use the following:
//Location of apps
NSString *str = #"/Users/ricardoanjos/Library/Developer/Xcode/DerivedData/EgnyteDrive-hforbniifiojczefbnwanzxakvlr/Build/Products/Debug/1.pdf";
NSURL* url = [[NSURL alloc] initFileURLWithPath:str];
CFURLRef urlRef = (__bridge CFURLRef)url;
CFArrayRef appUrlsRef = LSCopyApplicationURLsForURL(urlRef, kLSRolesEditor);
NSArray *appUrls = CFBridgingRelease(appUrlsRef);
NSLog(#"appUrls: %#", appUrls);
I hope it will help.

File write with [NSBundle mainBundle] fails

I am trying to take content from one file and write it into another. I am reading fine, but I am not able to write it into another file.
I have a database of words. I want to separate the words into different files based on the number of letters. All four letter words go into one file, and so on. I added a txt file called "4letter" into my resources and the following is my code:
NSError *error;
//READ
NSString *dbFile = [[NSBundle mainBundle] pathForResource:#"words" ofType:#"txt"];
NSString *test = [NSString stringWithContentsOfFile:dbFile encoding:NSUTF8StringEncoding error:&error];
//convert from string to array
NSArray *lines = [test componentsSeparatedByString:#"\n"];
NSFileHandle *logFile = nil;
logFile = [NSFileHandle fileHandleForWritingAtPath:[[NSBundle mainBundle] pathForResource:#"4letter" ofType:#"txt"]];
//Test if write works
for (int i=0; i<5; i++)
{
NSString *randomAnagram = [[lines objectAtIndex:i] lowercaseString];
[logFile writeData: [randomAnagram dataUsingEncoding: NSNEXTSTEPStringEncoding]];
}
In iOS, you can't write into a file in your app's bundle -- the entire bundle is read-only. Use a path into the Documents folder instead.
See special File System Programming Guide for better understnading.
In iOS, you can't write into a file in your app's bundle -- the entire bundle is read-only.
Consider reading iOS Data Storage Guidelines to better understand the purpose of directories below, in context of iCloud backup.
<Application_Home>/AppName.app
This is the bundle directory containing the app itself. Do not write
anything to this directory. To prevent tampering, the bundle directory
is signed at installation time. Writing to this directory changes the
signature and prevents your app from launching again.
<Application_Home>/Documents/
Use this directory to store critical user documents and app data
files. Critical data is any data that cannot be recreated by your app,
such as user-generated content. The contents of this directory can be
made available to the user through file sharing. The contents of this
directory are backed up by iTunes.
<Application_Home>/Library/
This directory is the top-level directory for files that are not user
data files. You typically put files in one of several standard
subdirectories but you can also create custom subdirectories for files
you want backed up but not exposed to the user. You should not use
this directory for user data files. The contents of this directory
(with the exception of the Caches subdirectory) are backed up by
iTunes. For additional information about the Library directory, see
“The Library Directory Stores App-Specific Files.”
See full list (tmp/, Documents/Inbox) in iOS Standard Directories: Where Files Reside
UPDATE
I use NSFileManager method URLForDirectory:inDomain:appropriateForURL:create:error:
Like Caleb said, you can't write to your app's directory, but you can write to your app's Documents folder. You can get it like this:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
Your app's bundle is read-only. There is two ways I could see:
1) Write in documents folder:
NSArray *pathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path =  [myPathList  objectAtIndex:0];
2) Use sqlite database. This is the same as 1 (you must save db in documents anyway), but you're using sqlite database. I think this is better than a lot of txt and plist files: here's a tutorial on the topic.
I use the following code :
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *appFile = [documentsDirectory stringByAppendingPathComponent:#"set.txt"];
NSString *data=#"Kostas";
[data writeToFile:appFile atomically:YES];
NSString *myData = [NSString stringWithContentsOfFile:appFile];
NSLog(#"Data : %# ",myData);

Failure to write NSString to file on iPad

I'm using a text file to save the changes made by a user on a list (the reason that I'm doing this is so that I can upload the text file to a PC later on, and from there insert it into an Excel spreadsheet). I have 3 data structures: A NSMutableArray of keys, and a NSMutableDictionary who's key values are MSMutableArrays of NSStrings.
I iterate through these data structures and compile a file string that looks much like this:
(Key);(value)\t(value)\t(value):\n(Key);(value).. .so on.
SO, onto the actual question: When I attempt to save it, it fails. I'm 99% sure this is because of the file path that I'm using, but I wanted backup to check this out. Code follows:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *filePath = [paths objectAtIndex:0];
NSString *fileString = [NSString stringWithString:[self toFileString]];
if(![fileString writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:NULL]){
NSLog(#"File save failed");
} else {
// do stuff
}
(Code above is re-copied, since the actual code is on a different computer. It compiles, so ignore spelling errors?)
I tried using NSError, but I got bogged down in documentation and figured I might as well ask SO while trying to figure out how to properly use NSError (might be a little bit of an idiot, sorry).
99% sure it's the NSArray *paths line that's tripping it up, but I don't know how else to get the documents directory.
Edit: Problem solved, and one final question: If I save it to the App's document directory, where can I go after I close the app to see if it saved properly? If it works like I think it does, isn't it sandboxed in with the app's installation on the simulator? (i.e. no way of checking it)
NSLog() that filePath string. I think you're trying to write to the directory itself, not to a file.
Try this instead:
filePath = [[paths objectAtIndex:0]stringByAppendingPathComponent:#"myfile.txt"];
What is the file name you want to save? The method
NSArray *paths = NSSearchPathForDirectoriesInDomains(...);
NSString *filePath = [paths objectAtIndex:0];
...
if(![fileString writeToFile:filePath ...
means you are saving the string into a file path which has the same name as a folder. This will of course fail. Please give it a name, e.g.
NSString* fileName = [filePath stringByAppendingPathComponent:#"file.txt"];
if(![fileString writeToFile:fileName ...
and try again.
BTW, to use NSError:
NSError* theError = nil;
if(![fileString writeToFile:fileName ... error:&theError]) {
// ^^^^^^^^^
NSLog(#"Failed with reason %#", theError);
// theError is autoreleased.
}