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.
Related
Let's say I have 3 async methods which all do the same thing: send some data to a server and add the response data to an array (all of them add to the same array).
I call these methods at the same time, but they send different amounts of data to the server.
If the first method sends more data to the server than the third method, the third method will get the response back sooner, thus add the data to the array sooner. The response data consits of coordinates, so the order in which they are in the array matters.
How can I make sure, that even if the third method gets the repsonse sooner than the first or the second one, it won't add the data to the array before the previous methods did? Thus, preserving the order of the coordinates in the array.
The methods are NSURLConnections and they all send an async request.
EDIT: Here is the working code:
//Array of snapped points from the JSON
NSArray *snappedPoints = [result objectForKey:#"snappedPoints"];
NSMutableArray *locationsArray = [[NSMutableArray alloc] init];
//Loop through the snapped points array and add each coordinate to a temporary array
for (int i = 0; i<[snappedPoints count]; i++) {
NSDictionary *location = [[snappedPoints objectAtIndex:i] objectForKey:#"location"];
double latitude = [[location objectForKey:#"latitude"] doubleValue];
double longitude = [[location objectForKey:#"longitude"] doubleValue];
CLLocation *loc = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude];
[locationsArray addObject:loc];
}
//Add these temporary location arrays to the dictionary with the key as the request number
[tempLocationDictionary setObject:locationsArray forKey:[NSString stringWithFormat:#"%i",requestNumber]];
//If all requests have completed get the location arrays from the dicitonary in the same order as the request were made
if([tempLocationDictionary count] == numberOfRequests){
//Just a path because I am drawing these coordinates on a map later
GMSMutablePath *path = [GMSMutablePath path];
//Loop through the dictionary and get the location arrays in the right order
for (int i = 0; i<[tempLocationDictionary count]; i++) {
//Create a dummy array
NSArray *array = [tempLocationDictionary objectForKey:[NSString stringWithFormat:#"%i",i+1]];
//Get the coordinates from the array which we just got from the dictionary
for (CLLocation *location in array) {
[path addCoordinate:location.coordinate];
}
}
One way would be to replace NSURLConnection with NSURLSession and its related classes. It's newer and generally a superior choice. For your specific situation, it's possible to use a custom NSURLSessionConfiguration where you would set HTTPMaximumConnectionsPerHost to 1. That way the connections would be forced to run in order, solving your problem.
Of course, not having simultaneous connections might slow things down. In that case you'll have to accumulate the data temporarily without adding it to your array and only update the array when all the connections are complete. There are various ways you might do this depending on exactly what data comes back from the server.
One relatively simple way: If you know you'll always have three connections, use an NSMutableDictionary with integer NSNumber objects as the keys. After each connection you'd do something like
mutableDictionary[#1] = // your server data here
Use #2 or #3 for the other connections. Every time you add data, check to see if you have results for all three, and if so, add everything to your array. There are lots of other ways around this, the key is to have some kind of temporary structure where you can (a) accumulate data until all the connections are complete, and (b) keep track of which data came from which connection, either simply by number, or by URL, or by some other unique data that the server provides.
This is a problem to be solved with NSOperationQueue and dependencies.
Check out Advanced NSOperations [transcript] from WWDC 2015:
When the WWDC app starts up, there's a bunch of setup that we need to do.
First, we are going to download a small configuration file, and this file will tell us small things like what's the most recently supported version of the application, what features we have enabled, and so on.
So after we download this file, we are going to perform a version check to make sure that you are running the latest version of the WWDC app.
And then after we check the version of the app, we can start downloading useful pieces of information, such as the news that we show in the News tab and the schedule for the conference.
After we've downloaded the schedule, then we can start importing any favorites that you've saved to iCloud, any feedback that you've submitted so you can see it in the app, and we are also going to start downloading the list of videos.
Applying dependencies as they describe for these operations should sort you nicely.
Is there anyway I can use the Path returned from the "PHImageFileURLKey" to go into the photo library and retrieve the image?
The path returned is in this format:
"file:///var/mobile/Media/DCIM/102APPLE/IMG_2607.JPG"
My plan is to store this path in the database and use it to fetch the image when I need to get it back.
Any help is appreciated. Thank you!
I think your solution of retrieving Photo Kit asset from the URL is wrong.
Here is what I would do (supposing you have access to PHAsset):
Store the localIdentifier:
PHAsset *asset = /*Your asset */
NSString *localIdentifier = asset.localIdentifier;
//Now save this local identifier, or an array of them
When it is time to retrieve them you simply do:
PHFetchResult *savedAssets = [PHAsset fetchAssetsWithLocalIdentifiers:savedLocalIdentifiers options:nil];
[savedAssets enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop) {
//this gets called for every asset from its localIdentifier you saved
}];
If you only have access to “PHImageFileURLKey” then disregard this answer.
This isn't documented, so I'd strongly advise against using that URL for anything more than a prototype app. That said, this does appear to work:
dispatch_queue_t queue = dispatch_queue_create("photoLoadQueue", 0);
dispatch_async(queue, ^{
NSURL *privateUrl = [NSURL URLWithString:#"file:///var/mobile/Media/DCIM/102APPLE/IMG_2607.JPG";
NSData *imageData = [NSData dataWithContentsOfURL:privateUrl];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = [UIImage imageWithData:imageData];
});
});
Naturally you'll need to replace the string used to initiate the url with one which is valid for your phone.
There are probably a load of issues with doing this - it's just not how the framework is meant to be used. Here are some off the top of my head:
When running in the simulator, the root path changes regularly between launches of the app, so if you store absoluteUrls like this your database will quickly become full of dead URLs. This will be inconvenient to say the least.
Worse, the URLs for the images may change on a real device - you don't have control over it, and once they change it's your app's fault for making the user reselect them or whatever.
You're not going to ever find out about changes to the PHAsset which the photo came from.
This may be circumventing user permission for photo access - what happens if your app's permission to access photos is revoked? This is probably an issue with lots of approaches to storing photos for later use, however.
You don't control the file - what if the user deletes it?
If I were you, I would retrieve the image properly from the photos framework, using PHImageManager requestImageForAsset: targetSize: contentMode: options: resultHandler:, and store it in a file within your app's directory, at a sensible resolution for whatever you're doing with it. This still doesn't give you asset changes, but is a pretty good solution.
If you want to store the assets themselves and only request the images when you actually need them, it might be worth investigating transient asset collections, though I've not used them so that might not work for what you need.
I am doing an iOS app. The goal is to send suggestions from user to improve the service I propose.
The app is a native app. I want to create a global NSMutableArray where I store all the suggestions. The problem is my device can be not connected to internet.
There is two cases:
If the device is connected when the user send his suggestion, it is send.
If not, the suggestion is saved in my NSMutableArray and send later.
Is it possible to create a global variable which store all my suggestions ? If my app is crashed/closed this variable can't be dealloc/reset.
Is a singleton enough to do this ?
first check for internet status of your device.if status is connected then send the array.if status in not connected then save the array into NSUserDefaults.To save suggestions array
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray * suggestions = [[NSMutableArray alloc] init];
[defaults setObject: suggestions forKey:#"suggestions"];
to Access those suggestions
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray * suggestions = [[NSMutableArray alloc]initWithArray:[defaultsobjectForKey:#"suggestions"]];
A singleton is not enough because you are going to lose all your data if your app is killed or crashes.
If there are only a few suggestions and they are short, the easiest approach would be to save them in your UserDefaults.
If you are going to save too much suggestions with long texts and several fields you will need to implement a database. Core Data would be a good solution for your problem.
An easy (single line of code) way to do this is to use plists and the command
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
on an NSArray
From the description:
Writes the contents of the array to a file at a given path.
If the array’s contents are all property list objects (NSString, NSData, NSArray, or NSDictionary objects), the file written by this method can be used to initialize a new array with the class method arrayWithContentsOfFile: or the instance method initWithContentsOfFile:. This method recursively validates that all the contained objects are property list objects before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.
In your example you would have (say) an NSArray of NSDictionaries with each NSDictionary containing the suggestions. Calling writeToFile would write it out in the event of no network availability. Then (say on app foreground event) you would read the file (arrayWithContentsOfFile call). If there is any content you would try to push to the server again.
You can use NSUserDefaults.
//write to NSUserDefaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[NSNumber numberWithInteger:userRatingInteger] forKey:#"userRating"];
[defaults synchronize];
//read from NSUserDefaults
NSNumber *userRatingNumber = [defaults objectForKey:#"userRating"];
if (userRatingNumber) { // the user has rated.
// Use the value as needed.
}
I have the first part figured out...
- (void)webViewDidFinishLoad:(UIWebView*) myTapView {
NSString *jsString = #"localStorage.getItem('username');";
NSString *someKeyValue = [tapView stringByEvaluatingJavaScriptFromString:jsString];
// This is where I would start storing it so I can retrieve it later
}
So here is my issue, I need to store the value I just got from localStorage so I can use it again. Problem is I don't know where to start. This code is inside my ViewController for the WebView, so I need to be able to use it in other areas in the ViewController.m file.
What is the best method for storing the info? I would also want the above code to be able to update the value, as it won't always be the same username (people can switch accounts).
There are a few ways to store data on iOS, but it depends on what kind of value you want to store. If it's just a plain string or other "light" data, the best approach is to save it to NSUserDefaults. If it's an image or video, you may want to consider archiving it using NSKeyedArchiver. Otherwise, if you want to build a complicated data model, use Core Data.
Here's a good post on NSHipster that describe in detail pros and cons of each approach.
According to your code, you want to store a string. I'd go with NSUserDefaults in this case.
Use NSUserDefaults to store UIWebView local storage value as follows
- (void)webViewDidFinishLoad:(UIWebView*) myTapView {
NSString *jsString = #"localStorage.getItem('username');";
NSString *someKeyValue = [myTapView stringByEvaluatingJavaScriptFromString:jsString];
// Store your Username in iPhone persistence variable and use it later in the application
NSUserDefaults *userdefault = [NSUserDefaults standardUserDefaults];
[userdefault setObject:someKeyValue forKey:#"username"];
[userdefault synchronize];
//use of User default
NSLog(#"User name %#",[userdefault valueForKey:#"username"]);
}
I am trying to build a nice function to access the network for images, if they are found on the web, I store them in a cache system I made.
If the image was already stored on the cache, I return it.
The function is called getImageFromCache and returns an image if it is in the cache, else, it would go to the network and fetch.
The code might look like this:
UIImageView* backgroundTiles = [[UIImageView alloc] initWithImage[self getImageFromCache:#"http://www.example.com/1.jpg"]];
Now, I am moving on to using threads because of big latencies due to network traffic. So I want images to show a temp image before I get the result from the web.
What I want to know is how can I keep track of so many images being accessed sequentially, being added to UIImageViews by this function (getImageFromCache).
Something just won't work there:
-(UIImage*)getImageFromCache:(NSString*)forURL{
__block NSError* error = nil;
__block NSData *imageData;
__block UIImage* tmpImage;
if(forURL==nil) return nil;
if(![self.imagesCache objectForKey:forURL])
{
// Setting a temporary image until we start getting results
tmpImage = [UIImage imageNamed:#"noimage.png"];
NSURL *imageURL = [NSURL URLWithString:forURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
imageData = [NSData dataWithContentsOfURL:imageURL options:NSDataReadingUncached error:&error];
if(imageData)
{
NSLog(#"Thread fetching image URL:%#",imageURL);
dispatch_async(dispatch_get_main_queue(), ^{
tmpImage = [UIImage imageWithData:imageData];
if(tmpImage)
{
[imagesCache setObject:tmpImage forKey:forURL];
}
else
// Couldn't build an image of this data, probably bad URL
[imagesCache setObject:[UIImage imageNamed:#"imageNotFound.png"] forKey:forURL];
});
}
else
// Couldn't build an image of this data, probably bad URL
[imagesCache setObject:[UIImage imageNamed:#"imageNotFound.png"] forKey:forURL];
});
}
else
return [imagesCache objectForKey:forURL];
return tmpImage;
}
This is not a direct answer to your question, but are you aware that there is no need to use GCD to download things asynchronously (on a background thread)? Just use NSURLConnection and its delegate methods. All your code will be on the main thread but the actual connection and downloading will happen in the background.
(And in fact I have written a class, MyDownloader, that takes care of all this for you:
http://www.apeth.com/iOSBook/ch37.html#_http_requests
Scroll down to the part about MyDownloader and its subclass MyImageDownloader, which is doing exactly the sort of thing you need done here. Moreover, note the subsequent code in that chapter showing how to use a notification when a download completes, prompting the table view that need these images to reload the row that contains the image view whose image has just arrived.)
its good your building it from scratch but if you want to save the all the work, there's a drop in Replacement SDWebImage Library with support for remote images coming from the web, and has all the functionality Like Temp Image, Asychronous Loading, Caching etc, you said you need
In your background thread, once the download has completed and you've saved the image to the cache, I'd suggest you post a notification using the NSNotificationCenter to let other parts of your app know that the cache has been updated.
This assumes that whichever part of the app manages the image views has registered its interest in those notification with the addObserverForName method. When it receives such a notification, it can then attempt to retrieve the images from the cache again and update its image views if appropriate.
Depending on the number of image views, you may want to pass through the image url in the notification in some way (e.g. in the userInfo dictionary), and then based on that decide which image views should be refreshed rather than refreshing them all.
I should add that I would also recommend getting rid of the inner dispatch_async call. There's no need for that, although you may need to add synchronisation to your cache object so it can be safely accessed from the main thread as well as the download thread.