Reference all files in a group or folder in Xcode - ios

I want to know if I create a group or folder full of files, like images, say img1.jpg, img1#2x.jpg, img2.jpg, img2#2x.jpg ... img10.jpg, img10#2x.jpg, is there a way to create an array of all these images dynamically without knowing how many images are there? Basically, I want something dynamic so it will find all the new images if I add more without having to also update my code. Can this be done?
Thanks for any help...

No, unfortunately XCode does not have that feature, however if you are willing to manually increment a constant, you can easily do some string manipulation to get an array:
#define IMAGE_COUNT 10
#define PREFIX #"img"
NSMutableArray *images, *images2x;
images = [NSMutableArray arrayWithCapacity:IMAGE_COUNT];
images2x = [NSMutableArray arrayWithCapacity:IMAGE_COUNT];
for(int i = 0; i < IMAGE_COUNT; i++){
NSString *imagePath = [PREFIX stringByAppendingFormat:#"%d.jpg",i];
NSString *image2xPath = [PREFIX stringByAppendingFormat:#"%d#2x.jpg",i];
[images addObject:[UIImage imageNamed:imagePath]];
[images2x addObject:[UIImage imageNamed:image2xPath]];
}
you are manually adding the files and they are all numbered so it can't hurt to simply change IMAGE_COUNT every time you add an image.

To make xCode automatically detect new images you need to add the folder that contains those images "as reference". When clicking and dragging the image folder into xCode it will pop up with an import window. One of the radial buttons will have the option "Add as reference." Select that one and the folder will be auto updated with anything that you add into it. You may have to clean and rebuild the project in order for xCode to actually send those updates to your app.
To do it in your app (in code) you would do something like:
NSError *error;
NSArray *fileList = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:yourFolderPath error:&error];
That will give you an array of NSStrings containing the names of all the files and folders inside the selected folder. If you need subfolders too then you are going to have to do recursive calls.

Related

Large files downloaded to Documents and backed up to iCloud

I have an iOS app in the app store that can download relatively large files that need to stay on the device for offline use. Those files are currently stored in the app's Documents folder but I'm just now reading that the Documents folder is backed up and should really only be used for user-generated content. This Apple technical Q&A states that the NSURLIsExcludedFromBackupKey should be set to prevent backup. This states that an app's /Library/Caches is the right place to put these kinds of files although further reading suggests that the folder may be cleared when the device is low on storage which is unacceptable for this app. I believe /Library/Application Support/ is then the best location for them -- does this sound right?
Unfortunately, this mistake got through the app review process. What are some best practices for fixing this now that people are using the app and already have some files persisted to the Documents folder and to their backups? It seems I need to move all the existing files and set their NSURLIsExcludedFromBackupKey on app update. How do I guarantee that this is done exactly once and that it isn't interrupted? Is moving the files out of the Documents folder important or could I leave them there? Will changing the files' backup status remove them from existing backups?
I'm using Swift 2.1.1 and targeting iOS 8.0+.
As stated in the technical Q&A, you best bet could be create a subdirectory in the Documents, and exclude that subdirectory once.
I don't believe you can write a 'do it once and be sure it is done' routine, since you can't guarantee your app doesn't crash while it is running. You certainly could set a completion flag when you are sure it is done so that once it is done you don't have to run it again.
Exclude your directory from backup, not the individual files.
From Xcode:
You can use this property to exclude cache and other app support files which are not needed in a backup. Some operations commonly made to user documents cause this property to be reset to false; consequently, do not use this property on user documents.
Here is the strategy I have used with good results
(sorry, its in objective-c -- I'm not a swift guy. Hopefully it will give you the idea):
- (BOOL)moveAllFiles{
// Searches through the documents directory for all files ending in .data
BOOL success = true;
NSString *myNewLocation = #"/store/my/files/here/now";
// Get the documents directory
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
// Get all files ending in .data (whatever your file type is)
NSArray *dataFilesArray = [NSArray arrayWithArray:[NSBundle pathsForResourcesOfType:#"data" inDirectory:documentDirectory]];
// If you have multiple resource types, use this line once each for each resource type, then append the arrays.
// Iterate the found files
NSString *fileName = [NSString string];
for (int i=0; i<[dataFilesArray count]; i++) {
fileName = [[dataFilesArray objectAtIndex:i] lastPathComponent];
// Move file, set success to false if unsuccessful move
if (![[NSFileManager defaultManager] moveItemAtPath:[dataFilesArray objectAtIndex:i]
toPath:[myNewLocation stringByAppendingPathComponent:fileName]
error:nil]) {
success = false; // Something went wrong
}
}
return success;
}
Now use the value of success to set a key in the user defaults file. Check for that key on startup. If it is false (or absent), run this routine (again).
This example is with file paths. You can do the same thing with file URLs if you wish.

iOS8 extension : share images between container and extension

I'm making an iOS 8 extension. Here's what I'm trying to do: Users select images from the photo library in the container app, and these images will be shared with the extension and for the further use.
Right now I'm doing it in this way (If you don't want to read this part, please skip below to read the actual codes): Use App Group and NSUserDefaults to share datas. Convert UIImage into NSData and then save all the images in a NSArray, then save the array into a NSDictionary (I have many arrays and this is the way I organize them - so I have to save them into dictionary), finally save the dictionary into user default.
Here's the coding:
NSArray *imageArray = ...
//this array contains all the images.
//photoDataArray is a NSMutableArray;
photoDataArray = [[NSMutableArray alloc]init];
for (UIImage *images in imageArray) {
[photoDataArray addObject:UIImagePNGRepresentation(images)];
}
NSThread * creationThread = [[NSThread alloc] initWithTarget:self selector:#selector(handleData) object:nil];
[creationThread start];
-(void)handleData{
NSDictionary *dic = [[NSDictionary alloc]init];
[dic SetObject:photoDataArray forKey:#"testImageArray"];
NSUserDefaults * def = [[NSUserDefaults alloc] initWithSuiteName:#"group.myCompany.myApp"];
[def setObject:dic forKey:#"dataDic"];
//done with saving data
[self.navigationController popViewControllerAnimated:YES];
//Navigation
}
When I want to retrieve the images:
NSUserDefaults * def = [[NSUserDefaults alloc] initWithSuiteName:#"group.myCompany.myApp"];
NSDictionary *dic = [def ObjectForKey:#"dataDic"];
NSArray *dataArray = [dic objectForKey:#"testImageArray"];
NSMutableArray *convertedArray = [[NSMutableArray alloc] init];
for (NSData *imageData in dataArray) {
[convertedArray addObject:[UIImage imageWithData:imageData]];
}
convertedArray would be the array of images I want to get.
Apparently, there are a lot of problems if I do it this way.
For example, the two major issues:
Doing this takes a lot of resources including memory. It takes about half minute to actually finish the process.If I have a array with about 20 images, I'll get "didRecieveMemoryWarning" about 3 times (I'm using a iPad mini as a test device). Sometimes the datas are not saved correctly. After the viewController is popped out(which means it runs to the last line of my storing code), I get nil for the array I just saved into the UserDefault! I'm sure my coding all worked normal, and this issue is caused by low memory because if the array has less than 15 images, I can save and retrieve them perfectly.
It's hard to save new images into a previously saved array. When I want to do that, I have to retrieve the previous array and add new image datas into that array, and then save the new array into the UserDefault. As mentioned before, saving an array into the UserDefault takes a lot of memory.
So my questions are pretty straight foward and specific:
Are there any other ways to transfer images from one target to another? In other words: How can I transfer images from the container app to the extension?
If not, are there any ways to solve the issue in my codes? Is this a proper way to do it?
Those are all I want to ask, but if you could answer following questions for me also, it will be really nice:
Why would I get more than one "didRecieveMemoryWarning" in one saving process? When the system received memory warning, will it stop the action immediately?
(Just to make sure) Is that safe to use UIImagePNGRepresentation for all the images including PNG and JPG?
Thank you.
From Apple's Documentation on App Extension Programming
Sharing Data with Your Containing App
The security domains for an app extension and its containing app are distinct, even though the extension bundle is nested within the containing app’s bundle. By default, your extension and its containing app have no direct access to each other’s containers.
You can, however, enable data sharing. For example, you might want to allow your app extension and its containing app to share a single large set of data, such as prerendered assets.
.....
When you set up a shared container, the containing app—and each contained app extension that you allow to participate in data sharing—have read and write access to the shared container. To avoid data corruption, you must synchronize data accesses. Use Core Data, SQLite, or Posix locks to help coordinate data access in a shared container.

How can I change .plist entries based on my Scheme?

I have some App-Info.plist entries that need to change based on my environment. When I'm doing developmental work, they need to be one set of values, vs QA vs Production.
What would be nice is if I could simply have a script or something that runs based on the Scheme used to do the compilation.
Is this possible?
You can do it by performing some extra steps:
Duplicate [AppName]-Info.plist file by any name. Example: [AppName]-Info-dev.plist or [AppName]-Info-staging.plist etc
Map newly created .plist file in App's Target Settings. Get idea from following screenshot:
At the end, if you want to get some entry from .plist file then you need to get it like: [[[NSBundle mainBundle] infoDictionary] objectForKey:#"baseUrl"]
Project setting will automatically pick correct .plist file and give you required value.
I think msmq's answer is valid, but you should be a little careful about using the main info.plist this way. Doing that suggests that all your versions of info.plist are almost identical, except for a couple of differences. That's a recipe for divergence, and then hard-to-debug issues when you want to add a new URI handler or background mode or any of the other things that might modify info.plist.
Instead, I recommend you take the keys that vary out of the main info.plist. Create another plist (say "Config.plist") to store them. Add a Run Script build phase to copy the correct one over. See the Build Settings Reference for a list of variables you can substitute. An example script might be:
cp ${SOURCE_ROOT}/Resources/Config-${CONFIGURATION}.plist ${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Config.plist
Then you can read the file using something like this (based on Read in the Property List):
NSString *baseURL;
NSString *path = [[NSBundle mainBundle] pathForResource:#"Config" ofType:#"plist"];
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSString *errorDesc = nil;
NSDictionary *dict = (NSDictionary *)[NSPropertyListSerialization
propertyListFromData:plistXML
mutabilityOption:NSPropertyListImmutable
format:NULL
errorDescription:&errorDesc];
if (dict != nil) {
baseUrl = dict[#"baseURL"];
} else {
NSAssert(#"Could not read plist: %#", errorDesc); // FIXME: Return error
}
There are other solutions of course. I personally generally use the preprocessor for this kind of problem. In my build configuration, I would set GCC_PREPROCESSOR_DEFINITIONS to include BaseURL=... for each build configuration and then in some header I would have:
#ifndef BaseURL
#define BaseURL #"http://default.example.com"
#endif
The plist way is probably clearer and easier if you have several things to set, especially if they're long or complicated (and definitely if they would need quoting). The preprocessor solution takes less code to process and has fewer failure modes (since the strings are embedded in the binary at compile time rather than read at runtime). But both are good solutions.
You can add a User-Defined-Setting in Xcode>Build-Settings, add its values according to all the schemes listed there. And then simply use that as a variable in Info plist file. That should work just fine.
This way you can avoid creating duplicate plist files, just for the sake of one or two different properties.

Github API Returns Empty Assets For Release Array

I am trying to track the download_count of a gitHub release via the gitHub api. I don't need much, I just want to see what it is.
I'm trying to get this information: http://developer.github.com/v3/repos/releases/#response
As you can see from the URL, the return includes a "download_count" key, but unfortunately, my assets array is entirely empty.
By Using This:
NSString * owner = #"...";
NSString * repo = #"...";
NSString * repoId = #"...";
// Get Release Info
NSString * releaseURL = [NSString stringWithFormat:#"https://api.github.com/repos/%#/%#/releases", owner, repo];
// Get Assets List Specifically
NSString * assetsURL = [NSString stringWithFormat:#"https://api.github.com/repos/%#/%#/releases/%#/assets", owner, repo, repoId];
NSURL * gitAssetsURL = [NSURL URLWithString:releaseURL];
NSData * gitAssetsRawData = [NSData dataWithContentsOfURL:gitAssetsURL];
NSString * gitDataString = [[NSString alloc] initWithData :gitAssetsRawData encoding:NSASCIIStringEncoding];
NSLog(#"%#", gitDataString);
Everything outputs fine, but my assets array is always empty. I do have releases, and the assets array is empty on all of my repositories.
Answered by Ivan in Comments, but just to close the question,
Ivan - "Assets are binaries you can upload when creating a release. Try creating a release and look for the "Attach binaries by dropping them here or selecting one." text. We track download counts only for those assets, not for releases themselves (currently)."
#Logan's answer is correct, but I think it should be noted as well that if you're seeing an empty list but did download some files into the release, that you should make sure you used the binaries download. I read his answer and thought that I had done everything correctly.
It turns out there are two sections in the UI, and I missed the binaries one completely. There's a larger (body) section, where you can add files, and right under that, there's a smaller binaries upload section. That was what I missed when I couldn't figure out why my assets were only showing up in the json under "body."

Invalidating QLPreviewController "cache"

QLPreviewController seems to cache file contents based on the local file's URL. In my application, the file contents can be updated remotely and would cause the new contents to be downloaded.
If I view a file in QLPreviewController, update it remotely, then re-preview it, the file does not show up as updated.
The file is definitely updated on disk, and other controls show the correct updated file.
The workaround I'm using right now is to basically move a file when it's previewed to a unique filename (using timestamp), which will of course not be in the QLPreviewController's cache. However, this has other repercussions, for example, if the app is killed or it crashes (god forbid), I won't know "where" to find the downloaded file.
I'm looking for less invasive hacks, or solutions to making QLPreviewController refresh its cache. The APIs don't seem to expose anything, so don't be afraid to submit a hack if it's less gross than the one I've presented above (not including copying/moving the file to a guaranteed unique URL, which I am already utilizing).
Just ran into this issue myself. I solved it by recreating the QLPreviewController each time I reload an item with the same name as the currently viewed item. Creating a new QLPreviewController clears the cache.
I know this is an old question but someone might have the same problem and find this answer helpful.
You should use refreshCurrentPreviewItem after downloading complete
I had the same problem. Opening a locally generated CSV file.
I have my _previewController* setup as a #property of my controller. Then what i did:
- (void)viewDidLoad
{
[super viewDidLoad];
self.previewController = [[QLPreviewController alloc] init];
_previewController.delegate=self;
_previewController.dataSource=self;
}
- (void)previewCSV
{
[_previewController reloadData]; // this triggers a reload
[self presentModalViewController:_previewController animated:YES];
}
IN other solution that comes to mind (not tested).
Depending on your URL, you could add something like http://url?time=123456 to your URL. Like this you change the URL but without side effect. The time (or any other parameter) you can change on each request.
It's the ugliest bug in iOS. Cache management in iOS 5 and beyond. I think is the same reason that makes iCloud buggy, Share-at-Home crashing and so on. Bad cache managements and so worst synchronization systems.
Well, my solution for this was to store the download file in a folder and use the current date to name the folder. It is equivalent to #Rogier's solution, but this works always. You get a name for the folder, for example, with [[NSDate date] description]. Instead of saving the file replacing the old one, you delete previous file, delete previous folder and save new file in a new folder. It's working fine for me.
Just remove all files from tmp directory like this:
- (void)clearCache
{
NSString *tempPath = NSTemporaryDirectory();
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:tempPath error:nil];
NSFileManager *fileManager = [NSFileManager defaultManager];
for (int i = 0; i < [dirContents count]; i++) {
NSLog(#"Directory Count: %i", [dirContents count]);
NSString *contentsOnly = [NSString stringWithFormat:#"%#%#", tempPath, [dirContents objectAtIndex:i]];
[fileManager removeItemAtPath:contentsOnly error:nil];
}
}

Resources