Unable to enumerate groups/results while using ALAssetsLibrary in static library - ios

This question is kind of solved, but one more request remaining at the bottom of this question. And I will choose an answer after all.
I am creating a static library that is using AssetsLibrary framework. While I am using it in a project to test whether is work. It return nothing while I call the enumerateGroupsWithTypes:usingBlock:failureBlock: method of the instance of AssetsLibrary.
I have trying -
to set breakpoints to see how it running this method. Turns out it did not go into the block that passing to usingBlock:, which is ALAssetsLibraryGroupsEnumerationResultsBlock, and either the failureBlock:. So that I got nothing.
to add the same enumerating code to the project I mentioned at the beginning to try to calling method of AssetsLibrary. it worked perfectly fine.
to test whether it is block by the main thread, then run it in the main thread. Got the same result as before.
For this issue, I have found an answer of other question that is talking about using media in the static library: https://stackoverflow.com/a/15331319/1583560, and I am not sure I have run into the same situation, is the media he/she mentions including accessing AssetsLibrary, I guess no here.
Hope someone can point some directions of this, thank you :)
Update
This is the code I used -
[[[ALAssetsLibrary alloc] init] enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
NSLog(#"%s",__PRETTY_FUNCTION__);
} failureBlock:^(NSError *error) {
NSLog(#"%s",__PRETTY_FUNCTION__);
}];
Code in the static library is the same as in the test project, the only difference is I make a model class to access AssetsLibrary in the static library.
To be clear, here are few changes I have made in the static library -
Change Build Product Path in Target > Build Settings to $(PROJECT_DIR)/../build
Moving required header file to Project section in Target > Build Phases > Copy Headers
Set Skip Install to YES in Target > Build Settings
Environment related -
OS X 10.9.1
Xcode 5.0.2
Standard architectures (including 64-bit) (both static library and the project)
ARC
More details
Here is my propose to make a assets model for easy accessing in this static library.
Having a group array to store all the albums, which is ALAssetsGroup here, in devices.
Enumerating albums out at the init of this model and storing into the group array.
Enumerating photos, which is ALAssets result, by the group given while needed.
And this model using singleton pattern.
BTW, I have tried to observe the ALAssetsLibraryChangedNotification in this static library. It's not working, too. Are there any potential obstructs at the front of AssetsLibrary?
Update
I have find out that I enumerate the groups while init the model I created. And there are threads make blocks not work. If I trigger the enumerate after the init complete, will work perfectly. And I also found the way to know when it is done enumerating (cf. https://stackoverflow.com/a/13309871/1583560) to get the group array which I stored.
Further, I still cannot find the document, Apple's, that addressing the threading of block, why it will not been call while init, yet. If someone could point out for me, I will appreciate it :).

This is not about "unable to enumerate assets on devices", because of the async of enumerating ALAssetsGroup.
The Async
According to apple's document about enumerateGroupsWithTypes:usingBlock:failureBlock: of ALAssetsLibrary, this is an async method, will not get full data stored in array after it run.
This method is asynchronous. When groups are enumerated, the user may be asked to confirm the application's access to the data; the method, though, returns immediately. You should perform whatever work you want with the assets in enumerationBlock.
Completion Notification
I do want to know when it is done. so that I have found an answer for knowing when the progress is done, and execute a block (cf. Create my own completion blocks in iOS). Even though this is not about the notification, but still gave me a hint to work it out.
postNotification: while it reach the end of the enumeration
[_assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if(group){
// add into the array you are going to use
}
else
{
// the last one will be nil,
// it also means the enumeration is done
// post notification then
[[NSNotificationCenter defaultCenter] postNotificationName:kEnumerateGroupCompleteNotification];
}
}
failureBlock:nil]; // leave nil here for make subject out
addObserver:selector:name:object: to add observer for reloading data
First, using the empty NSArray, which is the retained array we are using at previous step, in the ~DataSource protocol of UICollectionView or UITableView.
Second, adding an observer to the UIViewController, using #selector pass to this method to trigger the instance's reloadData to get the complete array just enumerating out. And the data will be shown on the view.
// In a UIViewController's implementation
- (void)viewDidLoad{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reloadTableView:)
name:kEnumerateGroupCompleteNotification
object:nil];
}
-(void)reloadTableView:(NSNotification*) notification {
// reloadData after receive the notification
[_tableView reloadData];
}
Conclusion
This is the way how I achieve my requirement. I have addressed ALAssetsGroup only above, but it will be the same in ALAssets's enumeration.
It is the same way if you want to get ALAssetsLibraryChangedNotification and reload data.
And a great thank for Ramshad's answer, I have accessing ALAssetsLibrary as your suggestion in my static library now.

