FIlter alassets by year - objective-c

I'm trying to filter AlAssets by year and month. I can already get the dates and filter by year and month, but it's too slow with about 1000 photos. What's the best way to do it?
+ (void) loadFromLibraryByDate:(ALAssetsLibrary *)library assetType:(NSString *)type toArray:(NSMutableArray *)array onTable:(UITableView *)tableView onYear:(NSString *)year onMonth:(NSString *)mouth withDelegate:(id) delegate{
//clean passed objects
[array removeAllObjects];
// filter for the library
NSInteger groupType = ALAssetsGroupAll;
// block to enumerate thought the groups
ALAssetsLibraryGroupsEnumerationResultsBlock listGroupBlock =
^(ALAssetsGroup *group, BOOL *stop){
if(group){
[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop){
if(asset){
// cachedPhotos = [NSMutableDictionary new];
if([asset valueForProperty:ALAssetPropertyType] == type){
if(year != nil && mouth != nil)
{
NSDate *date = [asset valueForProperty:ALAssetPropertyDate];
if(date.year == [year integerValue] && date.month == [mouth integerValue])
{
[array addObject:asset];
}
}
else if(year != nil && mouth == nil)
{
NSDate *date = [asset valueForProperty:ALAssetPropertyDate];
NSString *monthName = [date monthName:date.month];
if(date.year == [year integerValue])
{
if(![array containsObject:monthName])
{
[array addObject:monthName];
}
}
}
else
{
NSDate *date = [asset valueForProperty:ALAssetPropertyDate];
NSNumber *yearNum = [NSNumber numberWithInt:date.year];
if(![array containsObject:yearNum])
{
[array addObject:yearNum];
}
}
}
}
}];
}
else{
if( [delegate respondsToSelector:#selector(didFinishLoadingLibraryByDate:)] ){
[delegate performSelector:#selector(didFinishLoadingLibraryByDate:)];
}
[tableView reloadData];
}
};
// failure block, what happens if when something wrong happens when enumerating
ALAssetsLibraryAccessFailureBlock failBlock = ^(NSError *error){
NSLog(#"Error: %#", [error localizedDescription]);
static dispatch_once_t pred;
dispatch_once(&pred, ^{
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *libraryFailure = [[UIAlertView alloc] initWithTitle:#"Serviço de Localização" message:#"Para poder partilhar conteúdos nesta versão iOS, tem de autorizar os serviços de localização. (Definições > Serviços de Localização)" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[libraryFailure show];
[libraryFailure release];
});
});
};
[library enumerateGroupsWithTypes:groupType usingBlock:listGroupBlock failureBlock:failBlock];
Any help appreciated, thanks

I think you're on the right track. There is no way I know of to filter on metadata except by enumerating the way you are doing it. Unfortunately, enumerating through asset groups is just inherently slow on iOS -- if you think 1000 is bad, try 10k or 20k assets (not at all uncommon, I have that on my carry phone right now).
One way around this (not necessarily advised, as it's a lot of work and the bug potential is very high) is to build your own database of asset timestamps. While the user is otherwise busy (with a tutorial or something), enumerate over all the assets and copy the metadata and ALAssetPropertyAssetURL to whatever format works best for you. Don't forget to listen for ALAssetsLibraryChangedNotification messages if you do this.

You should enumerate all ALAsset first When App launching,and then filter them. Because Enumerating ALAsset from database is so slow, so you should not reenumerate them again.
Therer is a notice, reenumerate ALAsset more faster then the first. Apple should optimize the library.

Related

TableView doesn't show uiimage

I have an app that shows twitter account feed. So I have ImageView, textLabel and detailLabel for the content of the feed. The problem is that when all the data is loaded, the uiimage doesn't appear. When I click on the cell or scroll up-down, images are set. here is some of my code.
-(void)getImageFromUrl:(NSString*)imageUrl asynchronouslyForImageView:(UIImageView*)imageView andKey:(NSString*)key{
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:imageUrl];
__block NSData *imageData;
dispatch_sync(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
imageData =[NSData dataWithContentsOfURL:url];
if(imageData){
[self.imagesDictionary setObject:[UIImage imageWithData:imageData] forKey:key];
dispatch_sync(dispatch_get_main_queue(), ^{
imageView.image = self.imagesDictionary[key];
});
}
});
});
}
- (void)refreshTwitterHomeFeedWithCompletion {
// Request access to the Twitter accounts
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error){
if (granted) {
NSArray *accounts = [accountStore accountsWithAccountType:accountType];
// Check if the users has setup at least one Twitter account
if (accounts.count > 0)
{
ACAccount *twitterAccount = [accounts objectAtIndex:0];
NSLog(#"request.account ...%#",twitterAccount.username);
NSURL* url = [NSURL URLWithString:#"https://api.twitter.com/1.1/statuses/home_timeline.json"];
NSDictionary* params = #{#"count" : #"50", #"screen_name" : twitterAccount.username};
SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodGET
URL:url parameters:params];
request.account = twitterAccount;
[request performRequestWithHandler:^(NSData *responseData,
NSHTTPURLResponse *urlResponse, NSError *error) {
if (error)
{
NSString* errorMessage = [NSString stringWithFormat:#"There was an error reading your Twitter feed. %#",
[error localizedDescription]];
NSLog(#"%#",errorMessage);
}
else
{
NSError *jsonError;
NSArray *responseJSON = [NSJSONSerialization
JSONObjectWithData:responseData
options:NSJSONReadingAllowFragments
error:&jsonError];
if (jsonError)
{
NSString* errorMessage = [NSString stringWithFormat:#"There was an error reading your Twitter feed. %#",
[jsonError localizedDescription]];
NSLog(#"%#",errorMessage);
}
else
{
NSLog(#"Home responseJSON..%#",(NSDictionary*)responseJSON.description);
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadData:responseJSON];
});
}
}
}];
}
}
}];
}
-(void)reloadData:(NSArray*)jsonResponse
{
self.tweets = jsonResponse;
[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return self.tweets.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
SNTwitterCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell)
{
cell = [[SNTwitterCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
NSDictionary *tweetDictionary = self.tweets[indexPath.row];
NSDictionary *user = tweetDictionary[#"user"];
NSString *userName = user[#"name"];
NSString *tweetContaint = tweetDictionary[#"text"];
NSString* imageUrl = [user objectForKey:#"profile_image_url"];
[self getImageFromUrl:imageUrl asynchronouslyForImageView:cell.imageView andKey:userName];
cell.profileImage.image = [UIImage imageNamed:#"images.png"];
NSArray *days = [NSArray arrayWithObjects:#"Mon ", #"Tue ", #"Wed ", #"Thu ", #"Fri ", #"Sat ", #"Sun ", nil];
NSArray *calendarMonths = [NSArray arrayWithObjects:#"Jan", #"Feb", #"Mar",#"Apr", #"May", #"Jun", #"Jul", #"Aug", #"Sep", #"Oct", #"Nov", #"Dec", nil];
NSString *dateStr = [tweetDictionary objectForKey:#"created_at"];
for (NSString *day in days) {
if ([dateStr rangeOfString:day].location == 0) {
dateStr = [dateStr stringByReplacingOccurrencesOfString:day withString:#""];
break;
}
}
NSArray *dateArray = [dateStr componentsSeparatedByString:#" "];
NSArray *hourArray = [[dateArray objectAtIndex:2] componentsSeparatedByString:#":"];
NSDateComponents *components = [[NSDateComponents alloc] init];
NSString *aux = [dateArray objectAtIndex:0];
int month = 0;
for (NSString *m in calendarMonths) {
month++;
if ([m isEqualToString:aux]) {
break;
}
}
components.month = month;
components.day = [[dateArray objectAtIndex:1] intValue];
components.hour = [[hourArray objectAtIndex:0] intValue];
components.minute = [[hourArray objectAtIndex:1] intValue];
components.second = [[hourArray objectAtIndex:2] intValue];
components.year = [[dateArray objectAtIndex:4] intValue];
NSTimeZone *gmt = [NSTimeZone timeZoneForSecondsFromGMT:2];
[components setTimeZone:gmt];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
[calendar setTimeZone:[NSTimeZone systemTimeZone]];
NSDate *date = [calendar dateFromComponents:components];
NSString *tweetDate = [self getTimeAsString:date];
NSString *tweetValues = [NSString stringWithFormat:#"%# :%#",userName,tweetDate];
cell.textLabel.text = [NSString stringWithFormat:#"%#",tweetValues];
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#",tweetContaint];
[cell.detailTextLabel setFont:[UIFont fontWithName:#"Helvetica" size:20]];
return cell;
}
- (NSString*)getTimeAsString:(NSDate *)lastDate {
NSTimeInterval dateDiff = [[NSDate date] timeIntervalSinceDate:lastDate];
int nrSeconds = dateDiff;//components.second;
int nrMinutes = nrSeconds / 60;
int nrHours = nrSeconds / 3600;
int nrDays = dateDiff / 86400; //components.day;
NSString *time;
if (nrDays > 5){
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateStyle:NSDateFormatterShortStyle];
[dateFormat setTimeStyle:NSDateFormatterNoStyle];
time = [NSString stringWithFormat:#"%#", [dateFormat stringFromDate:lastDate]];
} else {
// days=1-5
if (nrDays > 0) {
if (nrDays == 1) {
time = #"1 day ago";
} else {
time = [NSString stringWithFormat:#"%d days ago", nrDays];
}
} else {
if (nrHours == 0) {
if (nrMinutes < 2) {
time = #"just now";
} else {
time = [NSString stringWithFormat:#"%d minutes ago", nrMinutes];
}
} else { // days=0 hours!=0
if (nrHours == 1) {
time = #"1 hour ago";
} else {
time = [NSString stringWithFormat:#"%d hours ago", nrHours];
}
}
}
}
return [NSString stringWithFormat:NSLocalizedString(#"%#", #"label"), time];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 100;
}
The fundamental problem is that the standard imageView property of the standard table view cell will automatically resize itself based upon the image that is present when cellForRowAtIndexPath finishes. But since there is no image yet when you first present the table, the cell is laid out as if there's no image. And when you asynchronously update the image view's image, it won't resize the image view.
There are a couple of ways of solving this:
Don't use the default imageView provided by UITableViewCell, but rather define your own custom cell subclass with an IBOutlet to its own UIImageView property. Make sure that this UIImageView has a fixed layout (i.e., it doesn't use the intrinsic size derived from the underlying image).
If you do that, you can asynchronously update the image property for your custom UIImageView outlet, and because the layout was not contingent upon the presence of the image, any asynchronous updates of that image should appear correctly.
When you receive the image, don't just set the image view's image property, but rather reload the whole row associated with that NSIndexPath using reloadRowsAtIndexPaths.
If you do this, the cell will be laid out correctly assuming that you retrieve the image from the cache correctly, and do so before cellForRowAtIndexPath finishes.
Note, if you do this, you will need to fix your getImageFromUrl to actually try to retrieve the image from the cache first (and do this from the main queue, before to dispatch to the background queue), or else you'll end up in an endless loop.
Having said that, there are deeper problems here.
As I mentioned above, you're caching your images, but never using the cache when retrieving the images.
You are asynchronously updating the image view.
You should initialize the image property of the UIImageView before you initiate the new asynchronous fetch, otherwise when a cell is reused, you'll see the old image there until the new image is retrieved.
What if the cell was reused in the intervening period between calling getImageFromUrl and when the asynchronous request finishes? You'll be updating the image view for the wrong cell. (This problem will be more apparent when doing this over a slow connection. Run your code using the network link conditioner to simulate slow connections and you'll see the problem I'm describing.)
What if the user rapidly scrolls down to the 100th row in the table? The network requests for the visible cells will be backlogged behind the other 99 image requests. You could even get timeout errors on slow connections.
There are a bunch of tactical little issues in getImageFromUrl.
Why dispatching synchronously from global queue to another global queue? That's unnecessary. Why dispatching UI update synchronously to main thread? That's inefficient.
Why define imageData as __block outside of the block; just define it within the block and you don't need __block qualifier.
What if you didn't receive a valid UIImage from the network request (e.g. you got a 404 error message); the existing code would crash. There are all sorts of responses the server might provide which are not a valid image, and you really must identify that situation (i.e. make sure that not only was NSData you received not nil, but also that the UIImage that you created from it was not nil, too).
I'd probably use NSCache rather than NSMutableDictionary for the cache. Also, regardless of whether you use NSCache or NSMutableDictionary, you want to make sure that you respond to memory pressure events and empty that cache if needed.
We can go through all of these individual problems, but it's a non-trivial amount of work to fix all of this. I might therefore suggest you consider the UIImageView categories of SDWebImage or AFNetworking. They take care of most of these issues, plus others. It will make your life much, much easier.

Using GCD and Core Data causes crash

I retrieve data back from our server and I need to process it.
For each key, I create a NSManagedObject. Each object is created in the same context. I am using Magical Record.
-(id)init {
if (self = [super init]){
self.context = [NSManagedObjectContext MR_contextForCurrentThread];
}
return self;
}
Threading:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
for (id key in boundariesDictionary) {
dispatch_group_async(group, queue, ^{
DLog(#"nsthread: %#", [NSThread currentThread]);
NSString *boundaryIDString;
if ([key isKindOfClass:[NSString class]]) {
boundaryIDString = key;
}
else if ([key isKindOfClass:[NSNumber class]]) {
boundaryIDString = [key stringValue];
}
if (boundaryIDString) {
DLog(#"boundaryIDString: %#", boundaryIDString)
NSDictionary *boundaryDictionary = [boundariesDictionary objectForKey:key];
Boundary *boundary = [Boundary MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:#"boundaryID == %# AND api == %#", [NSNumber numberWithInteger:[boundaryIDString integerValue]], self.serverCall.API] inContext:self.context];
if ([boundaryDictionary objectForKey:AVI_NAME]) {
if (boundary == nil) {
DLog(#"creating boundary %#", boundaryIDString);
boundary = [Boundary MR_createInContext:self.context];
boundary.boundaryID = [NSNumber numberWithInteger:[boundaryIDString integerValue]];
}
}
boundary = [self processBoundary:boundary fromBoundaryDictionary:boundaryDictionary];
}
}
}
[self processBoundary] just takes the dictionary and sets it to the managed object's attributes.
if ([boundaryDictionary objectForKey:#"name"]) {
boundary.name = [boundaryDictionary objectForKey:#"name"];
}
//more data processing
This is causing an error though:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x1776f7f0> was mutated while being enumerated.'
It runs fine if I don't use the same context for each thread.
I don't understand what set other then the NSDictionary boundariesDictionary that i'm enumerating through. I am not mutating boundariesDictionary at all, only copying the data into core data.
When I PO the object (0x1776f7f0 in this case), I get a list of Boundary objects in a set. Those Boundary objects would only exist in the NSManagedObjectContext "set", I don't add them to an NSArray, NSDictionary, or NSSet. But I don't believe I enumerate over that set. I do mutate it by creating new boundary objects to it.
I think there is something going on that I don't understand or quite grasp yet.
Any ideas?
UPDATE:
for (id key in boundariesDictionary) {
NSString *boundaryIDString;
if ([key isKindOfClass:[NSString class]]) {
boundaryIDString = key;
}
else if ([key isKindOfClass:[NSNumber class]]) {
boundaryIDString = [key stringValue];
}
if (boundaryIDString) {
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
DLog(#"saveWithBlock thread: %#", [NSThread currentThread]);
NSDictionary *boundaryDictionary = [boundariesDictionary objectForKey:key];
Boundary *boundary = [Boundary MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:#"boundaryID == %# AND api == %#", [NSNumber numberWithInteger:[boundaryIDString integerValue]], self.serverCall.API] inContext:localContext];
if ([boundaryDictionary objectForKey:AVI_NAME]) {
if (boundary == nil) {
boundary = [Boundary MR_createInContext:localContext];
boundary.boundaryID = [NSNumber numberWithInteger:[boundaryIDString integerValue]];
}
boundary = [self processBoundary:boundary fromBoundaryDictionary:boundaryDictionary];
if (boundary == nil) {
//Prompt Error
}
else {
for (NSNumber *groupID in groupIDs) {
if ([groupID isKindOfClass:[NSNumber class]]) {
Group *group = [Group MR_findFirstWithPredicate:[NSPredicate predicateWithFormat:#"groupID == %# OR groupID == 0 AND api == %#", groupID, self.serverCall.API]];
if (group != nil) {
group.lastUpdated = [NSDate date];
[group addBoundariesObject:boundary];
}
else {
DLog(#"group %# DNE", groupID);
}
}
}
}
}
} completion:^(BOOL success, NSError *error) {
DLog(#"saveWithBlock completion Block | time: %f", [[NSDate date] timeIntervalSinceDate:startTime]);
}];
}
}
So for my Group 29 should see all the boundaries I'm creating, but its not. Its inconsistent. Sometimes sees all, sometimes some, and sometimes none.
Also, I often see
NO CHANGES IN ** BACKGROUND SAVING (ROOT) ** CONTEXT - NOT SAVING
in the log. Its also inconsistent on how many of these messages I see, while the context that do save will insert more then 1 object.
Not sure if that is how it should be behaving, seems like it should be a 1-to-1 ratio if each block has its own context and each block only creates 1 object.
I log each thread ID, and it is creating a new thread for each block. No thread ID is being logged twice, so the threads shouldn't be being reused.
Managed object contexts are not thread safe, you must not use them or any managed objects/sets/data structures/whatever they might return from different queues or threads.
I don't know if magical record supports it, but you should definitely be using queue containment for your contexts, and then you have to do everything within the context of the MOC's private queue.
If you use thread containment, you must guarantee that the context and any managed objects created by the context are always serially accessed, which you're absolutely not doing in the code above.
You're issue is that gcd queues a threads are not a one to one mapping. GCD reuses threads, and thus your are likely crossing thread boundaries unknowingly here. My suggestion is to simply create a new context and stop using contextForCurrentThread. I wrote more details about the issues on my blog. ContextForCurrentThread will be deleted in an upcoming release.

After closing the App NSTimer updating in iphone

When my app become active I get last image from ALAssetsLibrary which is stored in sqlite and I start a NSTimer to check a new image is added to ALAssetsLibrary. After screenshot is taken in my app it displays alert because image name in sqlite and ALAssetsLibrary last image name are mismatching. When app is closed I stop timer and user open it again I start the timer again to check screenshot image is deleted or not. If it is not deleted it will display an alert
-(void)getAllImagesName
{
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
NSMutableArray *imgArray = [[NSMutableArray alloc]init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
// Within the group enumeration block, filter to enumerate just photos.
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
for (int i=0; i<[group numberOfAssets]; i++) {
// Chooses the photo at the last index
[group enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:i] options:0 usingBlock:^(ALAsset *alAsset, NSUInteger index, BOOL *innerStop) {
// The end of the enumeration is signaled by asset == nil.
if (alAsset) {
// ALAssetRepresentation *representation = [alAsset defaultRepresentation];
[imgArray addObject:alAsset.defaultRepresentation.filename];
}
}];
}
NSString *img;
img=[[DBModel database]getPreviousName];
NSLog(#"select img=%#",img);
NSArray *results;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains[cd] %#",img];
results = [contentArray filteredArrayUsingPredicate:predicate];
//NSLog(#"predicate results = %#",results);
if ([results count] != 0) {
[self displayAlertMsg];
}
else{
}
}
} failureBlock: ^(NSError *error) {
// Typically you should handle an error more gracefully than this.
//NSLog(#"No groups");
}];
}
timer1 = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(getAllImagesName) userInfo:nil repeats:YES];
runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer1 forMode:NSRunLoopCommonModes];
[runLoop run];
My problem is after user taken screen shot in other app and try to use my app it is displaying an alert. Is timer running after closing app? please let me know anyone not understanding my problem.

How does one eliminate Objective-C #try #catch blocks like this?

I'm a developer from Python world used to using exceptions. I found in many places that using exceptions is not so wise here, and did my best to convert to NSErrors when needed. but then I encounter this:
NSMutableArray *results;
for (NSDictionary *dict in dicts)
{
// Memory management code omitted
SomeModel *model = [[SomeModel alloc] init];
model.attr1 = [[dict objectForKey:#"key1"] integerValue];
model.attr2 = [[dict objectForKey:#"key2"] integerValue];
model.attr3 = [[dict objectForKey:#"key3"] integerValue];
model.attr4 = [[dict objectForKey:#"key4"] integerValue];
[results addObject:model];
}
with some of the objects in dict containing NSNull, which would result an "unrecognized selector" exception. In that case, I want to drop that datum completely. My first instinct is to wrap the whole content of the for block into a #try-#catch block:
NSMutableArray *results;
for (NSDictionary *dict in dicts)
{
#try
{
SomeModel *model = [[SomeModel alloc] init];
model.attr1 = [[dict objectForKey:#"key1"] integerValue];
model.attr2 = [[dict objectForKey:#"key2"] integerValue];
model.attr3 = [[dict objectForKey:#"key3"] integerValue];
model.attr4 = [[dict objectForKey:#"key4"] integerValue];
[results addObject:model];
}
#catch(NSException *exception)
{
// Do something
}
}
But is this a good approach? I can't come up with a solution without repeating checks on each variable, which is really ugly IMO. Hopefully there are alternatives to this that haven't occur to me. Thanks in advance.
The proper Objective-C way to do this would be:
for (NSDictionary *dict in dicts)
{
if (! [dict isKindOfClass:[NSDictionary class]])
continue;
// ...
}
Testing if a receiver can respond to a message before sending it is a typical pattern in Objective-C.
Also, take note that exceptions in Objective-C are always a programmer error and are not used for normal execution flow.
Many people use a category on NSDictionary for these cases:
- (id)safeObjectForKey:(id)aKey
{
id obj = [self objectForKey:aKey];
if ([obj isKindOfClass:[NSNull class]])
{
return nil;
}
return obj;
}
You still need to make sure, that your dict is an actual dictionary instance.
In the end I decided to solve the problem using KVC. Something like this:
- (id)initWithPropertyDictionary:(NSDictionary *)dict
lookUpTable:(NSDictionary *)keyToProperty
{
self = [self init];
for (NSString *key in dict)
{
NSString *propertyName;
if ([keyToProperty objectForKey:key])
propertyName = [keyToProperty objectForKey:key];
else
propertyName = key;
if ([[dict objectForKey:key] isKindOfClass:[NSNull class]])
{
[self release];
return nil;
}
else
{
[self setValue:[dict objectForKey:key] forKey:propertyName];
}
}
}
The setback of this resolution is that I'll have to use NSNumber for my properties, but for JSON data there is really no distinction between floating numbers and integers, so this is fine.
And if you really want primitive types, you can couple this method with custom setters that converts those NSNumbers into appropriate types.
With this, all you need to do is check for nil before adding the object into the array. Much cleaner everywhere except the model class.
Thanks to jaydee3 for inspiring me to focus on changing the model class.

Why are there no times in any of my GDataEntryCalendarEvent?

I am using a GDataServiceGoogleCalendar to anonymously fetch a GDataFeedCalendarEvent from a public url. But I am absolutely unable to retrieve times from any of the resulting GDataEntryCalendarEvent objects. I can read the title, so I believe the API works, but somehow the times arrays are lost somewhere.
The service is instantiated as follows:
- (GDataServiceGoogleCalendar *)calendarService {
static GDataServiceGoogleCalendar* service = nil;
if (!service) {
service = [[GDataServiceGoogleCalendar alloc] init];
[service setShouldCacheResponseData:YES];
[service setServiceShouldFollowNextLinks:YES];
[service setIsServiceRetryEnabled:YES];
}
return service;
}
This is the code where the data is retrieved:
for (GDataEntryCalendarEvent *event in eventEntries) {
NSString *title = [[event title] stringValue];
GDataDateTime *startTime = nil;
GDataDateTime *endTime = nil;
NSArray *times = [event times];
GDataWhen *when = nil;
if ([times count] > 0) {
when = [times objectAtIndex:0];
startTime = [when startTime];
endTime = [when endTime];
}
}
What is wrong with my code or the way I connect? The sample app retrieves the dates successfully.
I am sorry but I can not answer what is wrong with your code. I had problems with the date of the events, and that was my solution that works great for me:
Hope it will help you:
NSArray *events = [feed entries];
[events enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
GDataEntryEvent *entry = [events objectAtIndex:idx];
for (NSUInteger i=0; i<[[entry times] count]; i++) {
NSDate * startDate = [[[[entry times] objectAtIndex:i] startTime] date];
NSDate * endDate = [[[[entry times] objectAtIndex:i] endTime] date];
}
}];
There might be a better way but this works.
The Google API requires the use of an actual identified user even to access the public data.
So no access to most API call s is granted until an authenticated user account is being used.