Is there a way to get PHAssetResource for array of PHAsset? - ios

I'm trying to get all PHAsset from the library. However, when getting them, we need to get the PHAssetResource to get the fileSize.
When we have a small number of images, the array is returned in less than 30 seconds; however, when we have around 12k~ image, it's taking too long.
Is there any way to load the resources for all the assets by one call instead of calling the API several times ?
Example:
NSArray<PHAssetResource *> *const assetResources = [PHAssetResource assetResourcesForAsset: asset];
if (![assetResources firstObject]) {
return;
}
[resource valueForKey:#"fileSize"]
PS: I don't mind a solution in Swift.

Unfortunately there is no way. You always have to first get the PHAsset then query the PHAssetResources for this asset.
In theory there would be a way (the iOS Photo Library is CoreData database), but Apple has not included any API to make such a query.

Related

How to copy an AWS S3 file from one bucket to another using iOS SDK

I'm using the AWS iOS SDK and have been able to do the upload / download file operations as outlined in the tutorials. Now I'm trying to copy a file from one bucket and paste it into another. I own both buckets and have access to them. I also want to delete the file from the first bucket after copying it (so technically this is a cut-paste operation) but I'm assuming the way to do that is copy, paste, delete the original.
After some digging it seems like the way to do this is through the AWSS3 -uploadPartCopy: function. It seems like this function uses an AWSS3UploadPartCopyRquest object which has 3 relevant input properties, the destination bucket (bucket), the destination key (key) and the source location (replicateSource), which seems to be a URL for the location of the object to be copied.
This seems to me like a really strange format for such a function, and I'm also not familiar with what Uploading a part means, i.e. does this have to be part of a multi-part upload? Do I need to start a multi-part upload before calling uploadPartCopy?
I'm also not sure this is the way to go about this. It seems like an overcomplicated solution to a relatively simple task. Am I on the right track here?
Just Refer below code. It gives you idea in detail for copy data from one bucket to another. In my case i want to copy multiple images from same bucket.
NSString *sourceBucket = #"treedev1234";
NSString *destinationBucket = #"treedev1234";
AWSS3 *s3 = [AWSS3 defaultS3];
AWSS3ReplicateObjectRequest *replicateRequest = [AWSS3ReplicateObjectRequest new];
for(int i = 0;i<feedModel.imageCount;i++){
replicateRequest.bucket = destinationBucket;
replicateRequest.key = [NSString stringWithFormat:#"posts/%d/%d.jpg",newpostid,i];
replicateRequest.replicateSource = [NSString stringWithFormat:#"%#/posts/%d/%d.jpg",sourceBucket,oldpostid,i];
replicateRequest.ACL = AWSS3ObjectCannedACLPublicReadWrite;
[[s3 replicateObject:replicateRequest] continueWithBlock:^id(AWSTask *task) {
if(task.error)
NSLog(#"The share request failed. error: [%#]", task.error);
return nil;
}];
There are two operations to copy an object: PUT Object - Copy and Upload Part - Copy. If the object is not too large, "PUT Object - Copy", which is mapped to - replicateObject:, is easier to implement.
Also, Amazon S3 has the Cross-Region Replication feature, which automatically replicates objects if your two buckets are not in the same region.

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.

PhotoKit iOS8 - Retrieve image using the "PHImageFileURLKey"

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.

SPPlaylist items as SPTracks not loading (consistently)

I've been struggling with this for a while now and was looking around for some advice from anyone who's worked with CocoaLibSpotify on iOS (iOS 7 to be exact).
I'm trying to load all the (SPTrack) items in a SPPlaylist. I've looked though the example code and the documentation so I know the best way to observe things is with KVO, however I can't see how that's the best way here.
Looking at the "Guess the Into" example and other sources on GitHub and the web in general, the precess I'm doing is as follows:
Get the SPPlaylist (this is either directly from a the users playlists or created from a saved URL),
Load the playlist with SPAsyncLoading,
Loop though the SPPlaylistItems returned in the SPPlaylist and get all the SPTrack objects,
Pass the array of SPTrack objects into another SPAsyncLoading call,
Display all items from the loadedItems array.
Step 5 is where things seem to go wrong. Sometimes it is fine and I get all the tracks in the playlist displaying. However most of the time it doesn't load them, or only load a few (normally very little).
I've noticed that restarting the phone can sometimes make it work. Though after running the app (or changing the playlist) a couple of time, it stops working again.
When I log each step I get something like the following:
-[ViewController sessionDidLoginSuccessfully:]
Load playlist with URL: spotify:user:XXXXX:playlist:XXXXXXXXXXXXXXXXXXXXXX
Created playlist: <SPPlaylist: 0x16775bc0>: (null) (0 items)
Loaded playlist: <SPPlaylist: 0x16775bc0>: Playlist Name (59 items)
Created SPTrack items: 59
Loaded SPTrack items: 1
Failed to load SPTrack items: 58
As you can see, the user is logged on, with a valid session. Each step works according to plan until the end. Sometimes even if it does load a track, it won't allow me to play it anyway as well returning SP_TRACK_AVAILABILITY_UNAVAILABLE.
I've also added a code snippet below if it helps. But any help would be grateful at this point with this.
Thanks.
- (void)loadPlaylist
{
__weak NSURL *spotifyURL = <URL from saved SPPlaylist>;
NSLog(#"Load playlist with URL: %#", spotifyURL);
[SPPlaylist playlistWithPlaylistURL:spotifyURL inSession:[SPSession sharedSession] callback:^(SPPlaylist *playlist) {
_spotifyPlaylist = playlist;
NSLog(#"Created playlist: %#", _spotifyPlaylist);
[SPAsyncLoading waitUntilLoaded: _spotifyPlaylist timeout:kSpotifyTimeout then:^(NSArray *loadedItems, NSArray *notLoadedItems) {
NSLog(#"Loaded playlist: %#", _spotifyPlaylist);
if ([loadedItems count] > 0)
[self loadSpotifyPlaylist];
}];
}];
}
- (void)loadSpotifyPlaylist
{
NSArray *tracksArray = [self tracksFromPlaylistItems:[_spotifyPlaylist items]];
NSLog(#"Created SPTrack items: %d", [tracksArray count]);
if (tracksArray && [tracksArray count] > 0)
{
[SPAsyncLoading waitUntilLoaded:tracksArray timeout:kSpotifyTimeout then:^(NSArray *loadedItems, NSArray *notLoadedItems) {
_playlist = loadedItems;
NSLog(#"Loaded SPTrack items: %d", [loadedItems count]);
NSLog(#"Failed to load SPTrack items: %d", [notLoadedItems count]);
[self finishReloadData];
}];
}
_spotifyPlaylist and _playlist are both strong instances and tracksFromPlaylistItems: is the same method used in the "Guess the Info" sample code.
Edit: The kSpotifyTimeout timeout is set to 10.0.
What's your value of kSpotifyTimeout? I think what you're seeing is simply the fact that metadata can take a long time to load (especially yesterday when the Spotify backend seemed to be having trouble, which can happen from time to time).
Generally, loading the entire contents of a playlist at once is bad practice, and you'll see that Guess the Intro doesn't actually care whether all the tracks loaded or not - it just grabs what happened to load and uses those tracks for the game.
A good approach to take is to paginate loading of playlist tracks to match your UI. That is, to only load the tracks your user can see, plus maybe a "screen" or two's worth up and down. As the user scrolls, you can start loading tracks - UIScrollViewDelegates -scrollViewWillEndDragging:withVelocity:targetContentOffset: is particularly good for this, as it lets you see where the user will "land" and start to load tracks for that point. There's some example code for doing this as well as a helper class called SPSparseArray for partially loading content like playlists on the dev branch of CococaLibSpotify (although be aware that dev branch = beta quality).

Gracenote API - No track link data

Currently, in my AppDelegate, I have an instance variable declared for GNConfig that is set up with all properties I would like to receive.
This instance of GNconfig is used by any class that makes a gracenote request.
The requests I am using are recognition from an audio stream, recognition by local file and a text search which populates an array. The array is then used for track lookups by id for the corresponding array item.
I am able to get all the content I need, except for track and album link data (always returns null).
I have tried plenty of different suggestions and guides with no luck.
Could somebody please help me out? This data is essential to my app and my app is pretty much complete except for this big obstacle.
Thanks in advance.
** edit **
This is in my appDelegate:
_gnConfig = [GNConfig init:#"XXXXXXX-XXXXXXXXXXXXXXXXXXXXXXX"];
[_gnConfig setProperty:#"content.coverArt" value:#"1"];
[_gnConfig setProperty:#"content.coverArt.sizePreference" value:#"LARGE"];
[_gnConfig setProperty:#"content.allowFullResponse" value:#"1"];
I have this in one of my class methods:
NSURL *filePath = [item valueForProperty:MPMediaItemPropertyAssetURL];
[GNOperations recognizeMIDFileFromFile:self config:[[AppDelegate sharedDelegate] gnConfig] fileUrl:filePath];
In the delegate method I have:
gracenoteResponseItem = [result bestResponse];
NSLog("%#", [gracenoteResponseItem trackLinkData]);
Some tracks may not have link data available.
Also if you are doing a local lookup then you will have to set this config option:
[publicProperties setObject:#“1"forKey:#"content.allowfullresponse"];
Unless you have explicitly had your client ID entitled for Link IDs (aka 'external' or '3rd party' IDs), you won't get any in your responses.
By default, Gracenote Open Developer client IDs aren't entitled for any external IDs. You need to coordinate with Gracenote to entitle your client ID to start receiving the desired IDs.

Resources