So i have UICollectionView. I need to get all user photos from his photos library and when photo is loaded update my collection view. I tried using NSNotificationCenter. I added observer to UICollectionViewController and when i enumerate photos i post notification so i could update my collection view when photo us loaded. Here is my controller:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(phonePhotoLoaded:) name:#"PhonePhotoLoaded" object:nil];
[self.manager getPhonePhotos];
Selector method:
-(void)phonePhotoLoaded:(NSNotification *)userInfo
{
[self.collectionView performBatchUpdates:^{
NSDictionary *dic = [userInfo valueForKey:#"userInfo"];
[self.photos addObject:[dic valueForKey:#"photo"]];
[self.collectionView insertItemsAtIndexPaths:#[[NSIndexPath indexPathForItem:self.photos.count inSection:0]]];
} completion:^(BOOL finished){
}];
}
And in [self.manager getPhonePhotos]
ALAssetsLibrary *al = [PhotoManager defaultAssetsLibrary];
[al enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
usingBlock:^(ALAssetsGroup *group, BOOL *stop){
[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop){
if ([[asset valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto]) {
NSDictionary *userPhotos = [NSDictionary dictionaryWithObjectsAndKeys:asset, #"photo", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"PhonePhotoLoaded" object:self userInfo:userPhotos];
}
}];
}failureBlock:^(NSError *error) {
;}
];
This is what error i get:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'too many update animations on one view - limit is 31 in flight at a time (; }; layer = >)'
So as much as i understand, when i enumerate photos, i post notification, then notification method is executed but UICollectionView is not making animations. He somehow starts them when i finish enumerating. So how could i update my UI when photo is fetched? Any ideas?
Don't post a notification on each iteration of the photo enumeration (each time your block is called). Instead, build up an array of all of the assets and post it on the last call of the block (IIRC, the asset will be nil on the last call, need to check the docs).
Your current problem is that you are starting lots of animations by posting a notification each time and you should really collate all of your changes and then execute a single performBatchUpdates.
Related
Using Crittercism with some beta testers I am seeing an error appear several times that I've never experienced myself and I am unable to replicate.
Crittercism tells me:NSInternalInconsistencyException accessing _cachedSystemAnimationFence requires the main thread
And the line it is pointing to is:
[picker dismissViewControllerAnimated:YES completion:^{
Doing some reading on StackOverflow it appears that any UI code should be ran on the main thread. Is the error I am experiencing because the dismissViewControllerAnimated has been ran on a background thread?
Curious why this error is relatively random (i.e. I cannot reproduce it) and also how do I fix it.
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
__block PHObjectPlaceholder *assetPlaceholder;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetChangeRequest *changeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:[info objectForKey:#"UIImagePickerControllerOriginalImage"]];
assetPlaceholder = changeRequest.placeholderForCreatedAsset;
} completionHandler:^(BOOL success, NSError *error) {
NSArray *photos = [[NSArray alloc] initWithObjects:assetPlaceholder.localIdentifier, nil];
PHFetchResult *savedPhotos = [PHAsset fetchAssetsWithLocalIdentifiers:photos options:nil];
[savedPhotos enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop) {
NSMutableArray *images = self.event.eventAttachments;
if (images) {
[images addObject:asset];
} else {
images = [[NSMutableArray alloc]init];
[images addObject:asset];
}
self.event.eventAttachments = images;
[picker dismissViewControllerAnimated:YES completion:^{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:4];
NSArray *indexPaths = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tblChildrenNotes beginUpdates];
[self.tblChildrenNotes reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[self.tblChildrenNotes endUpdates];
}];
}];
}];
}
If we need to call any UI Related operation inside the block use dispatch.
Everything that interacts with the UI must be run on the main thread. You can run other tasks that don't relate to the UI outside the main thread to increase performance.
dispatch_async(dispatch_get_main_queue(), ^{
// Call UI related operations
});
I'm afraid I don't have a solution but I can offer the details of an app that is crashing the same way. We're using the Cordova platform to build our app. This crash only seems to happen in iOS9 (on an iPhone 6 in our case) and only when the user has Swype keyboard installed. We see the crash when opening/closing the keyboard to type in a text field displayed through Cordova's InAppBrowser plugin, and only sometimes. Deleting the Swype app makes the bug go away. Unfortunately, since we didn't write any of the objc involved in the app, we're having a tough time debugging. Good luck!
For Swift 3
DispatchQueue.main.async(execute: {
//UI Related Function
});
For me this solution fixed when trying to open pdfViewer when in landscape mode.
Check to make sure that your completionHandler is actually getting called back on the main thread and not some other thread. That is typically the cause for this particular issue.
When I receive data from a socket and pass the data to another VC via NSNotificationCenter, the passed object always logs (null), despite the object being present in the other class.
there is where I pass the data through.
UPDATED:
-(void) initSIOSocket {
[SIOSocket socketWithHost:#"http://192.168.1.4:8080" response:^(SIOSocket *socket) {
self.socket = socket;
NSLog(#"%# from initSIOSocket", self.socket);
[self.socket on:#"q_update_B" callback:^(NSArray *args) {
NSArray *tracks = [args objectAtIndex:0];
[[NSNotificationCenter defaultCenter] postNotificationName:#"qUpdateB" object:nil userInfo:[NSDictionary dictionaryWithObject:tracks forKey:#"tracksData"]];
}];
..
- (void)receiveUpdateBNotification:(NSNotification *)notification
{
// Do parse respone data method and update yourTableViewData
NSArray *tracks = [[notification userInfo] objectForKey:#"tracksData"];
NSLog(#"%#", tracks);
self.tracks = tracks;
[self.tableView reloadData];
}
Console is STILL logging as (null) object. The notification is successful, no data is sent.
For passing data using NSNotification you need to use the userInfo dictionary.
Post it like:
[[NSNotificationCenter defaultCenter] postNotificationName:#"qUpdateB" object:nil userInfo:[NSDictionary dictionaryWithObject:tracks forKey:#"MyData"]];
And retrieve it using:
- (void)receiveUpdateBNotification:(NSNotification *)notification
{
self.tracks = [[notification userInfo] objectForKey:#"MyData"];
[self.tableView reloadData];
}
Object property is not intended for passing data.
object
The object associated with the notification. (read-only) Declaration
#property(readonly, retain) id object Discussion;
This is often the object that posted this notification. It may be nil.
Typically you use this method to find out what object a notification
applies to when you receive a notification.
For example, suppose you’ve registered an object to receive the
message handlePortDeath: when the “PortInvalid” notification is posted
to the notification center and that handlePortDeath: needs to access
the object monitoring the port that is now invalid. handlePortDeath:
can retrieve that object as shown here:
- (void)handlePortDeath:(NSNotification *)notification
{
...
[self reclaimResourcesForPort:notification.object];
...
}
Reference
I needed to use my Singleton to pass the data using the NSNotificationCenter, like so.
-(void) initSIOSocket {
[SIOSocket socketWithHost:#"http://192.168.1.4:8080" response:^(SIOSocket *socket) {
self.socket = socket;
NSLog(#"%# from initSIOSocket", self.socket);
[self.socket on:#"q_update_B" callback:^(NSArray *args) {
NSArray *tracks = [args objectAtIndex:0];
self.setListTracks = tracks;
[[NSNotificationCenter defaultCenter] postNotificationName:#"qUpdateB" object:nil];
}];
}];
}
..
- (void)receiveUpdateBNotification:(NSNotification *)notification
{
if ([[notification name] isEqualToString:#"qUpdateB"])
NSLog (#"Successfully received the test notification!");
// Do parse respone data method and update yourTableViewData
NSArray *tracks = [[SocketKeeperSingleton sharedInstance]setListTracks];
self.tracks = tracks;
[self.tableView reloadData];
}
[aLib enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:assetsGroupEnumerationBlock failureBlock:failureBLock];
This method enumerate every group, I want to enumerate for first group only then I want to break it. My purpose is to ask for permission that iOS pop for first time. I am doing no additional work, I have notifications in blocks that notify and trigger other required functionality. But multiple group enumeration trigger notification multiple times and that I want to stop.
Here is my enumeration block with stop parameter
void(^assetsGroupEnumerationBlock)(ALAssetsGroup*, BOOL*) = ^(ALAssetsGroup *groups, BOOL *stop) {
*stop = YES;
NSDictionary *alAuthDict = #{#"alAssetsAuthStatusDictKey" : [NSString stringWithFormat:#"%ld",[self getALAssetAuthorizationStatus]]};
[[NSNotificationCenter defaultCenter]postNotificationName:#"alAssetsStatusNotificationName" object:nil userInfo:alAuthDict];
};
But notification is getting called two times I see nslog twice in console.
Use the stop parameter:
[lib enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
*stop = YES;
if (group) {
NSDictionary *alAuthDict = #{#"alAssetsAuthStatusDictKey" : [NSString stringWithFormat:#"%ld",[self getALAssetAuthorizationStatus]]};
[[NSNotificationCenter defaultCenter]postNotificationName:#"alAssetsStatusNotificationName" object:nil userInfo:alAuthDict];
}
} failureBlock:^(NSError *error) {
// denied
}];
I'm working on photo app and here is the deal:
I have created custom photo album an added some photos to it (users can create many albums). I used ALAssetsLibrary for this and CustomPhotoAlbum category. Everything worked flawlessly until I needed to get those photos back to reuse them.
So Ive created custom metod for this category and I'm saving UIImages to NSMutableArray but I want this method to return this array outside... My problem is that enumerateAssetsUsingBlock: is running in background and saving photos to album while I want to access them! How can I do something for this method to wait until every photo is loaded to album and then return NSArray?
Here is my method:
- (void)loadImagesFromAlbumNamed:(NSString *)name
{
NSMutableArray *photos = [[NSMutableArray alloc] init];
[self enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group == nil) {
return;
}
if ([[group valueForProperty:ALAssetsGroupPropertyName] isEqualToString:name]) {
[group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result == nil) {
return;
}
// NSLog(#"%#", result);
UIImage *image = [UIImage imageWithCGImage:(__bridge CGImageRef)([result defaultRepresentation])];
[photos addObject:image];
}];
NSLog(#"%#", [photos description]);
}
} failureBlock:^(NSError *error) {
NSLog(#"%#", [error localizedDescription]);
}];
}
I hope you get my point of view... If not, just ask and I'll try to explain it better way!
Don't try to wait. Instead, pass a block to your method which should be called when all of the asset processing blocks are completed and will pass the array of images back to the caller. The caller should not block, you just want to structure your method to deal with the asynchronous nature of what you're trying to do.
I'm using writeImageToSavedPhotosAlbum:orientation:completionBlock: to save an image and it's ok. Then I want an array of assets in the Album.
The first time I refresh my array it doesn't count the new image, while calling it a second time it works fine.
Why? I understand that the writeImageToSavedPhotosAlbum start another thread and that the Block is called after the save operation completes, isn't it?
This is the code I use to save the image and refresh my array:
[_album refresh];
ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
[library writeImageToSavedPhotosAlbum:image
metadata:metadataAsMutable
completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
NSLog(#"ERROR: the image failed to be written");
}
else {
NSLog(#"PHOTO SAVED - assetURL: %#", assetURL);
}
[_album refresh];
[_album refresh];
[spinner stopAnimating];
[self close];
}];
This is the refresh function:
- (void)refresh {
[_imgAssets removeAllObjects];
if (_albumGroup) {
[_albumGroup enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if(result == nil) {
NSLog(#"done enumerating photos (%d)", [_imgAssets count]);
return;
}
[_imgAssets addObject:result];
//NSLog(#">> enumerating photos (%d)", [_imgAssets count]);
}];
}
}
And this is what I obtain:
2013-06-04 23:19:07.962 PhotInfo[4835:c07] done enumerating photos (29)
2013-06-04 23:19:08.041 PhotInfo[4835:c07] PHOTO SAVED - assetURL: assets-library://asset/asset.JPG?id=CDC2E97A-FF47-4A52-99E2-574037155D92&ext=JPG
2013-06-04 23:19:08.042 PhotInfo[4835:c07] done enumerating photos (29)
2013-06-04 23:19:08.044 PhotInfo[4835:c07] done enumerating photos (30)
Moreover, if I replace the second of the 3 refresh with
[_album performSelectorOnMainThread:#selector(refresh) withObject:nil waitUntilDone:YES];
it may or may not read the added image.
Where is the problem?
Thanks for the help.
I've resolved setting an observer on ALAssetsLibraryChangedNotification.