Contacts

RSS for tag

Access the user's contacts and format and localize contact information using Contacts.

Posts under Contacts tag

44 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

Contacts: remove member from group not working on macOS
Hi, In my app, I have an option to remove a contact from a contact group (using the Contacts framework), and it's been working fine till recently users of the macOS version reported that it's not working. I have been using the CNSaveRequest removeMember(contact, from: group) API. The same API works fine on iOS. I'm not sure when it started but it seems to be affecting macOS14.6 as well as 15.1. I was able to reproduce it in a small test project as well, and have the same experience (the API works on iOS but not on macOS), so it definitely seems like a problem with the framework. Can someone confirm this, and/or suggest a workaround? Here's the code I run to test it out ...a simple SwiftUI view that has 4 buttons: Create contact and group Add contact to group Remove contact from group (optional) cleanup by deleting contact and group It's the 3rd step that seems to fail on macOS, but works fine on iOS. Here's the code to test it out: struct ContentView: View { let contactsModel = ContactsStoreModel() var body: some View { VStack (alignment: .center, spacing: 15){ Button ("1. Add Contact And Group") { print("add contact button pressed") contactsModel.addTestContact() if let _ = contactsModel.createdContact { print("created contact success") } } Button ("2. Add Contact To Group") { print("add to group button pressed") contactsModel.addContactToGroup() } Button ("3. Remove Contact From Group") { print("remove from group button pressed") contactsModel.removeContactFromGroup() } Button ("4. Delete Contact and Group") { print("remove from group button pressed") contactsModel.deleteContactAndGroup() } } .padding() } } #Preview { ContentView() } @available(iOS 13.0, *) @objc final class ContactsStoreModel: NSObject, ObservableObject { let contactStore = CNContactStore() var createdContact : CNContact? var createdGroup : CNGroup? public func addTestContact() { let storeContainer = contactStore.defaultContainerIdentifier() let contact = CNMutableContact() contact.givenName = "Testing" contact.familyName = "User" contact.phoneNumbers = [CNLabeledValue(label: "Cell", value: CNPhoneNumber(stringValue: "1234567890"))] let group = CNMutableGroup() group.name = "Testing Group" print("create contact id = \(contact.identifier)") print("create group id = \(group.identifier)") do { let saveRequest = CNSaveRequest() saveRequest.transactionAuthor = "TestApp" saveRequest.add(contact, toContainerWithIdentifier: storeContainer) saveRequest.add(group, toContainerWithIdentifier: storeContainer) try contactStore.execute(saveRequest) createdContact = contact createdGroup = group } catch { print("error in store execute = \(error)") } } public func addContactToGroup() { if let contact = createdContact, let group = createdGroup { do { let saveRequest = CNSaveRequest() saveRequest.transactionAuthor = "TestApp" saveRequest.addMember(contact, to: group) try contactStore.execute(saveRequest) } catch { print("error in store execute = \(error)") } } } public func removeContactFromGroup() { if let contact = createdContact, let group = createdGroup { do { let saveRequest = CNSaveRequest() saveRequest.transactionAuthor = "TestApp" saveRequest.removeMember(contact, from: group) try contactStore.execute(saveRequest) } catch { print("error in store execute = \(error)") } } } public func addGroupAndContact() { let storeContainer = contactStore.defaultContainerIdentifier() let group = CNMutableGroup() group.name = "Test Group" print("create group id = \(group.identifier)") if let contact = createdContact { do { let saveRequest = CNSaveRequest() saveRequest.transactionAuthor = "TestApp" saveRequest.add(group, toContainerWithIdentifier: storeContainer) saveRequest.addMember(contact, to: group) try contactStore.execute(saveRequest) createdGroup = group } catch { print("error in store execute = \(error)") } } } public func deleteContactAndGroup() { if let contact = createdContact, let group = createdGroup { do { let mutableGroup = group.mutableCopy() as! CNMutableGroup let mutableContact = contact.mutableCopy() as! CNMutableContact let saveRequest = CNSaveRequest() saveRequest.transactionAuthor = "TestApp" saveRequest.delete(mutableContact) saveRequest.delete(mutableGroup) try contactStore.execute(saveRequest) } catch { print("error in deleting store execute = \(error)") } } } }
0
0
65
3d
Support Status and Specific Implementation Methods for ContactProviderExtension with NotificationServiceExtension
I am considering an implementation to provide OS Contacts information in real-time using data from push notifications. Through a TSI (Technical Support Incident), Apple Support recommended combining ContactProviderExtension and NotificationServiceExtension. However, the following threads indicate that this combination is not supported, as confirmed by Apple DTS (Thread 1, Thread 2). Question 1: Latest Support Status for Using ContactProviderExtension with NotificationServiceExtension Additionally, there is limited documentation on implementing ContactProviderExtension, and I am specifically struggling with the following points: Question 2: Specific Implementation Guidance for ContactProviderExtension The method to call when providing Contacts information to the OS How to add and delete Contacts information provided to the OS How to verify the Contacts information currently provided to the OS Any insights on the latest support status or specific implementation methods for these extensions would be greatly appreciated.
0
0
131
1w
Timer driven refresh
I have an app that needs to refresh a server whenever a Contacts record is updated. I can observe Contacts, but that only seems to work when my app is running (and in foreground, which it cannot be on iPhone if the Contacts app is being updated). I want it to process, even if my app is in background, or has been terminated (swiped away), or after a phone restart. The only way I can think of is to periodically push a notification to the app from an external server. Is there any way to run a timer that sends a notification to the app on a periodic basis? The timers you can set seem to run even if the Clock app is swiped away, or following a phone restart. Is there anything like that I could use to wake my app periodically?
1
0
224
Oct ’24
callkit and contact app
Hello, I am a developer currently working on a personal contact management app. What is the app? My app stores additional information beyond basic contact details. Therefore, instead of using the Contacts framework, I manage contact objects using Core Data. What am I trying to achieve? I want to display additional information on the caller ID screen when a call is received from a number stored in my app. What have I tried? I’ve attempted the following methods without success: 1. Call Directory Extension: I thought using this method would allow me to display additional information from Core Data on the call screen. However, I learned that when a call is received, the iOS system first searches for the phone number in the Contacts app and only looks to the Extension app if no match is found. Therefore, displaying contact information from my app seems unfeasible. 2. Custom Call UI: Using CallKit seemed like a viable option to display the necessary information during a call, but it appears to only be possible with VoIP apps. My app does not support VoIP calls, so this method was also not implementable. I am wondering if there are any technologies available that could help me achieve my goal, or if there’s something I might be missing. Any advice would be greatly appreciated. Thank you! If a similar question has been asked, I apologize for the repetition.
3
0
300
Oct ’24
Displaying limited contacts list in UIKit
I have an app that was written in UIKit. It's too large, and it would be much too time consuming at this point to convert it to SwiftUI. I want to incorporate the new limited contacts into this app. The way it's currently written everything works fine except for showing the limited contacts in the contact picker. I have downloaded and gone though the Apple tutorial app but I'm having trouble thinking it through into UIKit. After a couple of hours I decided I need help. I understand I need to pull the contact IDs of the contacts that are in the limited contacts list. Not sure how to do that or how to get it to display in the picker. Any help would be greatly appreciated. func requestAccess(completionHandler: @escaping (_ accessGranted: Bool) -> Void) { switch CNContactStore.authorizationStatus(for: .contacts) { case .authorized: completionHandler(true) case .denied: showSettingsAlert(completionHandler) case .restricted, .notDetermined: CNContactStore().requestAccess(for: .contacts) { granted, error in if granted { completionHandler(true) } else { DispatchQueue.main.async { [weak self] in self?.showSettingsAlert(completionHandler) } } } // iOS 18 only case .limited: completionHandler(true) @unknown default: break } } // A text field that displays the name of the chosen contact @IBAction func contact_Fld_Tapped(_ sender: TextField_Designable) { sender.resignFirstResponder() // The contact ID that is saved to the Db getTheCurrentContactID() let theAlert = UIAlertController(title: K.Titles.chooseAContact, message: nil, preferredStyle: .actionSheet) // Create a new contact let addContact = UIAlertAction(title: K.Titles.newContact, style: .default) { [weak self] _ in self?.requestAccess { _ in let openContact = CNContact() let vc = CNContactViewController(forNewContact: openContact) vc.delegate = self // this delegate CNContactViewControllerDelegate DispatchQueue.main.async { self?.present(UINavigationController(rootViewController: vc), animated: true) } } } let getContact = UIAlertAction(title: K.Titles.fromContacts, style: .default) { [weak self] _ in self?.requestAccess { _ in self?.contactPicker.delegate = self DispatchQueue.main.async { self?.present(self!.contactPicker, animated: true) } } } let editBtn = UIAlertAction(title: K.Titles.editContact, style: .default) { [weak self] _ in self?.requestAccess { _ in let store = CNContactStore() var vc = CNContactViewController() do { let descriptor = CNContactViewController.descriptorForRequiredKeys() let editContact = try store.unifiedContact(withIdentifier: self!.oldContactID, keysToFetch: [descriptor]) vc = CNContactViewController(for: editContact) } catch { print("Getting contact to edit failed: \(self!.VC_String) \(error)") } vc.delegate = self // delegate for CNContactViewControllerDelegate self?.navigationController?.isNavigationBarHidden = false self?.navigationController?.navigationItem.hidesBackButton = false self?.navigationController?.pushViewController(vc, animated: true) } } let cancel = UIAlertAction(title: K.Titles.cancel, style: .cancel) { _ in } if oldContactID.isEmpty { editBtn.isEnabled = false } theAlert.addAction(getContact) // Select from contacts theAlert.addAction(addContact) // Create new contact theAlert.addAction(editBtn) // Edit this contact theAlert.addAction(cancel) let popOver = theAlert.popoverPresentationController popOver?.sourceView = sender popOver?.sourceRect = sender.bounds popOver?.permittedArrowDirections = .any present(theAlert,animated: true) } func requestAccess(completionHandler: @escaping (_ accessGranted: Bool) -> Void) { switch CNContactStore.authorizationStatus(for: .contacts) { case .authorized: completionHandler(true) case .denied: showSettingsAlert(completionHandler) case .restricted, .notDetermined: CNContactStore().requestAccess(for: .contacts) { granted, error in if granted { completionHandler(true) } else { DispatchQueue.main.async { [weak self] in self?.showSettingsAlert(completionHandler) } } } // iOS 18 only case .limited: completionHandler(true) @unknown default: break } } // MARK: - Contact Picker Delegate extension AddEdit_Quote_VC: CNContactPickerDelegate { func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) { selectedContactID = contact.identifier let company: String = contact.organizationName let companyText = company == "" ? K.Titles.noCompanyName : contact.organizationName contactNameFld_Outlet.text = CNContactFormatter.string(from: contact, style: .fullName)! companyFld_Outlet.text = companyText save_Array[0] = K.AppFacing.true_App setSaveBtn_AEQuote() } } extension AddEdit_Quote_VC: CNContactViewControllerDelegate { func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool { return false } func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) { selectedContactID = contact?.identifier ?? "" if selectedContactID != "" { let company: String = contact?.organizationName ?? "" let companyText = company == "" ? K.Titles.noCompanyName : contact!.organizationName contactNameFld_Outlet.text = CNContactFormatter.string(from: contact!, style: .fullName) companyFld_Outlet.text = companyText getTheCurrentContactID() if selectedContactID != oldContactID { save_Array[0] = K.AppFacing.true_App setSaveBtn_AEQuote() } } dismiss(animated: true, completion: nil) } }
1
0
303
2w
Using ContactAccessButton freezes the entire app
When using both the ContactsAccessButton demo project, as well as when implementing it in my own, the whole app freezes after entering a few characters and searching through contacts. I don't know if this is necessarily reproducable because it's probably related to the contacts in my contact book. Typing in "Lex" does not freeze the app, but typing in "Adam No" freezes it. I get the following console error before my app freezes and I'm forced to force quit it: #ContactsButton Failed to get service proxy: Error Domain=NSCocoaErrorDomain Code=4097 "connection to service with pid 3387 named com.apple.ContactsUI.ContactsButtonXPCService" UserInfo={NSDebugDescription=connection to service with pid 3387 named com.apple.ContactsUI.ContactsButtonXPCService} #ContactsButton Failed to get remote content: nil (got this a number of times ContactsAccessButton really doesn't seem production ready...
2
2
259
Sep ’24
Request .full contacts authorization after being granted .limited
We're adjusting to the new iOS 18 .limited contacts access mode. In our app, we don't request contacts right away. We have a search bar where users can search through their own contacts and select one using the ContactAccessButton. If they do select one, then they're prompted to "Grant Limited Access", not "Grant Full Access", as the screenshot shows below. Later on, we want to offer the ability for users to sync their entire contact book with our app. This will improve their experience on the app by automatically finding all their friends already on the app, without them having to do the manual work of clicking on every single contact in the ContactsAccessPicker. Is this possible right now? It doesn't seem like it—when I call ContactsStore.requestAccess(for: .contacts) while in .limited access mode, nothing happens. But I would like to show a prompt that gives the user the ability to grant all their contacts to improve their experience.
1
0
305
Sep ’24
Contact Prodvider Extension key 'com.apple.contact.provider.extension' not accepting in Testflight submission
ERROR ITMS-90349: "Invalid Info.plist value. The value of the EXExtensionPointldentifier key, com.apple.contact.provider.extension, in the Info.plist of "MainApp.app/Extensions/ContactProviderExtension.appex" is invalid. We were working on new iOS18 Contacts Provider extension and when try to test the feature in testflight we were unable to submit the build and getting the above error. The extensionPointldentifier 'com.apple.contact.provider.extension' was auto generated by xcode and apple doc mentioned the same value to use for Contacts Provider extension support. But it is not accepting in testflight. https://developer.apple.com/documentation/contactprovider/contactproviderextension Any help will be appreciated.
0
1
456
Sep ’24
iOS 18 recent update
I updated to the newest version of iOS 18 Beta this morning. One feature I preferred on the previous version was that when you're going into the call screen, you had to press the small phone icon rather than the persons name, to make the call. It stopped me accidentally calling people a lot of the time.. This feature seems to have gone on the newest version, or is that a bug? Essentially, the little (i) icon become a phone icon, and you used that to call, rather than the main contact name box.
0
1
338
Aug ’24
ContactAccessButton and ContactAccessPicker issues
We are trying to adopt the new iOS18 ContactAccessButton and ContactAccessPicker in our app and we are facing below issues and requirements in UI and functionality. is there a way to optionally hide the ContactAccessButton UI when there is no matching results ?. The search button in the ContactAccessButton UI is not working when showing no matches and browse contacts. The console showing below error #ContactsButton response after touch -- Should not show UI #ContactsButton: match for callback was unexpectedly nil? The ContactAccessPicker view and contact selection view opened from ContactAccessButton - view results are not presented properly. The presented view not fully covered the presenting screen and blank screen appears in edges Cancel button in ContactAccessPicker UI when list shown is not working.
4
2
425
Sep ’24
Contact Provider Extension "enumerateContent" Method Called Multiple Times
I'm facing an issue with the enumerateContent method in my Contact Provider extension. The method is being called multiple times before I call the first observer.didEnumerate.. Here's a sample of my code: class TestContactProviderRootContainerEnumerator : ContactItemEnumerator { let handler = TestContactProviderUsecaseHandler() func configure(for domain: ContactProviderDomain) { log.error("---> configure") } func enumerateContent(in page: ContactItemPage, for observer: ContactItemContentObserver) { let requestPage = getPageIndex(from: page.offset, pageSize: observer.suggestedPageSize) log.error("---> Begin Enumerate Content page=\(requestPage) pageSize =\(observer.suggestedPageSize)") func completion(items: [ContactItem], hasMore: Bool) { observer.didEnumerate(items) do { let generationMarker = try getGenerationMarker(page: page) if hasMore { let nextPage = ContactItemPage(generationMarker: generationMarker, offset: page.offset + items.count) log.error("---> nextPage set offset \(page.offset + items.count) nextPage: \(getPageIndex(from: page.offset + items.count, pageSize: observer.suggestedPageSize))") observer.didFinishEnumeratingPage(upTo: nextPage) }else { observer.didFinishEnumeratingContent(upTo: generationMarker) } }catch { observer.didFinishEnumeratingContentWithError(error) return } } log.error("---> Start Request page=\(requestPage) pageSize =\(observer.suggestedPageSize)") handler.requestForRecordsList(page: requestPage, perPage: observer.suggestedPageSize, sortBy: Field.Contact.lastName, modifiedSince: nil, completion: { contactItems, hasMore, error in log.error("---> Finish Request page=\(requestPage) pageSize =\(observer.suggestedPageSize)") if let error { log.error("---> Error: \(error)") observer.didFinishEnumeratingContentWithError(error) return } completion(items: contactItems, hasMore: hasMore) log.error("---> End Enumerate Content page=\(requestPage) pageSize =\(observer.suggestedPageSize)") }) } } Below are the logs that I'm seeing: ---> configure ---> Begin Enumerate Content page=1 pageSize =20 ---> Start Request page=1 pageSize =20 ---> Begin Enumerate Content page=1 pageSize =20 ---> Begin Enumerate Content page=1 pageSize =20 ---> Begin Enumerate Content page=1 pageSize =20 ....10+times ---> Begin Enumerate Content page=1 pageSize =20 ---> Finish Request page=1 pageSize =20 ---> Begin Enumerate Content page=1 pageSize =20​​ ​---> nextPage set offset 20 nextPage: 2​​ ​---> Begin Enumerate Content page=1 pageSize =20 ---> Begin Enumerate Content page=1 pageSize =20​ ​---> Begin Enumerate Content page=2 pageSize =20 ---> Begin Enumerate Content page=2 pageSize =20​ ​---> Start Request page=2 pageSize =20​ ​---> Begin Enumerate Content page=2 pageSize =20 ---> Begin Enumerate Content page=2 pageSize =20​ ​....10+times ---> Begin Enumerate Content page=2 pageSize =20 ---> Begin Enumerate Content page=2 pageSize =20 Has anyone else experienced this issue? Any ideas on why this is happening or how to fix it.
2
0
370
Aug ’24
Its not possible to use a Contact Provider Extension from within a Notification Service Extension
A Notification Service Extension is one of the more capable extensions, and there's a lot that can be done within it (for example, it can invoke a Call Extension). However its not possible to use a Contact Provider Extension within it. If a CPE has been enabled by the main app, then if a push is sent to the NSE, then within the NSE the ContactProviderManager class reports that the CPE is disabled and its not possible to anything with it. For example a call to ContactProviderManager.signalEnumerator() will hang and not complete. I was hoping to create a contact and make it available to the system on receipt of a push, but this isn't going to possible. Is this intentional and by design, or just due to the immaturity of this feature/iOS beta? The documentation of a Contact provider Extension says: "signalEnumerator() An example of using this call is to handle a push notification to your app when the provided contacts from your server update" It therefore seems strange that the main cited use case for ContactProviderManager.signalEnumerator() isn't actually possible if the push is delivered to an extension rather than the app.
3
0
503
Aug ’24
How does an app manage its own contacts?
In the documentation for the Contact Provider Extension contact provider it says Use the Contact Provider framework if your app manages its own contacts and wants to make them available in other apps that use the Contacts framework. But how does an app manage its own contacts? What needs to be done differently if it manages its own contacts versus managing a user's contacts? Does the user still need to grant contacts access for example? Is there a special group/domain that should be used (how) to add app contacts? There's no mention of any of this that I can see in the documentation for CNContact or CNContactStore.
4
0
451
Jul ’24
Refreshing Contact Provider Extension from NSE
Hi, While testing the new Contact Provider Extension in the iOS 18 betas I noticed that the Notification Service Extension is unable to connect to the CPE in order to trigger a refresh. The ContactProviderManager init throws an "Extension Not Found" error. The CPE documentation mentions a main use case of using notifications to trigger CPE updates: "An example of using this call is to handle a push notification to your app when the provided contacts from your server update." source Is the main app the only way to trigger a CPE update? The documentation does not specify this limitation. I have tested this on a physical iPhone 15 running iOS 18 beta 4. Thanks, Wes
4
1
506
Aug ’24
imageData property of CNContact
After setting the imageData property of CNContact through the API, I can see in the address book that the contact's avatar has been set to the imageData image, But when my phone version is iOS 17.1.0, when this contact calls in and locks the screen, I can see that this image is displayed in full screen, But when my phone version is iOS 17.5.1, when this contact calls in, the screen is locked and the image can only be displayed on the avatar, with a blurred image in the background, After iOS 17, contacts can set their avatars and posters separately in their contact list. How can I set these two images through code, just like setting the imageData property of CNContact through API. I see that there is a parameter in the CNContact property called _fullscreenImageData, but there is no value inside, and I am unable to obtain data for this parameter. I don't know how to manipulate this data in order to achieve the effect of setting up posters in the iOS 17.5.1 system.
2
0
330
Jul ’24