Can't save address to ABRecordRef - objective-c

I'm trying to add an address to picked contact:
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
didSelectPerson:(ABRecordRef)person{
// Adding address
ABMutableMultiValueRef addressMultipleValue = ABMultiValueCreateMutable(kABMultiDictionaryPropertyType);
NSMutableDictionary *addressDictionary = [[NSMutableDictionary alloc] init];
[addressDictionary setObject:#"8-15 Dereham Place" forKey:(NSString *)kABPersonAddressStreetKey];
[addressDictionary setObject:#"London" forKey:(NSString *)kABPersonAddressCityKey];
[addressDictionary setObject:#"EC2A 3HJ" forKey:(NSString *)kABPersonAddressZIPKey];
[addressDictionary setObject:#"United Kingdom" forKey:(NSString *)kABPersonAddressCountryKey];
[addressDictionary setObject:#"gb" forKey:(NSString *)kABPersonAddressCountryCodeKey];
ABMultiValueAddValueAndLabel(addressMultipleValue, (__bridge CFTypeRef)(addressDictionary), kABHomeLabel, NULL);
ABRecordSetValue(person, kABPersonAddressProperty, addressMultipleValue, nil);
CFErrorRef anError = NULL;
BOOL didSet;
didSet = ABAddressBookSave(_addressBook, NULL);
if (!didSet) {
NSError *er = (__bridge NSError *)(anError);
NSLog(#"Error saving record: %#", er.localizedDescription);}else{
NSLog(#"Record updated successfully");
}
CFRelease(addressMultipleValue);
}
As I see in console, there is no problem with saving it. However, when I open contact in Contacts App there is no saved address.

The release notes are REALLY helpful. Neither item referenced: "ABPeoplePickerNavigationController.h" or the new "PeoplePicker: Picking a Person or Property sample project" can be found.
Apple's "enhancement" has hosed 20 business apps of mine that rely on AB for contact info. Thanks again Apple for fixing something that wasn't broke.

Citing from iOS 8 Release Notes document:
The Address Book UI people picker has been changed for iOS 8. A new mode with new API has been added where the app does not need access to the user’s contacts and the user will not be prompted for access. A temporary copy of the selected person is returned to the app. See ABPeoplePickerNavigationController.h for more details.
See the new PeoplePicker: Picking a Person or Property sample project demonstrating usage of the new mode.
So you gonna meet the new API, until that, you're probably working just with temporary copies rather than actual database, that actually Contacts app accesses.

Related

Apple Music API - Create a Playlist

I have been exploring the Apple Music API to see what kind of functionality I can expect to be able to use in an iOS app. I have created a little test app that gains permission from the user and outputs the playlists I have (and songs) to NSLog.
MPMediaQuery *myPlaylistsQuery = [MPMediaQuery playlistsQuery];
[myPlaylistsQuery setGroupingType:MPMediaGroupingPlaylist];
NSArray *playlists = [myPlaylistsQuery collections];
for (MPMediaPlaylist *playlist in playlists) {
NSLog (#"%#", [playlist valueForProperty: MPMediaPlaylistPropertyName]);
NSArray *songs = [playlist items];
for (MPMediaItem *song in songs) {
NSString *songTitle =
[song valueForProperty: MPMediaItemPropertyTitle];
NSLog (#"\t\t%#", songTitle);
}
}
From this, I have been able to deduce the following (but I'm not 100% certain):
the playlist (basic info: name, id) is stored locally on the device
the playlist songs are also pulled from local storage but if the playlist hasn't been downloaded to the device it goes off to Apple to grab the song list.
So far, so good. What I want to know is:
is there a way of creating a playlist from my app (via the API)?
I know there is an MPMediaPlaylist addItem and add method but can't seem to find a way of creating the new playlist itself.
According to this page it should be possible: https://affiliate.itunes.apple.com/resources/blog/apple-music-api-faq/
Can a developer create brand new playlists on the user’s device with the Apple Music API?
Yes. The API allows develops to new create playlists on the user’s device.
I've figured this out. If you use the following code you can generate a new playlist and perform an action on it.
NSUUID *uuid = [NSUUID UUID]; //uuid for the playlist
[[MPMediaLibrary defaultMediaLibrary] getPlaylistWithUUID:uuid creationMetadata:[[MPMediaPlaylistCreationMetadata alloc] initWithName:#"YOUR PLAYLIST NAME"] completionHandler:^(MPMediaPlaylist * _Nullable playlist, NSError * _Nullable error) {
NSLog(#"%#", error);
if (!error) {
NSLog(#"All ok let's do some stuff with the playlist!");
}
}];
Apple's documentation on the whole API is severely lacking in terms of sample code and practical examples!

Delegate for ios app upgrade

Is there any delegate method that will be called when the user upgrades to or reinstalls a newer version of the iOS app?
I use Core Data to cache some information from server. When the schema of any entity is changed, I need to manually delete the SQLite database from the simulator, otherwise the app will crash on startup, with an error "The model used to open the store is incompatible with the one used to create the store." If there is any delegate method for app upgrade, the deletion could be automated.
You need to use CoreData versioning:
http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/CoreDataVersioning/Articles/Introduction.html
Daniel Smith's answer is the proper one, but I just want to add how my app determines its been updated. I look keep a 'current version' string in the defaults. When the app starts up, I compare it to the current version:
defaults has no string - this is the first run of the app
defaults version is different - the user updated the app
defaults is the same - user just restarted the app
Sometimes its nice to know the above. Make sure to save the defaults immediately after you set the tag and do whatever versioning you want, so a crash doesn't have you do it again.
EDIT: how not to crash if he model changes. I use this now, keep the old repository, and tweaking the model, on every tweak it just removes the old one (if it cannot open it) and creates a new one. This is modeled on Apple's code but not sure about what changes I made. In any case you don't get a crash if the model changes.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
//LTLog(#"_persistentStoreCoordinator = %#", _persistentStoreCoordinator);
if (_persistentStoreCoordinator)
{
return _persistentStoreCoordinator;
}
NSFileManager *manager = [NSFileManager defaultManager];
NSString *path = [[appDelegate applicationAppSupportDirectory] stringByAppendingPathComponent:[_dbName stringByAppendingPathExtension:#"SQLite"]];
storeURL = [NSURL fileURLWithPath:path];
BOOL fileExists = [manager fileExistsAtPath:path];
if(!fileExists) {
_didCreateNewRepository = YES;
}
if(_createNewRepository) {
[manager removeItemAtURL:storeURL error:nil];
if(fileExists) _didDestroyOldRepository = YES;
_didCreateNewRepository = YES;
}
while(YES) {
__autoreleasing NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if ([_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
break;
} else {
_persistentStoreCoordinator = nil;
[manager removeItemAtURL:storeURL error:&error];
if(fileExists) {
_didDestroyOldRepository = YES; // caller didn't want a new one but got a new one anyway (old one corrupt???)
_didCreateNewRepository = YES;
}
#ifndef NDEBUG
LTLog(#"CORE DATA failed to open store %#: error=%#", _dbName, error);
#endif
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible
* The schema for the persistent store is incompatible with current managed object model
Check the error message to determine what the actual problem was.
*/
//LTLog(#"Unresolved error %#, %#", error, [error userInfo]);
//abort();
}
}
return _persistentStoreCoordinator;
}
Follow the blog its good:
http://blog.10to1.be/cocoa/2011/11/28/core-data-versioning/

Importing VCard in objective C in iPhone

I am developping an iPhone App using XCode 4.2 .in a portion of the App I will be getting a VCard as an NSString
I found this function initWithVCardRepresentation and i think it will be easier than parsing the data one by one (i.e getting the first name then the last name etc ... , but I have a hard time to implement it in my code .
I have the AddressBook and the AddressBookUI frameworks and I am trying to use this code but can t find an exact way to do it
-(IBAction)Add{
// I have a NSString *card defined somewhere else
ABAddressBookRef *iPhoneAddressBook = ABAddressBookCreate();
ABRecordRef *contact = ABPersonCreatePeopleInSourceWithVCardRepresentation(iPhoneAddressBook, (__bridge_retained CFStringRef) card);
CFRelease(contact);
CFRelease(iPhoneAddressBook);
}
when I compile ,it crashes at the line
ABRecordRef *contact = ABPersonCreatePeopleInSourceWithVCardRepresentation(iPhoneAddressBook, (__bridge_retained CFStringRef) card);
and I get the following green error in the #autoreleasepool
Thread1:Program Received Signal "SIGABRT".
I am quite new to the Apps development , please let me know if the information I gave is sufficient
Thanks
If you want to have an ABPerson afterwards (what is advisable), use:
// Assuming NSString *card exists already.
ABPerson *person = [[[ABPerson alloc] initWithVCardRepresentation:[card dataUsingEncoding:NSUTF8StringEncoding] autorelease];

Calling -[NSFileManager setUbiquitous:itemAtURL:destinationURL:error:] never returns

I have a straightforward NSDocument-based Mac OS X app in which I am trying to implement iCloud Document storage. I'm building with the 10.7 SDK.
I have provisioned my app for iCloud document storage and have included the necessary entitlements (AFAICT). The app builds, runs, and creates the local ubiquity container Documents directory correctly (this took a while, but that all seems to be working). I am using the NSFileCoordinator API as Apple recommended. I'm fairly certain I am using the correct UbiquityIdentifier as recommended by Apple (it's redacted below tho).
I have followed Apple's iCloud Document storage demo instructions in this WWDC 2011 video closely:
Session 107 AutoSave and Versions in Lion
My code looks almost identical to the code from that demo.
However, when I call my action to move the current document to the cloud, I experience liveness problems when calling the -[NSFileManager setUbiquitous:itemAtURL:destinationURL:error:] method. It never returns.
Here is the relevant code from my NSDocument subclass. It is almost identical to Apple's WWDC demo code. Since this is an action, this is called on the main thread (as Apple's demo code showed). The deadlock occurs toward the end when the -setUbiquitous:itemAtURL:destinationURL:error: method is called. I have tried moving to a background thread, but it still never returns.
It appears that a semaphore is blocking while waiting for a signal that never arrives.
When running this code in the debugger, my source and destination URLs look correct, so I'm fairly certain they are correctly calculated and I have confirmed the directories exist on disk.
Am I doing anything obviously wrong which would lead to -setUbiquitous never returning?
- (IBAction)moveToOrFromCloud:(id)sender {
NSURL *fileURL = [self fileURL];
if (!fileURL) return;
NSString *bundleID = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleIdentifier"];
NSString *appID = [NSString stringWithFormat:#"XXXXXXX.%#.macosx", bundleID];
BOOL makeUbiquitous = 1 == [sender tag];
NSURL *destURL = nil;
NSFileManager *mgr = [NSFileManager defaultManager];
if (makeUbiquitous) {
// get path to local ubiquity container Documents dir
NSURL *dirURL = [[mgr URLForUbiquityContainerIdentifier:appID] URLByAppendingPathComponent:#"Documents"];
if (!dirURL) {
NSLog(#"cannot find URLForUbiquityContainerIdentifier %#", appID);
return;
}
// create it if necessary
[mgr createDirectoryAtURL:dirURL withIntermediateDirectories:NO attributes:nil error:nil];
// ensure it exists
BOOL exists, isDir;
exists = [mgr fileExistsAtPath:[dirURL relativePath] isDirectory:&isDir];
if (!(exists && isDir)) {
NSLog(#"can't create local icloud dir");
return;
}
// append this doc's filename
destURL = [dirURL URLByAppendingPathComponent:[fileURL lastPathComponent]];
} else {
// get path to local Documents folder
NSArray *dirs = [mgr URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
if (![dirs count]) return;
// append this doc's filename
destURL = [[dirs objectAtIndex:0] URLByAppendingPathComponent:[fileURL lastPathComponent]];
}
NSFileCoordinator *fc = [[[NSFileCoordinator alloc] initWithFilePresenter:self] autorelease];
[fc coordinateWritingItemAtURL:fileURL options:NSFileCoordinatorWritingForMoving writingItemAtURL:destURL options:NSFileCoordinatorWritingForReplacing error:nil byAccessor:^(NSURL *fileURL, NSURL *destURL) {
NSError *err = nil;
if ([mgr setUbiquitous:makeUbiquitous itemAtURL:fileURL destinationURL:destURL error:&err]) {
[self setFileURL:destURL];
[self setFileModificationDate:nil];
[fc itemAtURL:fileURL didMoveToURL:destURL];
} else {
NSWindow *win = ... // get my window
[self presentError:err modalForWindow:win delegate:nil didPresentSelector:nil contextInfo:NULL];
}
}];
}
I don't know if these are the source of your problems, but here are some things I'm seeing:
-[NSFileManager URLForUbiquityContainerIdentifier:] may take a while, so you shouldn't invoke it on the main thread. see the "Locating the Ubiquity Container" section of this blog post
Doing this on the global queue means you should probably use an allocated NSFileManager and not the +defaultManager.
The block passed to the byAccessor portion of the coordinated write is not guaranteed to be called on any particular thread, so you shouldn't be manipulating NSWindows or presenting modal dialogs or anything from within that block (unless you've dispatched it back to the main queue).
I think pretty much all of the iCloud methods on NSFileManager will block until things complete. It's possible that what you're seeing is the method blocking and never returning because things aren't configured properly. I'd double and triple check your settings, maybe try to simplify the reproduction case. If it still isn't working, try filing a bug or contacting DTS.
Just shared this on Twitter with you, but I believe when using NSDocument you don't need to do any of the NSFileCoordinator stuff - just make the document ubiquitous and save.
Hmm,
did you try not using a ubiquity container identifier in code (sorry - ripped out of a project so I've pseudo-coded some of this):
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *iCloudDocumentsURL = [[fm URLForUbiquityContainerIdentifier:nil] URLByAppendingPathComponent:#"Documents"];
NSURL *iCloudFileURL = [iCloudDocumentsURL URLByAppendingPathComponent:[doc.fileURL lastPathComponent]];
ok = [fm setUbiquitous:YES itemAtURL:doc.fileURL destinationURL:iCloudRecipeURL error:&err];
NSLog(#"doc moved to iCloud, result: %d (%#)",ok,doc.fileURL.fileURL);
And then in your entitlements file:
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>[devID].com.yourcompany.appname</string>
</array>
Other than that, your code looks almost identical to mine (which works - except I'm not using NSDocument but rolling it all myself).
If this is the first place in your code that you are accessing iCloud look in Console.app for a message like this:
taskgated: killed yourAppID [pid 13532] because its use of the com.apple.developer.ubiquity-container-identifiers entitlement is not allowed
Anytime you see this message delete your apps container ~/Library/Containers/<yourAppID>
There may also be other useful messages in Console.app that will help you solve this issue.
I have found that deleting the app container is the new Clean Project when working with iCloud.
Ok, So I was finally able to solve the problem using Dunk's advice. I'm pretty sure the issue I was having is as follows:
Sometime after the WWDC video I was using as a guide was made, Apple completed the ubiquity APIs and removed the need to use an NSFileCoordinator object while saving from within an NSDocument subclass.
So the key was to remove both the creation of the NSFileCoordinator and the call to -[NSFileCoordinator coordinateWritingItemAtURL:options:writingItemAtURL:options:error:byAccessor:]
I also moved this work onto a background thread, although I'm fairly certain that was not absolutely required to fix the issue (although it was certainly a good idea).
I shall now submit my completed code to Google's web crawlers in hopes of assisting future intrepid Xcoders.
Here's my complete solution which works:
- (IBAction)moveToOrFromCloud:(id)sender {
NSURL *fileURL = [self fileURL];
if (!fileURL) {
NSBeep();
return;
}
BOOL makeUbiquitous = 1 == [sender tag];
if (makeUbiquitous) {
[self displayMoveToCloudDialog];
} else {
[self displayMoveFromCloudDialog];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self doMoveToOrFromCloud:makeUbiquitous];
});
}
- (void)doMoveToOrFromCloud:(BOOL)makeUbiquitous {
NSURL *fileURL = [self fileURL];
if (!fileURL) return;
NSURL *destURL = nil;
NSFileManager *mgr = [[[NSFileManager alloc] init] autorelease];
if (makeUbiquitous) {
NSURL *dirURL = [[MyDocumentController instance] ubiquitousDocumentsDirURL];
if (!dirURL) return;
destURL = [dirURL URLByAppendingPathComponent:[fileURL lastPathComponent]];
} else {
// move to local Documentss folder
NSArray *dirs = [mgr URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
if (![dirs count]) return;
destURL = [[dirs firstObject] URLByAppendingPathComponent:[fileURL lastPathComponent]];
}
NSError *err = nil;
void (^completion)(void) = nil;
if ([mgr setUbiquitous:makeUbiquitous itemAtURL:fileURL destinationURL:destURL error:&err]) {
[self setFileURL:destURL];
[self setFileModificationDate:nil];
completion = ^{
[self hideMoveToFromCloudDialog];
};
} else {
completion = ^{
[self hideMoveToFromCloudDialog];
NSWindow *win = [[self canvasWindowController] window];
[self presentError:err modalForWindow:win delegate:nil didPresentSelector:nil contextInfo:NULL];
};
}
dispatch_async(dispatch_get_main_queue(), completion);
}

Adding a new record programmatically to a Cocoa Core Data Entity

i'm making a simple document-based application in Cocoa. Each document of this app should basically manage an array of Dates and Notes, so each record is a date and a note (textview). Also each document is protected by password.
To do that i created a Core Data entity called HistoryElement (that contains a date and a notes attribute), i also created a Settings entity that should have only a record which contains the password to open the file (i didn't found a better method, there is one ? The password is tied to each file, so i can't use preferences because it's not a global application password).
I have a preference tab which contain a Password textfield that binds to the password attribute of the Settings entity.
Ok...the problem is now this: when i create a new document there are no records on the Settings entity, so i wish to programmatically add one, so the user can put (if it want to protect it's file) the password in the password text field.
Instead, if i'm opening an existing file it should find that a record for Settings entity has been already add and it shouldn't create it again, instead the password text field should use this one.
I tried many ways, but i'm unable to do that. I tried for example this:
if([[settingsArrayController arrangedObjects] count] == 0) {`
NSLog(#"Init settings");`
[settingsArrayController add:self];`
}
It seems that it adds a new record when i create a new document, but if i put a password in the password text field and then save the document, when i open the document again the [[settingsArrayController arrangedObjects] count] returns 0 and it creates a new record again...
How can i do that ? There is a better/simple/elegant way to protect a document with a password ?
You need to add the code to NSPersistentDocument after the managedObjectContext has been initialized and the data has been loaded. It's hard to say what's wrong with the sample code you posted without knowing where you put it.
One easy place you could put it is in the windowControllerDidLoadNib. For example,
- (void)windowControllerDidLoadNib:(NSWindowController *)windowController
{
[super windowControllerDidLoadNib:windowController];
// user interface preparation code
NSManagedObjectContext *moc = [self managedObjectContext];
NSSet *settings = [moc fetchObjectsForEntityName:#"Settings"
withPredicate:nil];
if ([settings count] == 0)
{
NSManagedObject *obj = [NSEntityDescription insertNewObjectForEntityForName:#"Settings"
inManagedObjectContext:moc];
[obj setValue:#"myPass" forKey:#"password"];
} else {
// password record already exists, do something else.
}
}
Note that I am using a category on NSManagedObjectContext that does all the boiler plate query stuff:
// Convenience method to fetch the array of objects for a given Entity
// name in the context, optionally limiting by a predicate or by a predicate
// made from a format NSString and variable arguments.
//
- (NSSet *)fetchObjectsForEntityName:(NSString *)newEntityName
withPredicate:(id)stringOrPredicate, ...
{
NSEntityDescription *entity = [NSEntityDescription
entityForName:newEntityName inManagedObjectContext:self];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
if (stringOrPredicate)
{
NSPredicate *predicate;
if ([stringOrPredicate isKindOfClass:[NSString class]])
{
va_list variadicArguments;
va_start(variadicArguments, stringOrPredicate);
predicate = [NSPredicate predicateWithFormat:stringOrPredicate
arguments:variadicArguments];
va_end(variadicArguments);
}
else
{
NSAssert2([stringOrPredicate isKindOfClass:[NSPredicate class]],
#"Second parameter passed to %s is of unexpected class %#",
sel_getName(_cmd), [stringOrPredicate className]);
predicate = (NSPredicate *)stringOrPredicate;
}
[request setPredicate:predicate];
}
NSError *error = nil;
NSArray *results = [self executeFetchRequest:request error:&error];
if (error != nil)
{
[NSException raise:NSGenericException format:#"%#",[error description]];
}
return [NSSet setWithArray:results];
}
And that should be all there is to it.
I have created a quick sample project that illustrates what you need to do at: http://dl.dropbox.com/u/21359504/coredata-example.zip