Add the following method to your class:
+ (ALAssetsLibrary *)defaultAssetsLibrary
{
static dispatch_once_t pred = 0;
static ALAssetsLibrary *library = nil;
dispatch_once(&pred, ^{
library = [[ALAssetsLibrary alloc] init];
});
return library;
}
Then modify your code as
ALAssetsLibrary *assetsLibrary = [ViewController defaultAssetsLibrary];
[assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop)
{
[group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop)
{
if(result)
{
NSLog(#"Success");
}
}];
} failureBlock:^(NSError *error)
{
NSLog(#"Error loading images %#", error);
}];

Related

How to invoke action extensions?

From the iOS SDK docs, an Action extension might help users edit an image in a document that they’re viewing in a text editor. However I've been trying to google for examples how to do this and can only find articles on how to create app extensions and not how to use them in an app.
Suppose that I'm writing a word processing application for iOS. Picture the user having an embedded image in the app and wants to edit the image (e.g. apply a photo effect). How can the application provides the image to whatever image editing applications that the user has installed in the system, let it do it's thing, and then takes in the result?
I'd imagine the interaction style is pretty similar to LinkBack on the Mac. Except that the image editor is an app extension and displayed as a modal dialog (as per the SDK guide).
However I couldn't find any code example that shows:
How to provide input data (e.g. image) to the action extension.
How to invoke the action extension.
How to get back the output data from the action extension (including whatever additional metadata or editing information that the extension generates).
How to display the output data in a recognizable format (e.g. if I gave out a JPEG, I'd expect another JPEG will be given by the action extension as a result).
In the mean time I've figured out how to do this. It revolves primarily around UIActivityViewController and providing a callback block to the controller to receive the results from the action extension.
The example below works with Skitch as the test action extension.
// get the source image
UIImage* image = ...;
NSArray* activityItems = #[image];
UIActivityViewController* activityCtrl = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil];
activityCtrl.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
[returnedItems enumerateObjectsUsingBlock:^(NSExtensionItem* _Nonnull item, NSUInteger idx, BOOL * _Nonnull stop1) {
[item.attachments enumerateObjectsUsingBlock:^(NSItemProvider* _Nonnull attachment, NSUInteger idx, BOOL * _Nonnull stop2) {
if ([attachment hasItemConformingToTypeIdentifier:(__bridge id)kUTTypeImage]) {
[attachment loadItemForTypeIdentifier:(__bridge id)kUTTypeImage options:nil completionHandler:^(UIImage* returnedImage, NSError * _Null_unspecified error) {
// returnedImage is the result of the action extension
}];
*stop1 = YES;
*stop2 = YES;
}
}];
}];
};
// assume that `self` is a UIViewController
[self presentViewController:activityCtrl animated:YES completion:nil];
Unfortunately action extension providers are a bit rare, hence I couldn't test out how it actually interacts with other action extensions that can produce images.

How to identify photo with the unique field like checksum(md5sum) in IOS

I am creating an IOS app which can hide the photos the user been selected. I am able to do that, but the problem is, if the user selects again the already been selected photo, how do i need to check they are SAME ,i looked into the metadata info of the photo, but that can match to some other photo having same meta info, currently i have only idea to generate the checksum based on whole photo data with bytes, which i think may cause memory issue and also degrades performance.so any expertise help will be needful.thanks in advance.
__block NSMutableDictionary *imageMetadata = nil;
ALAssetsGroupEnumerationResultsBlock assetsEnumerationBlock = ^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) {
[self.assets addObject:result];
NSDictionary *metadata = result.defaultRepresentation.metadata;
imageMetadata = [[NSMutableDictionary alloc] initWithDictionary:metadata];
NSLog(#"%#",imageMetadata.description);
}
};
Seems like you could be handling image-identification at the front end.
If self.assets is an array, and even if your hiding photos while in a tableView (which would change the indexPaths), why not use arrayIndex in another array that houses all, lets say.. NSNumbers of hidden self.assets indexes.
But this is only after you exhausted all forms of Metadata identifiers? perhaps a combination of metadata-strings could yield something unique?
How are you dealing with this in the UI; a TableView?
I think, you can make use of ALAssetPropertyURLs, which maps asset representations UTIs to URLs that uniquely identify the asset.
To get this, use result.defaultRepresentation.url
The url should be like assets-library://asset/asset.JPG?id=1000000477&ext=JPG:

Is ALAssetsLibrary thread safe (Deadlock occurs when using multiple threads)

