I am having trouble removing files from my main bundle. When I delete them manually from support files in XCODE 4.2. They still show up when I run my app. I have opened up the app file with "show package contents" and manually deleted them from there and they still show up when I run the app. I have deleted the app from the simulator and from the ~/applications folder in library and the same behavior exists. Am I missing something?
Background: I have a helper app that I can drop files into the "support files" folder and run in order to convert them from KML to custom XML for use in another app via server downloads to the device. I create an array of file names from the main bundle with the code below and pass that to the parser. I have issues because it is including the deleted/removed files from the bundle and I can't figure out why. Any help would be appreciated.
-(NSArray*)findKMLFilesInMainBundle{
NSString *path = [[NSBundle mainBundle]resourcePath];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = [[NSError alloc]init];
NSMutableArray *kmlArray = [[NSMutableArray alloc]initWithCapacity:10];
NSArray *files = [fileManager contentsOfDirectoryAtPath:path error:&error];
unichar buffer[5];
//now seach for the kml files
for (NSString *fileName in files){
NSLog(#"%#",fileName);
int count = [fileName length];
int start = count - 3;
NSRange range = {start,3};
[fileName getCharacters:buffer range:range];
NSString *endString = [NSString stringWithCharacters:buffer length:3];
if ([endString isEqualToString:#"kml"]){
NSString *kmlFileName = [fileName stringByDeletingPathExtension];
NSLog(#"kmlFilename%#",kmlFileName);
[kmlArray addObject:kmlFileName];
}
}
for (NSString *name in kmlArray){
NSLog(#"file = %#",name);
}
return kmlArray;
}
Hold down ⌥ Option and choose Product → Clean Build Folder... from the menu bar. The default shortcut for this action is ⌥⇧⌘K.
Related
I'm trying to access the contents of a specific folder in my Application Bundle to copy it somewhere else but whenever I go into a folder it seems to be giving me the contents of the entire bundle. Any idea how I can get the contents only in this folder?
My Code
-(void) moveAssets {
NSString * resourcePath = [[NSBundle mainBundle] resourcePath];
NSLog(#"%#", resourcePath);
NSString *newPath = (#"%#/Test/",resourcePath);
NSError * error;
NSArray * directoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:newPath error:&error];
for (NSString* currentString in directoryContents){
NSLog(#"%#",currentString);
}
}
Logs
Logs keep giving me all the files and not just the ones in the Test folder
Try:
NSString *newPath = [resourcePath stringByAppendingPathComponent:#"Test"];
(reference).
I have created save.plist in a resource folder. I have written some data within that directly (without using coding). I am able to read that data but I'm not able to write through code to the same save.plist. By using following code I am trying to write the data but it gets stored within my .app plist.
The code is here
NSString *errorDesc = nil;
NSPropertyListFormat format;
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"save" ofType:#"plist"];
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSMutableDictionary *temp = (NSMutableDictionary *)[NSPropertyListSerialization
propertyListFromData:plistXML
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format errorDescription:&errorDesc];
if (!temp) {
NSLog(errorDesc);
[errorDesc release];
}
// [temp setValue:#"123" forKey:#"line1"];
// [temp writeToFile:plistPath atomically: YES];
//Reading data from save.plist
NSLog([temp objectForKey:#"name"]);
NSLog([temp objectForKey:#"wish"]);
NSNumber *num=[temp valueForKey:#"roll"];
int i=[num intValue];
printf("%d",i);
//writitng the data in save.plist
[temp setValue:#"green" forKey:#"color"];
[temp writeToFile:plistPath atomically: NO];
NSMutableDictionary *temp1 = (NSMutableDictionary *)[NSPropertyListSerialization
propertyListFromData:plistXML
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format errorDescription:&errorDesc];
NSLog([temp objectForKey:#"color"]);
I want that, the data which I want to write should get written into save.plist only which is stored in references. I am new with this concept. So if anyone knows it please help me.
Thanks in advance.
:-)
I don't know if I understand your question, but if you want to write into a .plist within your .app bundle you are probably doing something wrong. If you want to store preferences, you should consider using NSUserDefaults.
If you really want to modify a bundled .plist - here is some code:
NSString *plistPath = nil;
NSFileManager *manager = [NSFileManager defaultManager];
if (plistPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"Contents/Info.plist"])
{
if ([manager isWritableFileAtPath:plistPath])
{
NSMutableDictionary *infoDict = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
[infoDict setObject:[NSNumber numberWithBool:hidden] forKey:#"LSUIElement"];
[infoDict writeToFile:plistPath atomically:NO];
[manager changeFileAttributes:[NSDictionary dictionaryWithObject:[NSDate date] forKey:NSFileModificationDate] atPath: [[NSBundle mainBundle] bundlePath]];
}
}
Update:
Nate Flink pointed out that some of the NSFileManager methods used above are deprecated.
He posted an answer with the replacement methods below:
https://stackoverflow.com/a/12428472/100848
Updated version of the original awesome example by weichsel (thank you!). Xcode threw a couple warnings one of which is a deprecated method on NSFileManager. Updated here with non-deprecated methods from iOS 5.1
NSString *plistPath = nil;
NSFileManager *manager = [NSFileManager defaultManager];
if ((plistPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"mySpecial/PathTo.plist"]))
{
if ([manager isWritableFileAtPath:plistPath])
{
NSMutableDictionary *infoDict = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
[infoDict setObject:#"foo object" forKey:#"fookey"];
[infoDict writeToFile:plistPath atomically:NO];
[manager setAttributes:[NSDictionary dictionaryWithObject:[NSDate date] forKey:NSFileModificationDate] ofItemAtPath:[[NSBundle mainBundle] bundlePath] error:nil];
}
}
When you build the app, it will create an executable file "appName.app" and all the files are built in the bundle. Therefore, you can't access to resource folder when the app is running because all the data is in the bundle(not in folder).
However, you can access to a temp folder which contains some information of the app.
You can find the temp folder here:
Open finder--click on your username(under PLACES)--Library--Application Support--iPhone Simulator--User--Applications--(here you can find all the temp folders of your iPhone apps)
You can access to this temp folder by:
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
If you name your file save.plist, you can access to it like this:
NSString *filePath = [documentsDirectory stringByAppendingString:#"_save.plist"];
Then you just save your file to this filePath and it will appear in the temp folder named "Documents_save.plist".
*Note that the temp folder's name varies every time you run the app.
Recommend a book for you: 《Beginning iPhone Development--Exploring the iPhone SDK》. In Chapter 11 you can find what you want.
To summarize some of the other answers:
You're problem is that you're trying to write the file back into the folder that contains your application. That folder is not writable at runtime. Everything you're doing is fine, you just need to pick a different location to write your file to.
You can use the NSSearchPathForDirectoriesInDomains function to find a more suitable folder for this data. (Such as the #"Documents" folder.)
try this:
-(void)add:(NSRunningApplication *) app {
if ([self contains:app]) return;
[self.apps addObject:app.localizedName];
[self.apps writeToFile:self.dataFile atomically:YES];
}
from "Cocoa Programming".
you have to copy your plist into document directory...
because you cannot save anything without saving into document file....when you copied it will allow to write/modify on plist
This question already has answers here:
getting data from plist to NSMutableArray and writing the NSMutableArray to same plist iPhone
(3 answers)
Closed 9 years ago.
Previously, I used the following code to create an array for me and it worked.
bundle = [NSBundle mainBundle];
path = [bundle pathForResource:#"MultiSetting" ofType:#"plist"];
settingArray = [[NSMutableArray alloc] initWithContentsOfFile:path];
But after that, I wanted to modify the plist file, therefore, I used the following code to do that and it's NOT working.
NSFileManager *mgr = [NSFileManager defaultManager];
NSArray *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentPath objectAtIndex:0];
NSString *dstPath = [documentDirectory stringByAppendingPathComponent:#"MultiSetting.plist"];
bundle = [NSBundle mainBundle];
NSString *srcPath = [bundle pathForResource:#"MultiSetting" ofType:#"plist"];
NSError *error = nil;
[mgr copyItemAtPath:srcPath toPath:dstPath error:(NSError **)error];
settingArray = [[NSMutableArray alloc] initWithContentsOfFile:dstPath];
NSLog(#"%#", settingArray);
Is there any solution to solve this problem? Did I do anything wrong?
First thing you are passing error wrong. You need to change that line to
[mgr copyItemAtPath:srcPath toPath:dstPath error:&error];
Another thing according to your code after the first run, the above line will fail as there will already be a file that particular name. If you want to initialize only once then I guess you could write something like below which makes more sense:
NSError *error = nil;
if (![mgr fileExistsAtPath:dstPath]) {
[mgr copyItemAtPath:srcPath toPath:dstPath error:&error];
if (error) {
NSLog(#"%#",[error localizedDescription]);
}
}
And finally initWithContentsOfFile: might be failing because the plist cannot be parsed into an array. This can be because your plist file's root object is a dictionary (the default when you create the plist using Xcode).
Since you are able to parse the plist file in bundle it might be because, you might have accidentally copied the wrong file (or an empty file or a plist with root as dictionary) the first time and then was not able to copy. So try deleting the file from dstPath and try again.
To check the file do an NSLog of dstPath. For example if you get something like this in the console:
/Users/xxxx/Library/Application Support/iPhone Simulator/5.0/Applications/194351E6-C64E-4CE6-8C82-8F66C8BFFAAF/Documents/YourAppName.app
Copy this till the Documents folder, i.e
/Users/xxxx/Library/Application Support/iPhone Simulator/5.0/Applications/194351E6-C64E-4CE6-8C82-8F66C8BFFAAF/Documents/
Go to:
Finder -> Go -> Go To Folder
and paste the path and click on Go. This should take you to the actual directory and check the contents of the plist . Open it as source code in Xcode to see what is the root object.
Also try deleting this file you find here and running your app agin.
Another way to achieve the above would be to initialize the array from bundle and then write it directly instead of copying the file (but this is not a the direct answer to your problem just a workaround) i.e :
NSString *srcPath = [bundle pathForResource:#"MultiSetting" ofType:#"plist"];
NSError *error = nil;
//initialize from source
NSMutableArray *settingsArray = [[NSMutableArray alloc] initWithContentsOfFile:srcPath];
//write to file
NSError *error = nil;
//check if file exists
if (![mgr fileExistsAtPath:dstPath]) {
[settingArray writeToFile:dstPath atomically: YES];
if (error) {
NSLog(#"%#",[error localizedDescription]);
}
}
Any changes you make to the settingArray array do not automatically get saved to disk. You need to explicitly save it to disk. When you actually want to save the contents of the settingArray variable, you need to call:
[settingArray writeToFile:dstPath atomically: YES];
Pulling my hair out trying to work this out. i want to read and write a list of numbers to a txt file within my project. however [string writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error] doesnt appear to write anything to the file. I can see there is the path string returns a file path so it seems to have found it, but just doesnt appear to write anything to the file.
+(void)WriteProductIdToWishList:(NSNumber*)productId {
for (NSString* s in [self GetProductsFromWishList]) {
if([s isEqualToString:[productId stringValue]]) {
//exists already
return;
}
}
NSString *string = [NSString stringWithFormat:#"%#:",productId]; // your string
NSString *path = [[NSBundle mainBundle] pathForResource:#"WishList" ofType:#"txt"];
NSError *error = nil;
[string writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error];
NSLog(#"%#", error.localizedFailureReason);
// path to your .txt file
// Open output file in append mode:
}
EDIT: path shows as /var/mobile/Applications/CFC1ECEC-2A3D-457D-8BDF-639B79B13429/newAR.app/WishList.txt so does exist. But reading it back with:
NSString *path = [[NSBundle mainBundle] pathForResource:#"WishList" ofType:#"txt"];
returns nothing but an empty string.
You're trying to write to a location that is inside your application bundle, which cannot be modified as the bundle is read-only. You need to find a location (in your application's sandbox) that is writeable, and then you'll get the behavior you expect when you call string:WriteToFile:.
Often an application will read a resource from the bundle the first time it's run, copy said file to a suitable location (try the documents folder or temporary folder), and then proceed to modify the file.
So, for example, something along these lines:
// Path for original file in bundle..
NSString *originalPath = [[NSBundle mainBundle] pathForResource:#"WishList" ofType:#"txt"];
NSURL *originalURL = [NSURL URLWithString:originalPath];
// Destination for file that is writeable
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSURL *documentsURL = [NSURL URLWithString:documentsDirectory];
NSString *fileNameComponent = [[originalPath pathComponents] lastObject];
NSURL *destinationURL = [documentsURL URLByAppendingPathComponent:fileNameComponent];
// Copy file to new location
NSError *anError;
[[NSFileManager defaultManager] copyItemAtURL:originalURL
toURL:destinationURL
error:&anError];
// Now you can write to the file....
NSString *string = [NSString stringWithFormat:#"%#:", yourString];
NSError *writeError = nil;
[string writeToFile:destinationURL atomically:YES encoding:NSUTF8StringEncoding error:&error];
NSLog(#"%#", writeError.localizedFailureReason);
Moving forward (assuming you want to continue to modify the file over time), you'll need to evaluate if the file already exists in the user's document folder, making sure to only copy the file from the bundle when required (otherwise you'll overwrite your modified file with the original bundle copy every time).
To escape from all the hassle with writing to a file in a specific directory, use the NSUserDefaults class to store/retrieve a key-value pair. That way you'd still have hair when you're 64.
Apologies for the vague question title...
here is my scenario:
I am dynamically loading UIView content from nib files stored in NSBundles
BOOL bundlePathExists = [fm fileExistsAtPath:bundlePath];
if (bundlePathExists) {
NSBundle *tempBundle = [NSBundle bundleWithPath:bundlePath];
UIViewController *vc = [[UIViewController alloc] initWithNibName:nibName bundle:tempBundle];
[self.view addSubview:vc.view];
}
(note: the above is a simplified excerpt of the actual code, as not to confuse the nature of the question - the mechanics of doing this is relatively well documented here)
these nib files contain a number of images, some of which are unique to that bundle, and others that are shared across multiple bundles.
i'd like to be able to store the images that are common inside the main bundle, so they don't take up space in the other bundles, and to minimise the maintenance of the project as a whole - eg if i change one of those common images, i'd rather not have to rebuild every bundle it is referenced by.
i realize i could do this programatically by using
[commonImageViewIvar setImage:[UIImage imageWithName:commonImageName]];
however i would prefer to achieve this without writing custom code for each view (ie the view controller instantiated is not customised per nib, hence all information needs to be stored in the nib)
As promised here is the solution i came up with:
The bundle files themselves are downloaded as described here. To add to the information listed there, I found that if you create the views in an iphone app first, you can then preview them in the simulator. then, you can create a new project, as a bundle, and drag and drop ALL the image files & the xib &.h file into the new project. don't drag the .m file across as this creates build issues. then, ensure that the project settings define the BASE SDK as "Latest IOS", and not the default Mac OS X setting that would have been selected. you can then still edit the xib by double clicking on it. any common files can be deselected from the build by unticking the "target" column for that file. see "Additional Information" later in this answer post.
once i have downloaded each bundle in a zip file from the server, i utilize the following methods i coded for this purpose:
-(NSArray *) imageFileExtensions {
return [NSArray arrayWithObjects:#".png",#".gif",#".jpg",#".jpeg",#".bmp", nil];
}
and
-(void) cloneImageFilesInPath:(NSString *)path toPath:(NSString*)clonePath {
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *extensions = [self imageFileExtensions];
if ([fm fileExistsAtPath:path]) {
NSArray *files = [fm contentsOfDirectoryAtPath:path error:nil];
for (NSString *file in files){
for (NSString *ext in extensions) {
if ([file hasSuffix:ext]) {
NSString *targetFile = [clonePath stringByAppendingPathComponent:file];
if (![fm fileExistsAtPath:targetFile]) {
[fm createSymbolicLinkAtPath:targetFile withDestinationPath:[path stringByAppendingPathComponent:file] error:nil];
}
break;
}
}
}
}
}
these methods create scan the main app bundle directory and for each image file that is NOT in the custom bundle directory, a symbolic link is created inside the custom bundle directory to point back to the main app bundle directory.
They are invoked in my app delegate as follows:
...(insert code to unzip the file to bundlePath here)...
[self cloneImageFilesInPath:[[NSBundle mainBundle] bundlePath] toPath:bundlePath];
Additional information
to create the actual bundles, i made a separate app that removed any image files from a custom bundle directory, if those filenames are present in a directory that contains the common image files that are deployed in the main app bundle. i later discovered you can prevent xcode from including certain files from the build by deselecting the target column checkbox for that file, so this step is not necessarily needed - if you have a lot of views to create however, it may be easier to just leave them in the bundles, and strip them out using this method.
-(void) removeDuplicatedImageFilesInPath:(NSString *)sourcePath fromTargetPath:(NSString*)path {
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *extensions = [self imageFileExtensions];
if ([fm fileExistsAtPath:path]) {
NSArray *files = [fm contentsOfDirectoryAtPath:sourcePath error:nil];
for (NSString *file in files){
for (NSString *ext in extensions) {
if ([file hasSuffix:ext]) {
NSString *targetPath = [path stringByAppendingPathComponent:file];
if ([fm fileExistsAtPath:targetPath]) {
[fm removeItemAtPath:targetPath error:nil];
}
break;
}
}
}
}
}
Further Information for dealing with the zip files
I opted on using ObjectiveZip (following a suggestion by a poster here. To simplify the task, i wrapped this in the following 2 app delegate methods (one is used in the actual app, another in the the offline bundle creation app)
in the main app
-(void) unzipArchive:(NSString *)zipFileName toPath:(NSString *)path {
NSFileManager *fm = [NSFileManager defaultManager];
ZipFile *unzipFile = [[ZipFile alloc] initWithFileName:zipFileName mode:ZipFileModeUnzip];
NSArray *infos= [unzipFile listFileInZipInfos];
[unzipFile goToFirstFileInZip];
for (FileInZipInfo *info in infos) {
ZipReadStream *read1= [unzipFile readCurrentFileInZip];
NSMutableData *fileData = [[[NSMutableData alloc] initWithLength:info.length] autorelease];
int bytesRead1 = [read1 readDataWithBuffer:fileData];
if (bytesRead1 == info.length) {
NSString *fileName = [path stringByAppendingPathComponent:info.name ];
NSString *filePath = [fileName stringByDeletingLastPathComponent];
[fm createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:nil];
[fileData writeToFile:fileName atomically:YES];
}
[read1 finishedReading];
[unzipFile goToNextFileInZip];
}
[unzipFile close];
[unzipFile release];
}
and in the custom bundle creation offline app.
-(void) createArchive:(NSString *) zipFileName usingPath:(NSString *)path {
NSArray *files = [self filesInDirectory:path];
ZipFile *zipFile= [[ZipFile alloc] initWithFileName:zipFileName mode:ZipFileModeCreate];
for (NSString *file in files) {
NSString *fileNameForZip = [file substringFromIndex:path.length];
ZipWriteStream *stream= [zipFile writeFileInZipWithName:fileNameForZip fileDate:[NSDate dateWithTimeIntervalSinceNow:-86400.0] compressionLevel:ZipCompressionLevelBest];
[stream writeData:[NSData dataWithContentsOfFile:file]];
[stream finishedWriting];
}
[zipFile close];
[zipFile release];
}
note: the previous method relies on the following 2 methods, which create an NSArray containing the fully qualified path to all files in the given path (recursing into sub directories)
-(void)loadFilesInDirectory:(NSString *)path intoArray:(NSMutableArray *)files {
NSFileManager *fm = [NSFileManager defaultManager];
NSMutableArray *fileList = [NSMutableArray arrayWithArray:[fm contentsOfDirectoryAtPath:path error:nil]];
for (NSString *file in fileList) {
BOOL isDirectory = NO;
NSString *filePath = [path stringByAppendingPathComponent:file];
if ([fm fileExistsAtPath:filePath isDirectory:&isDirectory]) {
if (isDirectory) {
[self loadFilesInDirectory:filePath intoArray:files];
} else {
[files addObject:filePath];
};
};
}
}
-(NSArray *)filesInDirectory:(NSString *)path {
NSMutableArray *files = [NSMutableArray array];
[self loadFilesInDirectory:path intoArray:files];
return files;
}