Contacts Framework - fetching Email Field in Objective C - objective-c

I am trying to fetch needed fields from the contacts, using the Contact Framework. All needed fields are properly fetched, EXCEPT the email. If there is one contact without an email recorded, then the entire app crashes. If all contacts have an email defined, all works well. I have a class called ContacList in which a method
fetchContactsFromContactsFrameWork is defined as follows:
-(void)fetchContactsFromContactsFrameWork{
contactStore = [[CNContactStore alloc] init];
NSArray *keyToFetch = #[CNContactEmailAddressesKey,CNContactFamilyNameKey,CNContactGivenNameKey,CNContactPhoneNumbersKey,CNContactPostalAddressesKey,CNContactThumbnailImageDataKey,CNContactOrganizationNameKey];
CNContactFetchRequest *fetchRequest = [[CNContactFetchRequest alloc] initWithKeysToFetch:keyToFetch];
[contactStore enumerateContactsWithFetchRequest:fetchRequest error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
[groupsOfContact addObject:contact];
}];
phoneNumberArray = [#[] mutableCopy];
NSDictionary *peopleDic;
for (CNContact *contact in groupsOfContact) {
NSArray *thisOne = [[contact.phoneNumbers valueForKey:#"value"] valueForKey:#"digits"];
peopleDic = #{#"name":contact.givenName,
#"familyName":contact.familyName,
#"company":contact.organizationName,
#"image":contact.thumbnailImageData != nil ? contact.thumbnailImageData:#"",
#"email":contact.emailAddresses.firstObject != nil ? contact.emailAddresses.firstObject:#"",
#"phone":thisOne,
#"selected":#"NO"
};
[phoneNumberArray addObject:peopleDic];
}
totalPhoneNumberArray = [phoneNumberArray mutableCopy];
}
I am then using the array totalPhoneNumberArray in my PeopleTableViewController where I want to have access to the fetched information. In this I have created a method fetchedContacts, that loos like:
- (void)fetchContacts{
[[ContactList sharedContacts] fetchAllContacts]; //fetch all contacts by calling single method thanks to class 'ContactList"
NSArray *contactsArray = [[NSArray alloc]init];
_arrayOfContacts = [[NSMutableArray alloc]init]; //This will be the edited array to pass to the next ViewController
contactsArray = [[ContactList sharedContacts]totalPhoneNumberArray];//fetched original array now has local name 'contactsArray'
if (contactsArray.count !=0) {
for (int i=0; i<contactsArray.count; i++) {
NSMutableArray *person = [[NSMutableArray alloc]init];//person array will hold 6 properties...
UIImage *contactPic = [UIImage imageWithData:[[contactsArray objectAtIndex:i] valueForKey:#"image"]] ?: [UIImage imageNamed:#"noPerson.png"];
[person addObject:contactPic];
[person addObject:[[contactsArray objectAtIndex:i] valueForKey:#"name"]];
[person addObject:[[contactsArray objectAtIndex:i] valueForKey:#"familyName"]];
[person addObject:[[contactsArray objectAtIndex:i] valueForKey:#"company"]];
[person addObject:[[contactsArray objectAtIndex:i] valueForKey:#"phone"]];
CNLabeledValue *emailValue = [[contactsArray objectAtIndex:i]valueForKey:#"email"] ?:[[CNLabeledValue alloc]initWithLabel:#"email" value:#""];
//[person addObject:#"no email"];
[person addObject:emailValue.value];
//person array now has the form:{image,name,familyName,company,phone,email}
[_arrayOfContacts addObject:person];//arrayOfContacts holds all 'persons' fetched from main contactsArray...
}
}
}
As I explained already, this works perfectly, UNLESS one person has an empty email field, in which case it crashes...I have tried so many things: Turning the CNLabeledValue *emailValue to a NSString, testing for the length of that string, to no avail. Any help will be so much appreciated...

You have code that says:
CNLabeledValue *emailValue = [[contactsArray objectAtIndex:i]valueForKey:#"email"] ?: [[CNLabeledValue alloc]initWithLabel:#"email" value:#""];
[person addObject:emailValue.value];
But, when you populated that email value in the dictionary, you said that if there was no email address, use #"". So, the code above will then proceed to call value on #"", which will not work.
Rather than populating the dictionary with #"" when there is no email address, I'd suggest not setting that dictionary key at all. Or, if you're going to use a "special value" to indicate that there was no email address, you'd generally store [NSNull null]. Frankly, you should be doing the same with the image, too.
For example, I might do:
peopleDic = #{#"name" : contact.givenName ?: #"",
#"familyName" : contact.familyName ?: #"",
#"company" : contact.organizationName ?: #"",
#"image" : contact.thumbnailImageData ?: [NSNull null],
#"email" : contact.emailAddresses.firstObject ?: [NSNull null],
#"phone" : thisOne,
#"selected" : #"NO"
};
Of course, that means that when you retrieve the email value, you check to see if it is NSNull, and if so, set the label accordingly:
for (NSDictionary *contact in contactsArray) {
NSMutableArray *person = [[NSMutableArray alloc]init];//person array will hold 6 properties...
UIImage *contactPic = [contact[#"image"] isKindOfClass:[NSData class]] ? [UIImage imageWithData:contact[#"image"]] : [UIImage imageNamed:#"noPerson.png"];
[person addObject:contactPic];
[person addObject:contact[#"name"]];
[person addObject:contact[#"familyName"]];
[person addObject:contact[#"company"]];
[person addObject:contact[#"phone"]];
CNLabeledValue *email = contact[#"email"];
[person addObject:[email isKindOfClass:[CNLabeledValue class]] ? email.value : #""];
//person array now has the form:{image,name,familyName,company,phone,email}
[_arrayOfContacts addObject:person];//arrayOfContacts holds all 'persons' fetched from main contactsArray...
}
If you're interested in showing phone numbers as a string, there are a few options. Here are a few alternatives:
for (CNContact *contact in groupsOfContact) {
// if you want all phone numbers, you can get as an array, or build a joined string
NSArray *allPhoneNumberAsArray = [[contact.phoneNumbers valueForKey:#"value"] valueForKey:#"stringValue"];
NSString *allPhoneNumberAsSingleString = [allPhoneNumberAsArray count] > 0 ?[allPhoneNumberAsArray componentsJoinedByString:#"; "] : #"No phones";
// if you want only home phone numbers, filter those with `label` of `CNLabelHome`
NSArray *allHomePhoneNumberAsArray = [[[contact.phoneNumbers filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"label == %#", CNLabelHome]] valueForKey:#"value"] valueForKey:#"stringValue"];
NSString *allHomePhoneNumberAsSingleString = [allHomePhoneNumberAsArray count] > 0 ? [allHomePhoneNumberAsArray componentsJoinedByString:#"; "] : #"No home phones";
// if you only want the first home number, it's even easier
NSString *firstHomePhoneNumberAsSingleString = [allHomePhoneNumberAsArray firstObject] ?: #"No home phones";
...
}

NSMutableArray *emailArray;
NSString* email;
for (CNLabeledValue <NSString *>*label in contact.emailAddresses)
{
email = label.value;
NSLog(#"email : %#",email);
if ([email length] > 0) {
[emailArray addObject:email];
}
}

Related

Objective-c: Use predicate with an object

I have a search bar for my table view and until now I used a predicate to check if the data array contains the search bar value. But now there are objects in my data array and now I don't know how to use the predicate. Here is my code:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains [cd] %#", self.controller.searchBar.text];
NSMutableArray *temp = [[NSMutableArray alloc] init];
for (int i = 0; i < [self.data count]; i++) {
Student *student = [[Student alloc] initWithIdentifier:[[self.data objectAtIndex:self.tableView.indexPathForSelectedRow.row] objectForKey:#"id"] name:[[self.data objectAtIndex:self.tableView.indexPathForSelectedRow.row] objectForKey:#"name"] is_public:[[self.data objectAtIndex:self.tableView.indexPathForSelectedRow.row] objectForKey:#"is_public"] password:[[self.data objectAtIndex:self.tableView.indexPathForSelectedRow.row] objectForKey:#"passwort"]];
[temp addObject:student];
}
self.results = [temp filteredArrayUsingPredicate:predicate];
Now it compares the search bar value with the object. But it should compare the search bar value with object.name. How can I do this?
EDIT:
The Student code:
#implementation Student
- (id)initWithIdentifier:(NSString *)identifier name:(NSString *)name is_public:(NSString *)is_public password:(NSString *)password {
self= [super init];
if( self ) {
self.identifier = identifier;
self.name = name;
self.is_public = is_public;
self.password = password;
}
return self;
}
- (NSDictionary*)writableRepresentation {
NSMutableDictionary *writableRepresentation= [NSMutableDictionary dictionaryWithCapacity:4];
[writableRepresentation setValue:self.identifier forKey:#"Identifier"];
[writableRepresentation setValue:self.name forKey:#"Name"];
[writableRepresentation setValue:self.is_public forKey:#"is_public"];
[writableRepresentation setValue:self.password forKey:#"password"];
return writableRepresentation;
}
+ (Student*)studentFromDictionary:(NSDictionary*)dictionaryRepresentation {
return [[Tipprunde alloc] initWithIdentifier:[dictionaryRepresentation valueForKey:#"Identifier"] name:[dictionaryRepresentation valueForKey:#"Name"] is_public:[dictionaryRepresentation valueForKey:#"is_public"] password:[dictionaryRepresentation valueForKey:#"password"]];
}
#end
It don't seem to work with SELF.name. I get the following error:
-[Student objectForKey:]: unrecognized selector sent to instance...

How to return arrays object + count IOS

hi at all ,I've this code :
+(NSArray *)splatterUrls
{
NSString *jsonString = [ ApiMethod jsonOfStores];
NSDictionary *results =[jsonString objectFromJSONString];
NSArray *movieArray = [results objectForKey:#"Seasons"];
//int i=0;
// Search for year to match
for (NSDictionary *movie in movieArray)
{
NSNumber *idSplatterMovie = [movie objectForKey:#"Id"];
// NSLog(#" %#", idSplatterMovie );
NSArray *try = [movie objectForKey:#"Episodes"];
// NSLog(#"%#", try);
for (NSDictionary *op in try)
{
if([idSplatterMovie integerValue] == 46)
{
//i++;
NSArray *movieArrayString = [op objectForKey:#"Url"];
// NSLog(#" %#", movieArrayString);
return movieArrayString;
}
}
}
}
I want to return movieArrayString with all his objects and how many object contains in it. I think that I should use this method : + (id)arrayWithObjects:(const id *)objects count:(NSUInteger)count. It's possible? If yes, can you tell me how can use it?
Thank you so much!
by the way , i have to call splatterUrls method and implement in home.m that it is :
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *urlSplatter= [GetSplatterUrlsMovie splatterUrls];
NSLog(#" %#", urlSplatter);
}
Looks good as it is to me.
Do this to return your movies array, array will be equal to your movies array:
NSArray *array = [self splatterUrls];
Then to get the count/number of objects in your array do this, i is equal to the number of objects in the array:
int i = [array count];
What is the problem ??
You return a NSarray ... call the method count on your NSarray object!

iOS/Objective-C: Attempting to Scan a string for substrings which will be assigned to multiple NSStrings

I'm attempting to complete the Stanford iPhone Programming (FA10) assignement "Flickr Fetcher" -- so far things are going well, however I have come to an impasse:
I have successfully extracted the location of the "Top 100" pictures, which are formated in a string as "Country, State, City". I would like to create two NSStrings -- one being the country, the other string being the State and City. From where I can then do
cell.textLabel.text = countryString;
cell.detailTextLabel.text = stateCityString;
in my table view datasource methods.
From research on stackoverflow and the Apple Documentaion, NSScanner seems to be my best bet -- here is what I have so far...
- (void)viewDidLoad {
//Get the top 100 photos from Flickr
self.topPlacesArray = [FlickrFetcher topPlaces];
NSString *mainLabelString = [[NSString alloc] init];
NSString *stringFromArray = [[NSString alloc] init];
//This retrieves the string of the location of each photo
stringFromArray = [topPlacesArray valueForKey:#"_content"];
NSScanner *theScanner = [NSScanner scannerWithString:stringFromArray];
NSCharacterSet *commaSet = [[NSCharacterSet alloc] init];
commaSet = [NSCharacterSet characterSetWithCharactersInString:#","];
while ([theScanner isAtEnd] == NO) {
if ([theScanner scanUpToCharactersFromSet:commaSet intoString:&stringFromArray]) {
NSLog(#"%#",stringFromArray);
}
}
I'm just trying to see if the string properly substrings itself -- however I am getting a "SIGBART" at the beggining of the while loop, the error is this:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI length]: unrecognized selector sent to instance 0x8939eb0'
From all the documentation I have seen on NSScanner, it seems I have it set up properly, however, no matter what changes I do, it seems unable to even begin the loop.
What do I have to do to set up NSScanner properly, to avoid the "SIGABRT"? (for the record, i'm assuming "SIGABRT" is a segfault?). Thank you all for your time, you all are the best!
(Btw: I know this is not fully implemented yet for both country and state-city, i just want to get used to NSScanner, I will implement the rest once I get NSScanner under control)
EDIT 1: SosBorn! You are incredible! Thank you so much! So I have implemented this for my viewDidLoad:
- (void)viewDidLoad
{
self.topPlacesArray = [FlickrFetcher topPlaces];
NSArray *ArrayOfStrings = [[NSArray alloc] init];
NSArray *placeElements = [[NSArray alloc] init];
NSString *country = [[NSString alloc] init];
NSString *city = [[NSString alloc] init];
NSString *state = [[NSString alloc] init];
ArrayOfStrings = [topPlacesArray valueForKey:#"_content"];
for (NSString *place in ArrayOfStrings) {
placeElements = [place componentsSeparatedByString:#", "];
if ([placeElements count] == 3 && [placeElements objectAtIndex:0] != nil) {
city = [placeElements objectAtIndex:0];
[self.cityArray addObject:city];
state = [placeElements objectAtIndex:1];
[self.stateArray addObject:state];
country = [placeElements objectAtIndex:2];
[self.countryArray addObject:country];
NSLog(#"%#, %#, %#", city, state, country);
}
else {
NSLog(#"Did this work?");
}
}
[ArrayOfStrings release];
[placeElements release];
[country release];
[city release];
[state release];
[super viewDidLoad];
}
This worked like a complete charm BUT i'm having some bad access going on in the Delegate when trying to access self.window.rootViewController = self.viewController -- this doesn't make any-sense (i actually have a completely empty table, etc...) -- so i'm thinking I played with bad memory management with my substring-ing and now it gets in trouble with this delegate call.
Chuck, I was very interested in your comment as I was taught that the proper way to make variables is to call [myclass alloc] init]; and then release when you are done -- as I have. Of course my objective-C greenness is showing a bit... blush.
You all and this incredible community are such an asset to us Students -- thank you for all your time and dedication. The only path to progress is a path of cooperation!
EDIT 2: Ok -- now it's totally fixed with no terrible leaking problems. Chuck you were right! I had the pricniples of alloc init completely mixed up in my head -- here was my final solution:
NSMutableArray *array1 = [[NSMutableArray alloc] init];
NSMutableArray *array2 = [[NSMutableArray alloc] init];
NSMutableArray *array3 = [[NSMutableArray alloc] init];
self.cityArray = array1;
self.countryArray = array2;
self.stateArray = array3;
[array1 release];
[array2 release];
[array3 release];
NSArray *ArrayOfStrings = [topPlacesArray valueForKey:#"_content"];
NSArray *topPlaces = [NSArray arrayWithArray:ArrayOfStrings];
NSArray *topPlacesSorted = [topPlaces sortedArrayUsingSelector:#selector(compare:)];
ArrayOfStrings = topPlacesSorted;
for (NSString *place in ArrayOfStrings) {
NSArray *placeElements = [place componentsSeparatedByString:#", "];
if ([placeElements count] == 3 && [placeElements objectAtIndex:0] != nil) {
NSString *city = [placeElements objectAtIndex:0];
[self.cityArray addObject:city];
NSString *state = [placeElements objectAtIndex:1];
[self.stateArray addObject:state];
NSString *country = [placeElements objectAtIndex:2];
NSString *stateAndCountry = [NSString stringWithFormat:#"%#, %#", state, country];
[self.countryArray addObject:stateAndCountry];
NSLog(#"%#, %#, %#", city, state, country);
}
else {
NSLog(#"Nil Request");
}
Thank you again SosBorn, i was feeling like I had forgotten the basics of CS ಠ_ಠ.
The only thing that really bothers me is why do we have to initialize instance NSMutableArrays that way -- i found this was the only way to get them to actually work.
Not totally sure why it is crashing, but I think another approach to this would serve you better. You have a topPlacesArray, why not iterate through the array and process each array entry seperately? I am making some assumptions about the topPlacesArray, but it would look something like this:
for (NSString *place in topPlacesArray)
{
//Place is probably in this format: "Country, State, City"
NSArray *placeElements = [place componentsSeperatedByString:#","];
//This should give you an array with three elements. Country State and city.
NSString *country = [placeElements objectAtIndex:0];
NSString *cityState = [NSString stringWithFormat:#"%#, %#", country, cityState];
//Now you have your strings that you need. Do whatever you need to do with them.
//Add them to an array or set the value of a text label, etc.
}
Didn't take the time to handle memory management but you get the idea.

iPad app crashing with no crash log while reading from file

The basic structure of my program has the user select an item from a UITableView, which corresponds to a stored text file. The file is then read into an array and a dictionary, where the array has the keys (I know I can just get the keys from the dictionary itself, this isn't my question).
The view is then changed to a UISplitView where the master view has the keys, and the detail view has the items in the dictionary attributed to that key. In this case, it's a series of "Yes/No" questions that the user selects the answer to.
My problem is this: When I click on a cell in the UITableView (first screen), it works fine, the data is read in perfectly, and so on. When I go back to the UITableView and click on the same cell again, the program crashes. Here is the read-in-from-file method:
-(NSArray *)readFromFile:(NSString *)filePath{
// NSLog(#"Path was: %#", filePath);
NSString *file = [[NSString alloc] initWithContentsOfFile:filePath];
// NSLog(#"File was: %#", file);
NSScanner *fileScanner = [[NSScanner alloc] initWithString:file];
NSString *held;
NSString *key;
NSMutableArray *detailStrings;
NSMutableArray *keys = [[NSMutableArray alloc] init];
NSMutableDictionary *details = [[NSMutableDictionary alloc] init];
/**
This is where the fun stuff happens!
**/
while(![fileScanner isAtEnd]){
//Scan the string into held
[fileScanner scanUpToString:#"\r" intoString:&held];
NSLog(#"Inside the while loop");
// If it is a character, it's one of the Key points, so we do the management necessary
if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
NSLog(#"Word at index 2: %#", [checkers objectAtIndex:2]);
if(detailStrings != nil){
[details setObject:detailStrings forKey:key];
[detailStrings release];
}
NSLog(#"After if statement");
key = [checkers objectAtIndex:2];
[keys addObject:(NSString *) key];
detailStrings = [[NSMutableArray alloc] init];
}
else if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
NSLog(#"Word at index 1: %#", [checkers objectAtIndex:1]);
[detailStrings addObject:[checkers objectAtIndex:1]];
}
}
NSLog(#"File has been read in");
[details setObject:detailStrings forKey:key];
NSArray *contents = [[NSArray alloc] initWithObjects:(NSMutableArray *) keys, (NSMutableDictionary *) details, nil];
[detailStrings release];
return contents;
}
I've determined that the program crashes inside the
if(detailStrings != nil)
statement. I figure this is because I'm missing some memory management that I am supposed to be doing, but don't have the knowledge of where it's going wrong. Any ideas as to the problem, or why it is crashing without giving me a log?
detailStrings is not initialized when you enter the while loop. When you declare NSMutableArray *detailStrings; inside a method, detailStrings is not automatically set to nil. So when you do
if ( detailStrings != nil ) { .. }
it enters the if statement and since it is not initialized, it will crash when you access detailStrings.
Another thing is that detailStrings won't be initialized if it enters the else part of the loop first. That will cause a crash too. So based on your requirement, either do
NSMutableArray *detailStrings = nil;
or initialize it before you enter the while loop.
Deepak said truth. You should initialize detailStrings with nil first.
But there is second possible issue:
I recommend also to set nil after release, because in the next loop you may test nonexistent part of memory with nil.
if(detailStrings != nil){
[details setObject:detailStrings forKey:key];
[detailStrings release];
detailStrings = nil;
}
And the third possible issue: depending from incoming data you may go to the second part of IF statement first time and try to addObject into non-initialized array.
The fourth (hope last): you have memory leak with "checkers" arrays
Here's what I'm seeing:
//read in the file
NSString *file = [[NSString alloc] initWithContentsOfFile:filePath];
//create the scanner
NSScanner *fileScanner = [[NSScanner alloc] initWithString:file];
//declare some uninitialized stuff
NSString *held;
NSString *key;
NSMutableArray *detailStrings;
//initialize some stuff
NSMutableArray *keys = [[NSMutableArray alloc] init];
NSMutableDictionary *details = [[NSMutableDictionary alloc] init];
//begin loop
while(![fileScanner isAtEnd]){
//scan up to a newline
[fileScanner scanUpToString:#"\r" intoString:&held];
//see if you scanned a lowercase string
if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
//make an array
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
//do a check... against an uninitialized value
if(detailStrings != nil){
//set a potentially uninitialized value into an array with an uninitialized key
[details setObject:detailStrings forKey:key];
At this point, you're pretty much hosed.
The fix:
properly initialize your variables
run the static analyzer
read the memory management programming guide

Initialization of an NSDictionary in Objective C (for iOS)

I am relatively new to Objective-C and now I have a problem in my iPhone app that I don't fully understand.
I try to use a NSMutableDictionary, this does not seem to work as i expect for some reason. When I run the debugger and do po numberToCallerMap to see the dictionary, I get an exception. I have read the documentation for NSMutableDictionary on how to initialize it, but I can not see what I am doing wrong. Help and advice are appreciated. The variable causing me problem is numberToCallerMap, here is the relevant function:
- (void)setData:(NSString*)value{
[list release];
list = [[NSMutableArray alloc] init];
SBJSON *json = [[[SBJSON alloc] init] autorelease];
NSMutableDictionary* numberToCallerMap;
CallerInfo* caller;
NSDictionary* callerInfo;
#try {
NSArray *array = (NSArray*)[json objectWithString:value];
// reading all the items in the array one by one
numberToCallerMap = [NSMutableDictionary dictionary];
for (id *item in array) {
// if the item is NSDictionary (in this case ... different json file will probably have a different class)
NSDictionary *dict2 = (NSDictionary *) item;
CallInfo *data = [CallInfo alloc];
[data initFromDictionary:dict2];
callerInfo = (NSDictionary*)[dict2 valueForKey:#"caller"] ;
//Here, we want the phonenumber to be part of the CallerInfo object instead.
// It is sent from the server as part of the Call-object
NSString* number = (NSString*)[dict2 valueForKey:#"phoneNumber"];
[callerInfo setValue:number forKey:#"phoneNumber"];
caller = (CallerInfo*)[numberToCallerMap valueForKey:number];
if(caller == nil || [caller isKindOfClass:[NSNull class]]){
caller = [CallerInfo alloc];
[caller initFromDictionary:callerInfo];
[numberToCallerMap setValue:caller forKey:number];
[list insertObject:caller atIndex:0];
}
[caller addRecentCall:data];
}
}
#catch (NSException * e) {
[list release];
list = [[NSMutableArray alloc] init];
}
#finally {
[numberToCallerMap release];
}
}
This is probably not the only problem, but you are not alloc-ing your numberToCallerMap dictionary, you are getting it from a convenience class method -- [NSMutableDictionary dictionary] -- that returns it autoreleased. So you should not call release on it yourself.