Can't Access Contacts Sources on Device in iOS 6 - ios

This code worked OK on iOS 5.1 and also does work in the iPhone simulator with iOS 6. It fails silently on my iPhone 4 running iOS 6. The end result is that I cannot add a person to the Contacts app. Neither of the following code snippets work (log follows each):
ABRecordRef defaultSource = ABAddressBookCopyDefaultSource(_addressBook);
NSLog(#"2 - defaultSource = %#", defaultSource);
AB: Could not compile statement for query (ABCCopyArrayOfAllInstancesOfClassInSourceMatchingProperties):
SELECT ROWID, Name, ExternalIdentifier, Type, ConstraintsPath, ExternalModificationTag, ExternalSyncTag, AccountID, Enabled, SyncData, MeIdentifier, Capabilities FROM ABStore WHERE Enabled = ?;
2012-09-24 11:00:36.731 QR vCard[193:907] 2 - defaultSource = (CPRecord: 0x1f59fd50 ABStore)
When I try to add a person to the Address Book I get this (seems to be because the source is invalid, even though it looks like it might be OK from the above):
2012-09-24 11:18:32.231 QR vCard[220:907] ABAddressBookAddRecord error = The operation couldn’t be completed. (ABAddressBookErrorDomain error 1.)
I thought I could get all the sources and then pick one, but the following returns none at all:
CFArrayRef allSources = ABAddressBookCopyArrayOfAllSources (_addressBook);
NSLog(#"2 - allSources = %#", allSources);
AB: Could not compile statement for query (ABCCopyArrayOfAllInstancesOfClassInSourceMatchingProperties):
SELECT ROWID, Name, ExternalIdentifier, Type, ConstraintsPath, ExternalModificationTag, ExternalSyncTag, AccountID, Enabled, SyncData, MeIdentifier, Capabilities FROM ABStore WHERE Enabled = ?;
2012-09-24 10:58:09.908 QR vCard[177:907] 2 - allSources = ()

I had the same issue and I could not get the Allow Access To Contacts alert to popup.
The Answer was posted by Kyle here:
https://stackoverflow.com/a/12648938/480415
// Request authorization to Address Book
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, NULL);
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) {
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
// First time access has been granted, add the contact
});
}
else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
// The user has previously given access, add the contact
}
else {
// The user has previously denied access
// Send an alert telling user to change privacy setting in settings app
}

This log message is an indication that your app is not (maybe not yet) allowed to access Contacts. iOS 6 gives users the possibility to deny apps the permission to access the address book.
The message disappears once the user has allowed your app access to Contacts - either via the pop up dialog, or by going to Settings -> Privacy -> Contacts.
For more infos on this topic, see WWDC 2012 session 710 "Privacy support in iOS and OS X".

