iOS/iPhone programming. Address book. Extra property for user/contact - iphone

I'm developing an iPhone application which uses standard iPhone address book (database) of contacts. I need to add some extra property to contacts but as I see iOS API does not permit addition of extra/custom properties to contacts.
Questions:
1. Is there an ability to deal with extra properties in iOS adressbook API?
2. I need expert's advice about standard approaches of how to store extra data for users/contacts: using SQLite, XML or maybe there is some data store dedicated for every application?

It isn't possible to append custom fields to Address Book records. You should consider looking into Core Data where you can store your custom fields mapped to a record ID.

Add custom property to address book.
The following code listing adds a custom property, and then removes
it:
NSNumber* stringProperty = [NSNumber numberWithInteger:kABStringProperty];
NSString* testProperty = #"com.example.myProperty";
NSDictionary* dict = [NSDictionary dictionaryWithObject:stringProperty
forKey:testProperty];
NSInteger result = [ABPerson addPropertiesAndTypes:dict];
NSLog(#"Added %d properties.", result);
result = [ABPerson removeProperties:[NSArray arrayWithObject:testProperty]];
NSLog(#"Removed %d properties.", result);
More info:
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AddressBook/Tasks/AddingProperties.html

Related

Best way to check existent data in the database (iOS)

I'm developing an app that manages messages, and I want the app connects to the server, get messages and save them in the database(coredata). If the messages already exist, doesnt do anything and if they dont, add them to the database.
I'm thinking some ways to do it but I don't know exactly what to do. Any help? Thanks in advance
I would recommend using Restkit framework http://restkit.org
Reskit provides integration with Core Data.
Pros of using Restkit:
- Combines HTTP request/responses API, along with object mapping, offline/caching support with Core Data, all in one framework
- Object Mapping means that you're writing clean code, you define your classes and how they map to the JSON attributes, then you GET/POST/DELETE with few lines of code after that
- Core Data support means that your projects can work offline, data is sync when working online, but persistent when you need it offline
- The framework is well maintained
Cons:
- Works only with JSON REST APIs
- There can be a steep learning curve for some aspects
- Can be challenging if you work with REST APIs that are not completely 'standard'
The simplest way is to add a guid attribute (an identifier of type NSString, for example) to the entity you are interested in and check for that guid when you import data.
Here, you have two ways: let the server generate the guid for you or implement your own algorithm in the client side (iPhone, iPad, etc.). In both cases you need to be sure the guid is unique for each message.
So, for example, suppose the server generates the messages (and each message has its own guid). When you import data you also save the guid for each message object. If you have already a message with a specific guid, you don't add it, otherwise you add it. This could be done using the Find-or-Create pattern (see Implementing Find-or-Create Efficiently).
Hope that helps.
This is simple, it took me sometime to learn this, I use it in most of my apps.
First you need an ID of the fetched item, for example messageID.
When you fetch the JSON with all the items, for example using AFNetworking, you're going to receive an array of objects in NSDictionaries.
Before parsing the item load all the IDs of your stored items in a NSMutableDictionary (key => messageID, value objectID, this is related to the Core Data fault).
Don't forget to init the NSMutableArray somewhere:
_dictionaryOfEventIDAndObjectID = [NSMutableDictionary dictionary];
- (void)prepareDictionaryOfMessageIDs
{
[self.dictionaryOfEventIDAndObjectID removeAllObjects];
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Message"];
[fetchRequest setResultType:NSDictionaryResultType];
NSExpressionDescription *objectIDDescription = [[NSExpressionDescription alloc] init];
objectIDDescription.name = #"objectID";
objectIDDescription.expression = [NSExpression expressionForEvaluatedObject];
objectIDDescription.expressionResultType = NSObjectIDAttributeType;
[fetchRequest setPropertiesToFetch:#[objectIDDescription, #"messageID"]];
NSArray *objectsDict = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (NSDictionary *objectDict in objectsDict) {
[self.dictionaryOfMessageIDAndObjectID setObject:[objectDict valueForKeyPath:#"objectID"] forKey:[objectDict valueForKeyPath:#"messageID"]];
}
}
Then in the fetched data completion block just add something like this:
for (NSDictionary *objectDict in objectsDict) {
NSString *fetchedID = [objectDict objectForKey:#"id"];
if ([self.dictionaryOfMessageIDAndObjectID objectForKey:fetchedID]) {
continue;
}
[self parseMessageFromDictionary:objectDict];
}

Adding multiple values as a single entry into an NSMutableArray and retrieving it

I retrieve 6 values(say name, age, sex, address, id, tag) from a web service. All are string variables. I concatenate these strings and add it to an NSMutableArray. I pass this array to another class, where I need each of these strings separately. That is I need to be able to retrieve these values from the array separately. How can I do this.
Do I need to add tags like "Name", "Age" etc along with the values to make the retrieval easier. Whats the appropriate way to do it.
Edit: i concatenate it into a single string. How should I be adding my values to the collection, so that I can retrieve the elements easily.
IMO, the most appropriate way of doing what you are trying to do is using an NSMutableDictionary, that allows you to access individual elements based on their key.
Example:
loadedBuffers = [[NSMutableDictionary alloc] initWithCapacity:CD_BUFFERS_START];
[loadedBuffers setObject:bufferId forKey:filePath];
...
[loadedBuffers objectForKey:filePath]
You do no strictly need using a dictionary, but it will make your life so much easier.
In your case (if I understand it correctly), I would do:
NSMutableArray* result = [NSArray arrayWithCapacity:kNUM_OF_ROWS];
NSString *name, *age, *sex....;
<for each set of strings from the web service>
<retrieve strings>
NSMutableDictionary dict = [NSMutableDictionary dictionaryWithCapacity:kNUM_OF_FIELDS];
[dict setObject:name forKey:#"name"];
...
[dict setObject:address forKey:#"address"];
[result addObject:dict];
<end_for>
return result;
By doing like this, you will be able to access sequentially each set of strings; then access each string individually.
In short, instead of encoding your set of strings by concatenating them into another string, you would expand them in a dictionary to make retrieval easier.
I would agree that the best practise here would be to use a dictionary or custom object. That way each string gets stored with its companions (e.g. you have one person's data all together) and you don't have to deal with the messy method you already have implemented. It sounds like you might want to save data, so here's a snippet to help you. If that's not what you're after, let me know and I'll modify my response to help!
Say you have a custom object class Person, where you create and manage data objects to save to disk via the app delegate. You'd do something like:
Person *newPerson = [[Person alloc] init];
[newPerson setName:#"John"];
[newPerson setAge:#"25"];
[newPerson setSex:#"M"];
[yourAppDelegate.newPersonArray insertObject:newPerson atIndex:[mainDelegate.newPersonArray count]];

Loading text from a file

I am making an Iphone drinking card game app.
All the card mean something different and i want the user to be able to press an info button and then show a new screen with information about the current card. How can i make a document to load text from instead of using a bunch og long strings?
Thanks
You could look into plist files - they can be loaded quite easily into the various collection objects and edited with the plist editor in Xcode.
For instance, if you organize your data as a dictionary, the convenience constructor
+ (id)dictionaryWithContentsOfURL:(NSURL *)aURL
from NSDictionary would provide you with as many easily accessible strings as you need.
This method is useful if you consider your strings primarily data as opposed to UI elements.
Update:
As #Alex Nichol suggested, here is how you can do it in practice:
To create a plist file:
In your Xcode project, for instance in the Supporting Files group, select New File > Resource > Property List
You can save the file in en.lproj, to aid in localization
In the Property list editing pane, select Add Row (or just hit return)
Enter a key name (for instance user1) and a value (for instance "Joe")
To read the contents:
NSURL *plistURL = [[NSBundle mainBundle] URLForResource:#"Property List" withExtension:#"plist"];
NSLog(#"URL: %#", plistURL);
NSDictionary *strings = [NSDictionary dictionaryWithContentsOfURL:plistURL];
NSString *user1 = [strings objectForKey:#"user1"];
NSLog(#"User 1: %#", user1);
A plist, a JSON string, and an SQLite database walked into a bar ...
Oops!! I mean those are the three most obvious alternatives. The JSON string is probably the easiest to create and "transport", though it's most practical to load the entire thing into an NSDictionary and/or NSArray, vs read from the file as each string is accessed.
The SQLite DB is the most general, and most speed/storage efficient for a very large number (thousands) of strings, but it takes some effort to set it up.
In my other answer, I suggest the use of a dictionary if your texts are mostly to be considered as data. However, if your strings are UI elements (alert texts, window titles, etc.) you might want to look into strings files and NSBundle's support for them.
Strings files are ideally suited for localization, the format is explained here.
To read them into you app, use something like this:
NSString *text1 = NSLocalizedStringFromTable(#"TEXT1", #"myStringsFile", #"Comment");
If you call your file Localizable.strings, you can even use a simpler form:
NSString *str1 = NSLocalizedString(#"String1", #"Comment on String1");
A useful discussion here - a bit old, but still useful.

Restoring Annotations via Core-Data

Currently, I have an application where a user clicks on a map and adds annotation points with certain subtitles. However, when the phone is power-cycled, all their added points are gone. I'm interested in making these annotations persistent. I've been trying to figure out how to do this with Core-Data, but after reading this tutorial here: http://www.raywenderlich.com/934/core-data-tutorial-getting-started, I'm a bit lost on where to start.
Any help would be appreciated, thanks.
If you have a core data application set up, you will only need to create an entity in the xcdatamodel file. Add attributes for whatever you may want to store.
latitude - double
longitude - double
title - NSString
etc. until you have what you want.
When you want to add an annotation, you should create a new core data object for your entity. It will look something like this
Location *newLocation = (Location *)[NSEntityDescription insertNewObjectForEntityForName:#"Location" inManagedObjectContext:self.managedObjectContext];
Location.latitude = ....
// and so on to store the information you want in its attributes;
You can set the attributes at different point if you change the values at a later point. You just need to be able to access the right object to go with the annotation. You should be able to do this by using NSFetchRequest in your managedObjectContext. You can use NSPredicate to filter the objects to the one you want. Then when you are ready to exit the app, save your context using
NSError *error = nil;
if ([managedObjectContext save:&error]) {
// handle the error;
}
which will store all the objects you've added to be used next time you open the app. You will then be able to create your annotations based on the objects in your managedObjectContext. I hope this is what you were looking for, or at least gives you an idea how to approach what you'd like to do.

Is there any way to get the "Me" card from iPhone Address Book API?

So I'm stumped on this one.
In Mac OS X there is an easy way to get the "Me" card (the owner of the Mac/account) from the built-in address book API.
Has anyone found a way to find out which contact (if it exists) belongs to the owner of the iPhone?
You could use the undocumented user default:
[[NSUserDefaults standardUserDefaults] objectForKey:#"SBFormattedPhoneNumber"];
and then search the address book for the card with that phone number.
Keep in mind that since the User Default is undocumented, Apple could change at any time and you may have trouble getting into the App Store.
Another approach you could take, although it is much more fragile, is to look at the device name. If the user hasn't changed it from the default "User Name's iPhone" AND they are using their real name as an iPhone, you could grab the user name from that. Again, not the best solution by any means, but it does give you something else to try.
The generally accepted answer to this question is to file a Radar with Apple for this feature and to prompt users to choose their card.
Contacts container have a me identifier property on iOS that can be accessed using container.value(forKey: "meIdentifier")
if let containers = try? CNContactStore().containers(matching: nil) {
containers.forEach { container in
if let meIdentifier = container.value(forKey: "meIdentifier") as? String {
print("Contacts:", "meIdentifier", meIdentifier)
}
}
The identifier is a legacy identifier used in the old AddressBook framework. You can still access it in CNContact:
let iOSLegacyIdentifier = contact.value(forKey: "iOSLegacyIdentifier")
There is no such API in the iPhone SDK 2.2.1 and earlier. Please file a request for it at: http://bugreport.apple.com
Edit: [Obsolete answer]
There's no API for getting the "me" card because there is no "me" card. The iPhone's contacts app has no way of marking a card as being "me", and the API reflects this.
I came up with a partial solution to this
you can get the device name as follows
NSString *ownerName = [[UIDevice currentDevice] name];
in English a device is originally called, for example, 'Joe Blogg's iPhone'
the break out the name
NSRange t = [ownerName rangeOfString:#"’s"];
if (t.location != NSNotFound) {
ownerName = [ownerName substringToIndex:t.location];
}
you can then take that name and search the contacts
CNContactStore *contactStore = [CNContactStore new];
NSPredicate *usersNamePredicate = [CNContact predicateForContactsMatchingName:usersName];
NSArray * keysToFetch = #[[CNContactFormatter descriptorForRequiredKeysForStyle:CNContactFormatterStyleFullName],CNContactPhoneNumbersKey,CNContactEmailAddressesKey,CNContactSocialProfilesKey, ];
NSArray * matchingContacts = [contactStore unifiedContactsMatchingPredicate:usersNamePredicate keysToFetch:keysToFetch error:nil];
Of course other languages differ in the device name string e.g. 'iPhone Von Johann Schmidt' so more parsing needs to be done for other languages and it only works if the user hasn't changed the name of the device in iTunes to something like "Joes phone' but it gives you a starting point
well... it gives you an array of matching items :) So if there is more than one contact with that array you just have to use pot luck and go with the first one or work thru multiple cards and take what you need from each.
I did say its a partial solution and even though it won't work for all user cases you might find it works for many of your users and reduces a little friction