I'm currenly working on a tiny project which amis to loading ALL gallery photos into my app to show some fancy effect. Unfortunately, these default thumbnail provided by system cannot meet my requirement. So I try to create my own thumbnails using "fullScreenImage". To speed up the process, I load fullScreenImage using background operations. The main methods are:
- (void)getFullScreenImage:(NSURL *)url success:(void(^)(UIImage *))callback
{
NSLog(#"Requesting %#", url);
[assetsLibraryInstance assetForURL:url resultBlock:^(ALAsset *asset) {
callback(asset.defaultRepresentation.fullScreenImage);
}
failureBlock:nil];
}
- (void)processURLs:(NSArray *)urls
{
for (NSURL *url in urls) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) ^{
[self getFullScreenImage:url success:^(UIImage *img) {
NSLog(#"Got image %#", img);
}] ;
});
}
}
Only the "Requesting..." log is printed in console, the "getFullScreenImage" method is locked, no any output.
I tried below methods to work around this issue:
Not sharing assetsLibraryInstance (Didn't work)
Don't dispatch_async when enumeate urls in "processURLs". (Did work, but I don't want to use a signle thread to process all URLs)
Not using global queue, using main queue (Did work, but all these "fullScreenImage" works were doing on UI thread, making UI non-responsive)
Using a private queue created with "dispatch_queue_create". (Didn't work)
So, is ALAssetsLibrary thread safe? I guess it's not... Or, is there any better way I can use to:
Load fullScreenImage in background
Multiple threading
Thanks!

UIDocument open/close behavior

I have a UIDocument based app, without iCloud support. The user is able to create documents, and save them to app's Document directory.
I have also created few "sample" documents, and passing them with the app's bundle. I want the user to be able to open sample documents the same way as they can open their own documents:
NSArray *samplesContent = [[NSBundle mainBundle] URLsForResourcesWithExtension:#"doco" subdirectory:#"Samples"];
for (NSURL *sampleURL in samplesContent) {
Doco *doco = [[Doco alloc] initWithFileURL:sampleURL];
[doco disableEditing];
[doco openWithCompletionHandler:^(BOOL success) {
if (success) {
[self.docos addObject:doco];
if (self.docos.count >= samplesContent.count) {
[self.tableView reloadData];
}
} else DLog(#"Failed to open %#", [sampleURL pathExtension]);
}];
}
What I am seeing is this:
On viewDidLoad, docs array gets populated with sample docs and displayed in the tableView the first time app launches. No issues here, all is smooth and nice.
Then I back out of the view with the following code to close all open docs:
int count = 0;
for (Doco *doco in self.docos) {
if (doco.documentState == UIDocumentStateNormal) {
[doco closeWithCompletionHandler:nil];
count++;
}
}
DLog(#"Closed %i docs", count);
When I open the view again, the array of docs should get populated again and tableView re-populated, but nothing happens.
The completion handler below never gets called, although the URL is pointing to the same file and it is valid:
[doco openWithCompletionHandler:^(BOOL success) {}
I do not have this issue for user generated docs stored in Documents, so my assumption is that it has something to do with auto-save, that gets called on read-only bundle and fails
But I am sort of stuck on this part, any help will be appreciated.
The problem has already been identified, but I think it's worth describing a couple of simple solutions since including sample documents in an app's bundle is not uncommon.
So the problem is that the sample document is trying to save changes when it is closed, but saving cannot succeed in a read-only app bundle.
I think there are two main solutions here:
Copy the sample document into the Documents directory, where it can be treated like any other document and saved successfully (if you want user edits to the sample document to be saved, use this approach).
Prevent the document from trying to save (for read-only sample documents).
So here are some simple examples...
1. Copy sample documents into Documents directory
On first launch (or indeed whenever you decide to 'refresh' the sample document), use NSFileManager to copy the file into place:
- (void)refreshSampleDocuments
{
NSArray *sampleFromURLs = [[NSBundle mainBundle] URLsForResourcesWithExtension:#"doc" subdirectory:#"Samples"];
for (NSURL *sampleFromURL in sampleFromURLs) {
NSString *sampleFilename = [sampleFromURL lastPathComponent];
NSURL *sampleToURL = [[self documentsDirectoryURL] URLByAppendingPathComponent:sampleFilename];
// ...
// Do some checks to make sure you won't write over any user documents!
// ....
NSError *error;
BOOL copySuccessful = [[NSFileManager defaultManager] copyItemAtURL:sampleFromURL toURL:sampleToURL error:&error];
if (!copySuccessful) {
// Handle error...
}
}
}
2. Prevent sample documents from trying to save
This approach is much simpler (for read-only documents), and easier than trying to prevent updates wherever they might occur in the document.
When closeWithCompletionHandler: is called on UIDocument, autosaveWithCompletionHandler: is invoked to ensure the document file is saved before closing. This in turn invokes hasUnsavedChanges to decide whether a save is necessary. So if hasUnsavedChanges returns NO, then any invocation of the autosaving mechanism will result in no changes being written out.
(N.B. manually calling saveToURL:forSaveOperation:completionHandler: will still force a save, regardless of what hasUnsavedChanges returns.)
So in your UIDocument subclass, override hasUnsavedChanges to return NO if the document is read-only.
#interface MyDocument : UIDocument
#property(nonatomic, getter = isReadOnly) BOOL readOnly;
#end
#implementation MyDocument
- (BOOL)hasUnsavedChanges
{
return [self isReadOnly] ? NO : [super hasUnsavedChanges];
}
#end
If UIDocument is updated it will try to save changes on close.
Since UIDocument was loaded from read-only bundle, I had to make sure that it does not get updated, otherwise close block returns success=NO, and document is not closed...

iCloud - renaming open documents on another device sometimes fails

The issue: I'm working on an iCloud document on device A, e.g. iPod Touch. Then I change the name of the document on device B, e.g. my Mac (via the Finder). The change goes up to the cloud and after a pause device A gets to hear about it.
And then:
some of the time all is just fine - I pick up the change of name via a changed fileURL property and can update my interface accordingly - the document continues to behave just as it should
some of the time, the document's fileURL is returned as something such as: file://localhost/var/mobile/Library/Mobile%20Documents/.ubd/peer-43A0AEB6-84CE-283E-CA39-FCC4EF3BC8F8-v23/ftr/purg-012fdcfbe3b3bbce6e603fdfd2f000b2cb28649e95 Not surprisingly this file won't save.
Can anyone explain what is going on and how to work around it?
Background
The name change is picked up fine by by NSMetadataQuery. So, for e.g., I can rename documents that are not open and all my iCloud functionality works fine. The issue only seems to occur with open documents.
Other iCloud features are working fine, e.g. I can change content on one device, e.g. my Mac, and detect and then update my interface on another device, e.g. my iPod Touch, that has the relevant iCloud document open.
I first spotted this when I added an override for presentedItemDidMoveToURL: to my UIDocument subclass. The override reliably picks up name changes made in the cloud, e.g. renaming the document on another device. Then sometimes newURL is the final expected URL for the renamed document, i.e. something sensible from which I can extract the new name use `lastPathComponent', update my interface etc. On other occasions newURL is a document in some other directory with a last path component beginning 'purg-', e.g. purg-012fdcfbe3b3bbce6e603fdfd2f000b2cb28649e95.
- (void) presentedItemDidMoveToURL:(NSURL *) newURL;
{
[super presentedItemDidMoveToURL: newURL];
if ([(id)[self delegate] respondsToSelector:#selector(documentNameChanged:)])
{
[[self delegate] documentNameChanged: self];
}
}
The presentedItemDidMoveToURL: method does not seem to be the root cause of the problem. For example, if I don't override that method at all, but periodically check in the viewController that is looking after the open document, then sometimes after a rename fileURL will return the new name and sometimes it will return `purg-.....'. So the issue appears to be to do with how renaming is handled.
Update
As al_lea pointed out, the issue here was related to accommodatePresentedItemDeletionWithCompletionHandler:. Expanding on al_lea's answer, I added the code below to my UIDocument subclass. This fixed the issue.
- (void) accommodatePresentedItemDeletionWithCompletionHandler: (void (^) (NSError *errorOrNil)) completionHandler
{
PresentedDocument* presentedDocument = [self retain];
[presentedDocument closeWithCompletionHandler: ^(BOOL success) {
NSError* error = nil;
if (!success)
{
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
#"Could not close document that is being deleted on another device",
NSLocalizedDescriptionKey, nil];
error = [NSError errorWithDomain: #"some_suitable_domain"
code: 101
userInfo: userInfo];
}
completionHandler(error); // run the passed in completion handler (required)
dispatch_async(dispatch_get_main_queue(), ^
{
[[NSNotificationCenter defaultCenter] postNotificationName: NOTIFY_presentedDocumentDeletedOnAnotherDevice
object: presentedDocument
userInfo: nil];
[presentedDocument tidyUpAfterDelete]; // app specific tidy up
[presentedDocument release];
});
}];
}
With this code in place, there are no spurious and confusing presentedItemDidMoveToURL: calls made and, in addition, the relevant object can listen for notifications of deletions on other devices.
This type of URL appears when a UIDocument is opened on local and get deleted from a remote device:
file://localhost/var/mobile/Library/Mobile%20Documents/.ubd/peer-43A0AEB6-84CE-283E-CA39-FCC4EF3BC8F8-v23/ftr/purg-
You need to close the document first before it get deleted - detect this in NSFilePresenter's accommodatePresentedItemDeletionWithCompletionHandler:

Resources