If you got here from Google and you're using iOS's new CNContactStore framework, and getting these errors, read on:
I thought it'd be cleaner to make my CNContactStore a member variable that was initialized with the class instance:
class foo {
var contactStore = CNContactStore()
func findByIdentifier(identifier: String) -> CNContact {
let contact = try self.contactStore.unifiedContactWithIdentifier(identifier...
return contact
}
}
After I called this about fifty times, it started erroring out with
AB: Could not compile statement for query (ABCCopyArrayOfAllInstancesOfClassInSourceMatchingProperties)
I tried rate-limiting my calls, but that didn't help. It turned out that instantiating a new CNContactStore for every call had zero performance ramifications and completely solved the problem for me:
class foo {
func findByIdentifier(identifier: String) -> CNContact {
let contactStore = CNContactStore()
let contact = try contactStore.unifiedContactWithIdentifier(identifier...
return contact
}
}
Hope this helps!

Related

The app with id xxxxxxxx does not have the right view_structure on app with id yyyy

I'm trying to create a new item in a podio app in an iOS app with. The following lines of code as per the documentation in podio but I am having a 403 error with the subject as the error message. I assumed that I needed to authenticate the session as an app as I think that it will use the app id to create the PKTItem in the app used to sign in.
let item = PKTItem(forAppWithID: lastItem + 1)
item?.setValue(1, forField: "service")
item?.setValue(testPet, forField: "pet")
item?.save().onComplete({(response, error) in
if (error != nil){
print("error: \(error)")
}
else{
print("response")
}
})
I was in the assumption that the item would be created in the app that was used to sign with the item having an id of lastItem + 1, I tried to see if the object's IDs are correct
//this is supposed to be lastItem + 1
print("PKTItem ID: \(item?.appItemID)")
//this is supposed to be the app ID where it should create the new item
print("PKTItem AppID: \(item?.appID)")
PKTItem ID: Optional(0)
PKTItem AppID: Optional(1)
I don't what I was doing wrong. Please assist me.
Sounds like you are using the wrong set of credentials to look at the app item you are attempting to work with. How did you authorize? Do you have the right App ID and Token combination for this script?
Typically you will see [PodioKit authenticateAutomaticallyAsAppWithID:123456 token:#"my-app-token"]; and that allows you to interact with anything in the the app 123456, but it sounds like you are trying to interact with data in 2 apps based on the error message. If this is a small scale implementation (or for testing purposes) you can use the Authenticate as User method instead. Here is the documentation they have on Authentication for the Objective C library.
PKTAsyncTask *authTask = [PodioKit authenticateAsUserWithEmail:#"myname#mydomain.com" password:#"p4$$w0rD"];
[authTask onComplete:^(PKTResponse *response, NSError *error) {
if (!error) {
// Successfully authenticated
} else {
// Failed to authenticate, double check your credentials
}
}];

Mark Notifications as Read in iOS 11 with CloudKit

I have an app that uses CloudKit and everything worked perfectly until iOS 11.
In previous iOS versions, I used a CKQuerySubscription with NSPredicate to receive notifications whenever a user changes a specific table, matching the NSPredicate properties.
After doing so, whenever the server sent notifications, I would iterate through them, fetching its changes and afterwards marking them as READ so I would parse through them again (saving a serverToken).
Now in iOS 11 , Xcode informs me that those delegates are deprecated, and I should change them, but this is where I'm having trouble with - I cannot figure out how to do it in the non-deprecated way, for iOS 11.
Here's my code:
Saving a subscription
fileprivate func setupCloudkitSubscription() {
let userDefaults = UserDefaults.standard
let predicate = NSPredicate(format: /*...*/) // predicate here
let subscription = CKQuerySubscription(recordType: "recordType", predicate: predicate, subscriptionID: "tablename-changes", options: [.firesOnRecordUpdate, .firesOnRecordCreation])
let notificationInfo = CKNotificationInfo()
notificationInfo.shouldSendContentAvailable = true // if true, then it will push as a silent notification
subscription.notificationInfo = notificationInfo
let publicDB = CKContainer.default().publicCloudDatabase
publicDB.save(subscription) { (subscription, err) in
if err != nil {
print("Failed to save subscription:", err ?? "")
return
}
}
}
Check for pending notifications
fileprivate func checkForPendingNotifications() {
let serverToken = UserDefaults.standard.pushNotificationsChangeToken
let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: serverToken)
var notificationIDsToMarkRead = [CKNotificationID]()
operation.notificationChangedBlock = { (notification) -> Void in
if let notificationID = notification.notificationID {
notificationIDsToMarkRead.append(notificationID)
}
}
operation.fetchNotificationChangesCompletionBlock = {(token, err) -> Void in
if err != nil {
print("Error occured fetchNotificationChangesCompletionBlock:", err ?? "")
print("deleting existing token and refetch pending notifications")
UserDefaults.standard.pushNotificationsChangeToken = nil
return
}
let markOperation = CKMarkNotificationsReadOperation(notificationIDsToMarkRead: notificationIDsToMarkRead)
markOperation.markNotificationsReadCompletionBlock = { (notificationIDsMarkedRead: [CKNotificationID]?, operationError: Error?) -> Void in
if operationError != nil {
print("ERROR MARKING NOTIFICATIONS:", operationError ?? "")
return
}
}
let operationQueue = OperationQueue()
operationQueue.addOperation(markOperation)
if token != nil {
UserDefaults.standard.pushNotificationsChangeToken = token
}
}
let operationQueue = OperationQueue()
operationQueue.addOperation(operation)
}
As you can see, the code above works perfectly on iOS until 11;
Now Xcode prompts warning on the following lines:
let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: serverToken)
Warning:
'CKFetchNotificationChangesOperation' was deprecated in iOS 11.0: Instead of iterating notifications to enumerate changed record zones, use CKDatabaseSubscription, CKFetchDatabaseChangesOperation, and CKFetchRecordZoneChangesOperation
AND
let markOperation = CKMarkNotificationsReadOperation(notificationIDsToMarkRead: notificationIDsToMarkRead)
Warning:
'CKMarkNotificationsReadOperation' was deprecated in iOS 11.0: Instead of iterating notifications, consider using CKDatabaseSubscription, CKFetchDatabaseChangesOperation, and CKFetchRecordZoneChangesOperation as appropriate
I tried applying CKDatabaseSubscription but by doing so I cannot apply a NSPredicate to filter the subscription as I can do with CKQuerySubscription, and if I try to fetch and mark pending notifications
as Read it shows those warnings.
What's the best approach for iOS 11 in this case? Any hint?
Thank you.
So as I've mentioned in the comment above in the openradar bug report , which can be found here this is a known issue in iOS 11 when fetching changes for public records and then mark those changes as read, saving the given token.
As there's not a true solution for this issue, because Apple hasn't gave a workaround for this, or maybe not marking those delegate-functions as deprecated until a solution is given I had to go through a different path, which was the follwowing:
I had to create a custom CKRecordZone in the Private Database, then I had to subscribe database changes in that zoneID, by doing so, whenever the user changes something in that database the desired push-notifications and/or silent-push-notifications fire as expected and then I can parse the new data.
My issue here is that I had the User Profile in a public database, so whenever the user changed something related to him (Name, Bio, etc) it saved in CloudKit and silent notifications would fire to the user's other devices to update this data - this I can do perfectly with the private database as well - but my problem was that other users could search for app-users to follow-unfollow them and if that data is stored in the private-database it will be out of general users search scope.
In order to overcome this I had to semi-duplicate the User Profile data.
The user fetches and edits its data through the private-database, and on save it also update a semi-related table in the public-database so it is available to search for general-users.
Until Apple allows us to fetch changes from public-database as we used to do in iOS 10 this solution will work for me temporarily.

Address book change callback registered with ABAddressBookRegisterExternalChangeCallback is never called (iOS 8)

I've found plenty of examples around this but after reading the entire ABAddressBook documentation I'm still not able to figure out why, in my case, my change callback is not being called. I simply set up an address book and register a callback function for it.
I can access the address book just fine, but the callback function is never called no matter how much I change contacts in the Contacts app and then reopen my app. Is there any reason that the callback would never be called? I've already made sure I don't release the address book or unregister the callback.
The init code:
// Set up address book API.
CFErrorRef *error = NULL;
_addressBook = ABAddressBookCreateWithOptions(NULL, error);
if (error) {
NSLog(#"Could not initialize address book: %#", CFBridgingRelease(CFErrorCopyFailureReason(*error)));
} else {
ABAddressBookRegisterExternalChangeCallback(_addressBook, RogerAddressBookChangeCallback, (__bridge void *)self);
NSLog(#"Registered callback");
}
The callback function:
void RogerAddressBookChangeCallback(ABAddressBookRef addressBook, CFDictionaryRef info, void *context) {
NSLog(#"Address book change");
ABAddressBookRevert(addressBook);
RogerAddressBook *instance = (__bridge RogerAddressBook *)context;
[instance import];
}
I see the log output Registered callback but never Address book change.
Actually the code for ABAddressBook is written in C. So you may find difficulties in using the Original ABAddressBook Framework.
So I suggest for using an third party library (Which is just an makeover of C to Obj-C) to access contacts and the Contacts Change.
Here is the link for an popular library https://github.com/Alterplay/APAddressBook
Using the above framework you could easily observe the changes in Address book.
Observe address book external changes
// start observing
[addressBook startObserveChangesWithCallback:^
{
NSLog(#"Address book changed!");
}];
// stop observing
[addressBook stopObserveChanges];
This library also has lot of options like sorting, filtering etc.
Access to address book requires user authorization. If authorization status is kABAuthorizationStatusNotDetermined, your code fails silently, returning non-nil result and producing no error.
I have next code to create address book:
- (ABAddressBookRef)newAddressBookRef
{
ABAuthorizationStatus authorizationStatus = ABAddressBookGetAuthorizationStatus();
if (authorizationStatus == kABAuthorizationStatusAuthorized)
{
ABAddressBookRef addressBookRef = nil;
CFErrorRef error;
addressBookRef = ABAddressBookCreateWithOptions(NULL, &error);
return addressBookRef;
}
return nil;
}
And next code to explicitly request address book access (usually performed on application start).
typedef void(^AddressBookHelperAccessRequestCompletionHandler)(BOOL accessGiven);
- (void)requestAccessToAddressBook:(ABAddressBookRef)addressBookRef withCompletionHandler:(AddressBookHelperAccessRequestCompletionHandler)completionHandler
{
ABAuthorizationStatus authorizationStatus = ABAddressBookGetAuthorizationStatus();
switch (authorizationStatus)
{
case kABAuthorizationStatusNotDetermined:
{
// Request access permissions for even for NULL address book reference.
// When permissions have not been granted yet, all address book references will be equal to NULL
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error)
{
if (granted)
{
[self registerForAddressBookChanges];
}
if (completionHandler)
{
completionHandler(granted);
}
});
break;
}
case kABAuthorizationStatusDenied:
case kABAuthorizationStatusRestricted:
[self showNoContactsAccessAlert];
default:
{
if (completionHandler)
{
completionHandler(authorizationStatus == kABAuthorizationStatusAuthorized);
}
break;
}
}
}

Access "My Info" from iOS app [duplicate]

Outside of asking the user to input their name, is there any way to get it off the device?
I tried this library, which attempts to extract the name from [UIDevice currentDevice] name], but that doesn't work in a lot of situations:
https://github.com/tiboll/TLLNameFromDevice
Is the user's name present in the phonebook or anywhere else that we have access to in iOS 6?
Well you could go through all the contacts in the AddressBook and see if any of them are marked with the owner flag.
Just be aware that doing this will popup the "this app wants access to the address book" message. Also Apple isn't very keen on these kind of things. In the app review guide it is specified that an app can not use personal information without the user's permission.
You could use Square's solution:
Get the device's name (e.g. "John Smith's iPhone").
Go through the contacts on the phone and look for a contact named "John Smith".
JBDeviceOwner and ABGetMe will both do this for you.
You could use CloudKit. Following a snippet in Swift (ignoring errors):
let container = CKContainer.defaultContainer()
container.fetchUserRecordIDWithCompletionHandler(
{
(recordID, error) in
container.requestApplicationPermission(
.PermissionUserDiscoverability,
{
(status, error2) in
if (status == CKApplicationPermissionStatus.Granted)
{
container.discoverUserInfoWithUserRecordID(
recordID,
completionHandler:
{
(info, error3) in
println("\(info.firstName) \(info.lastName)")
}
)
}
}
)
}
)
The above code was based on the code at http://www.snip2code.com/Snippet/109633/CloudKit-User-Info
to save folks time. in swift4:
let container = CKContainer.default()
container.fetchUserRecordID(
completionHandler: {
(recordID, error) in
guard let recordID = recordID else {
return
}
container.requestApplicationPermission(
.userDiscoverability,
completionHandler: {
(status, error2) in
if (status == CKContainer_Application_PermissionStatus.granted)
{
if #available(iOS 10.0, *) {
container.discoverUserIdentity(withUserRecordID:
recordID,
completionHandler:
{
(info, error3) in
guard let info = info else {
return
}
print("\(info.firstName) \(info.lastName)")
}
)
}
}
}
)
}
)
however: CKUserIdentity no longer exposes either first or last name
So this answer no longer works.
You can use:
NSLog(#"user == %#",[[[NSHost currentHost] names] objectAtIndex:0]);
I did receive compiler warnings that the methods +currentHost and -names were not found. Given the warning, I’m not sure of Apple’s intention to make this available (or not) as a publicly accessible API, however, everything seemed to work as expected without the need to include any additional header files or linking in additional libraries/frameworks.
Edit 1:
You may also take a look at this Link
Edit 2:
If you have integrated your app with Facebook you can easily retrieve the user info, see Facebook Fetch User Data
For SWIFT you can use
NSUserName() returns the logon name of the current user.
func NSUserName() -> String

Catch 22 according to the documentation for address book use with iOS 6

According to the documentation with iOS6 an address book should be created using ABAddressBookCreateWithOptions.
It also says if the caller does not have access to the db then this method will return null.
However access is requested by calling ABAddressBookRequestAccessWithCompletion, which takes an ABAddressBookRef as a parameter.
So according to the documentation you can't get an ABAddressBookRef in the first place if you don't have access, but to get access you have to have an ABAddressBookRef to pass as a parameter.
Eh. Catch 22? How do you create an ABAddressBookRef then?
Been googling for some example/tutorial code for this but haven't found any.
TIA
I use code like this:
if (ABAddressBookCreateWithOptions) {
_book = ABAddressBookCreateWithOptions(NULL, NULL);
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) {
ABAddressBookRequestAccessWithCompletion(_book, ^(bool granted, CFErrorRef error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (granted && !error) {
ABAddressBookRevert(_book);
}
});
});
}
} else {
_book = ABAddressBookCreate();
}
where _book is my ABAddressBookRef.
If my app has been denied access then _book will be NULL and you can't access the address book.
See my answer here:
https://stackoverflow.com/a/13671852/341994
ABAddressBookRequestAccessWithCompletion is useless and should be ignored.
If you need to know the authorization status, call ABAddressBookGetAuthorizationStatus.
If the authorization is undetermined (the user has never run this app), just attempt to access the database. This will cause the authorization alert to appear.
If the user has authorized or denied access, ABAddressBookRequestAccessWithCompletion does nothing of any value so there's no point calling it.

Resources