I am new to swift . I used APAdressBook Framework for retrieving contacts in iPhone .Every thing working fine up to IOS 8.4 but when checked in IOS 9 of Simulator and iPhone devices it is not accessing contacts and even it not showing alert message like would you like to access contacts any one can face this type of problem if yes please give me your valuable answer?
Apple Inc. introduce Contacts.framework from iOS 9. So, it would be better use this framework to retrieve contacts.
Here is way to fetch email or whole contact from Contacts app.
Add Contacts.framework to your project.
Create or add new file of type header and give name like yourProjectName-Bridging-Header.h write #import <Contacts/Contacts.h> statement into file and save and set appropriate path of this file from build setting.
Now create method
func getAllContacts() {
let status = CNContactStore.authorizationStatusForEntityType(CNEntityType.Contacts) as CNAuthorizationStatus
if status == CNAuthorizationStatus.Denied {
let alert = UIAlertController(title:nil, message:"This app previously was refused permissions to contacts; Please go to settings and grant permission to this app so it can use contacts", preferredStyle:UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title:"OK", style:UIAlertActionStyle.Default, handler:nil))
self.presentViewController(alert, animated:true, completion:nil)
return
}
let store = CNContactStore()
store.requestAccessForEntityType(CNEntityType.Contacts) { (granted:Bool, error:NSError?) -> Void in
if !granted {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// user didn't grant access;
// so, again, tell user here why app needs permissions in order to do it's job;
// this is dispatched to the main queue because this request could be running on background thread
})
return
}
let arrContacts = NSMutableArray() // Declare this array globally, so you can access it in whole class.
let request = CNContactFetchRequest(keysToFetch:[CNContactIdentifierKey, CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey, CNContactPhoneNumbersKey, CNContactFormatter.descriptorForRequiredKeysForStyle(CNContactFormatterStyle.FullName)])
do {
try store.enumerateContactsWithFetchRequest(request, usingBlock: { (contact:CNContact, stop:UnsafeMutablePointer<ObjCBool>) -> Void in
let arrEmail = contact.emailAddresses as NSArray
if arrEmail.count > 0 {
let dict = NSMutableDictionary()
dict.setValue((contact.givenName+" "+contact.familyName), forKey: "name")
let emails = NSMutableArray()
for index in 0...arrEmail.count {
let email:CNLabeledValue = arrEmail.objectAtIndex(index) as! CNLabeledValue
emails .addObject(email.value as! String)
}
dict.setValue(emails, forKey: "email")
arrContacts.addObject(dict) // Either retrieve only those contact who have email and store only name and email
}
arrContacts.addObject(contact) // either store all contact with all detail and simplifies later on
})
} catch {
return;
}
}
}
Call this method where you want self.getAllContacts()
And when you want to retrieve
for var index = 0; index < self.arrContacts.count; ++index {
let dict = self.arrContacts[index] as! NSDictionary
print(dict.valueForKey("name"))
print(dict.valueForKey("email"))
}
Related
Step 1: Link (https://github.com/MailCore/MailCore2)
Step 2: I have added mailcore-framework in my project
Step 3: pod install for UICKeyChainStore in my project done
Step 4: Send mail successfully using MCOSMTPSession, MCOMessageBuilder.
Step 5: My problem is that I am not able to fetch using mailcore. Is there any other framework for fetching mail (inbox)?
Sorry for late answer. I have two apps in the App Store, both of them use MailCore2, so I can explain you a thing or two about that.
Of course you can fetch the emails with MailCore2, this is the code. If you have any other doubt with MailCore2 write about that, I will try to find the answer.
var imapsession:MCOIMAPSession = MCOIMAPSession()
func prepareImapSession()
{
// CONFIGURE THAT DEPENDING OF YOUR NEEDS
imapsession.hostname = imapHostname // String
imapsession.username = userName // String
imapsession.password = password // String
imapsession.port = portIMAP // UInt32 number
imapsession.authType = MCOAuthType.saslLogin
imapsession.connectionType = MCOConnectionType.TLS
}
func useImapWithUIDS()
{
// There is more than one option here, explore depending of your needs
let requestKind : MCOIMAPMessagesRequestKind = .headers
let folder : String = "INBOX"
// HERE ALSO EXPLORE DEPENDING OF YOUR NEEDS, RANGE IT IS THE RANGE OF THE UIDS THAT YOU WANT TO FETCH, I SUGGEST TO YOU TO CHANGE THE // NUMBER ONE IF YOU HAVE A LOWER BOUND TO FETCH EMAIL
let uids : MCOIndexSet = MCOIndexSet(range: MCORangeMake(1, UINT64_MAX))
let fetchOperation = imapsession.fetchMessagesOperation(withFolder: folder, requestKind: requestKind, uids: uids)
fetchOperation?.start
{ (err, msg, vanished) -> Void in
if (err != nil)
{
error = err
NSLog((err?.localizedDescription)!)
}
else
{
guard let msgs = msg as? [MCOIMAPMessage]
else
{
print("ERROR GETTING THE MAILS")
return
}
for i in 0..<msgs.count
{
// THE SUBJECT
let subject = msgs[i].header.subject
// THE uid for this email. The uid is unique for one email
let uid = msgs[i].uid
// The sequenceNumber like the nomber say it is the sequence for the emails in the INBOX from the first one // (sequenceNumber = 1) to the last one , it not represent always the same email. Because if you delete one email then //next one will get the sequence number of that email that was deleted
let sequenceNumber = msgs[i].sequenceNumber
}
}
}
// MARK: - EXTRACT THE CONTENT OF ONE EMAIL, IN THIS FUNCTION YOU NEED THE uid, THE UNIQUE NUMBER FOR ONE EMAIL
func useImapFetchContent(uidToFetch uid: UInt32)
{
let operation: MCOIMAPFetchContentOperation = imapsession.fetchMessageOperation(withFolder: "INBOX", uid: uid)
operation.start { (Error, data) in
if (Error != nil)
{
NSLog("ERROR")
return
}
let messageParser: MCOMessageParser = MCOMessageParser(data: data)
// IF YOU HAVE ATTACHMENTS USE THIS
let attachments = messageParser.attachments() as? [MCOAttachment]
// THEN YOU NEED THIS PROPERTIE, IN THIS EXAMPLE I TAKE THI FIRST, USE WHAT EVER YOU WANT
let attachData = attachments?.first?.data
// FOR THE MESSAGEPARSER YOU CAN EPLORE MORE THAN ONE OPTION TO OBTAIN THE TEXT
let msgPlainBody = messageParser.plainTextBodyRendering()
}
You can give https://github.com/snipsco/Postal a try. This is a framework which aims to provide simple access to common email providers.
Suppose John developed App A and Heather developed App B. They each have different Apple Developer's accounts and they are not on the same team or associated in any way. App B is backed by a public CloudKit database. Is there any way for App A to write to App B's public CloudKit database? Namely, can App A do this:
let DB = CKContainer(identifier: "iCloud.com.Heather.AppB").publicCloudDatabase
and then write to or read from this DB?
I'm guessing that this is not allowed out of the box, but is there a way to set up authentication so that this is possible?
This looks/sounds like the solution you seek.
CloudKit share Data between different iCloud accounts but not with everyone as outlined by https://stackoverflow.com/users/1878264/edwin-vermeer an iCloud specialist on SO.
There is third party explaination on this link too. https://medium.com/#kwylez/cloudkit-sharing-series-intro-4fc82dad7a9
Key steps shamelessly cut'n'pasted ... make sure you read and credit Cory on medium.com!
// Add an Info.plist key for CloudKit Sharing
<key>CKSharingSupported</key>
<true/>
More code...
CKContainer.default().discoverUserIdentity(withPhoneNumber: phone, completionHandler: {identity, error in
guard let userIdentity: CKUserIdentity = identity, error == nil else {
DispatchQueue.main.async(execute: {
print("fetch user by phone error " + error!.localizedDescription)
})
return
}
DispatchQueue.main.async(execute: {
print("user identity was discovered \(identity)")
})
})
/// Create a shred the root record
let recordZone: CKRecordZone = CKRecordZone(zoneName: "FriendZone")
let rootRecord: CKRecord = CKRecord(recordType: "Note", zoneID: recordZone.zoneID)
// Create a CloudKit share record
let share = CKShare(rootRecord: rootRecord)
share[CKShareTitleKey] = "Shopping List” as CKRecordValue
share[CKShareThumbnailImageDataKey] = shoppingListThumbnail as CKRecordValue
share[CKShareTypeKey] = "com.yourcompany.name" as CKRecordValue
/// Setup the participants for the share (take the CKUserIdentityLookupInfo from the identity you fetched)
let fetchParticipantsOperation: CKFetchShareParticipantsOperation = CKFetchShareParticipantsOperation(userIdentityLookupInfos: [userIdentity])
fetchParticipantsOperation.fetchShareParticipantsCompletionBlock = {error in
if let error = error {
print("error for completion" + error!.localizedDescription)
}
}
fetchParticipantsOperation.shareParticipantFetchedBlock = {participant in
print("participant \(participant)")
/// 1
participant.permission = .readWrite
/// 2
share.addParticipant(participant)
let modifyOperation: CKModifyRecordsOperation = CKModifyRecordsOperation(recordsToSave: [rootRecord, share], recordIDsToDelete: nil)
modifyOperation.savePolicy = .ifServerRecordUnchanged
modifyOperation.perRecordCompletionBlock = {record, error in
print("record completion \(record) and \(error)")
}
modifyOperation.modifyRecordsCompletionBlock = {records, recordIDs, error in
guard let ckrecords: [CKRecord] = records, let record: CKRecord = ckrecords.first, error == nil else {
print("error in modifying the records " + error!.localizedDescription)
return
}
/// 3
print("share url \(url)")
}
CKContainer.default().privateDB.add(modifyOperation)
}
CKContainer.default().add(fetchParticipantsOperation)
And on the other side of the fence.
let acceptShareOperation: CKAcceptSharesOperation = CKAcceptSharesOperation(shareMetadatas: [shareMeta])
acceptShareOperation.qualityOfService = .userInteractive
acceptShareOperation.perShareCompletionBlock = {meta, share, error in
Log.print("meta \(meta) share \(share) error \(error)")
}
acceptShareOperation.acceptSharesCompletionBlock = {error in
Log.print("error in accept share completion \(error)")
/// Send your user to wear that need to go in your app
}
CKContainer.default().container.add(acceptShareOperation)
Really I cannot hope to do the article justice, go read it... its in three parts!
If the apps were in the same organization, there is a way to set up shared access. But as you described the situation, it is not possible.
I am having trouble understanding some of the CloudKit sharing concepts and the WWDC 2016 "What's new in CloudKit" video doesn't appear to explain everything that is required to allow users to share and access shared records.
I have successfully created an app that allows the user to create and edit a record in their private database.
I have also been able to create a Share record and share this using the provided sharing UIController. This can be successfully received and accepted by the participant user but I can't figure out how to query and display this shared record.
The app creates a "MainZone" in the users private database and then creates a CKRecord in this "MainZone". I then create and save a CKShare record and use this to display the UICloudSharingController.
How do I query the sharedDatabase in order to access this record ? I have tried using the same query as is used in the privateDatabase but get the following error:
"ShareDB can't be used to access local zone"
EDIT
I found the problem - I needed to process the accepted records in the AppDelegate. Now they appear in the CloudKit dashboard but I am still unable to query them. It seems I may need to fetch the sharedDatabase "MainZone" in order to query them.
Dude, I got it: First you need to get the CKRecordZone of that Shared Record. You do it by doing the following:
let sharedData = CKContainer.default().sharedCloudDatabase
sharedData.fetchAllRecordZones { (recordZone, error) in
if error != nil {
print(error?.localizedDescription)
}
if let recordZones = recordZone {
// Here you'll have an array of CKRecordZone that is in your SharedDB!
}
}
Now, with that array in hand, all you have to do is fetch normally:
func showData(id: CKRecordZoneID) {
ctUsers = [CKRecord]()
let sharedData = CKContainer.default().sharedCloudDatabase
let predicate = NSPredicate(format: "TRUEPREDICATE")
let query = CKQuery(recordType: "Elder", predicate: predicate)
sharedData.perform(query, inZoneWith: id) { results, error in
if let error = error {
DispatchQueue.main.async {
print("Cloud Query Error - Fetch Establishments: \(error)")
}
return
}
if let users = results {
print(results)
self.ctUsers = users
print("\nHow many shares in cloud: \(self.ctUsers.count)\n")
if self.ctUsers.count != 0 {
// Here you'll your Shared CKRecords!
}
else {
print("No shares in SharedDB\n")
}
}
}
}
I didn't understand quite well when you want to get those informations. I'm with the same problem as you, but I only can get the shared data by clicking the URL... To do that you'll need two functions. First one in AppDelegate:
func application(_ application: UIApplication, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShareMetadata) {
let acceptSharesOperation = CKAcceptSharesOperation(shareMetadatas: [cloudKitShareMetadata])
acceptSharesOperation.perShareCompletionBlock = {
metadata, share, error in
if error != nil {
print(error?.localizedDescription)
} else {
let viewController: ViewController = self.window?.rootViewController as! ViewController
viewController.fetchShare(cloudKitShareMetadata)
}
}
CKContainer(identifier: cloudKitShareMetadata.containerIdentifier).add(acceptSharesOperation)
}
in ViewConroller you have the function that will fetch this MetaData:
func fetchShare(_ metadata: CKShareMetadata) {
let operation = CKFetchRecordsOperation(recordIDs: [metadata.rootRecordID])
operation.perRecordCompletionBlock = { record, _, error in
if error != nil {
print(error?.localizedDescription)
}
if record != nil {
DispatchQueue.main.async() {
self.currentRecord = record
//now you have your Shared Record
}
}
}
operation.fetchRecordsCompletionBlock = { _, error in
if error != nil {
print(error?.localizedDescription)
}
}
CKContainer.default().sharedCloudDatabase.add(operation)
}
As I said before, I'm now trying to fetch the ShareDB without accessing the URL. I don't want to depend on the link once I already accepted the share. Hope this helps you!
I need the identifier of a newly created contact directly after the save request. The use case: Within my app a user creates a new contact and give them some attributes (eg. name, address ...) after that he can save the contact. This scenario is working as aspected. My code looks like this:
func createContact(uiContact: Contact, withImage image:UIImage?, completion: String -> Void)
{
let contactToSave = uiContact.mapToCNContact(CNContact()) as! Cnmutablecontawctlet
if let newImage = image
{
contactToSave.imageData = UIImageJPEGRepresentation(newImage, 1.0)
}
request = CNSaveRequest()
request.addContact(contactToSave, toContainerWithIdentifier: nil)
do
{
try self.contactStore.executeSaveRequest(request)
print("Successfully saved the CNContact")
completion(contactToSave.identifier)
}
catch let error
{
print("CNContact saving faild: \(error)")
completion(nil)
}
}
The Contact Object (uiContact) is just an wrapper of CNContact.
In the closure completion I need to return the identifier but on this time I have no access to them, because he is creating by the system after the write process.
One solution could be to fetch the newly saved CNContact with predicate
public func unifiedContactsMatchingPredicate(predicate: NSPredicate, keysToFetch keys: [CNKeyDescriptor]) throws -> [CNContact]
but this seems to me like a bit unclean because this contact could have only a name and more than one could exist. Something like a callback with the created identifier would be nice. But there isn´t.
Is there a other way to solve this problem?
This may be a little late but in case someone needs this.
By using the latest SDK (iOS 11), I was able to get the identifier just by:
NSError *error = nil;
saveReq = [[CNSaveRequest alloc] init];
[saveReq addContact:cnContact toContainerWithIdentifier:containerIdentifier];
if (![contactStore executeSaveRequest:saveReq error:&error]) {
NSLog(#"Failed to save, error: %#", error.localizedDescription);
}
else
{
if ([cnContact isKeyAvailable:CNContactIdentifierKey]) {
NSLog(#"identifier for new contact is: %#", cnContact.identifier);
// this works for me everytime
} else {
NSLog(#"CNContact identifier still isn't available after saving to address book");
}
}
swift 4
This is the way to get contact id when creating contact
do {
try store.execute(saveRequest)
if contactToAdd.isKeyAvailable(CNContactIdentifierKey) {
print(contactToAdd.identifier) // here you are getting identifire
}
}
catch {
print(error)
}
In AddressBook on device I have a record linked with Facebook contact record.
I fetch it into CNContact with CNContactFetchRequest with:
contactFetchRequest.mutableObjects = true
contactFetchRequest.unifyResults = false
After getting, I modify it, then I trying to update it with:
let store = CNContactStore()
let saveRequest = CNSaveRequest()
if contact != nil {
mutableContact = contact!.mutableCopy() as! CNMutableContact
saveRequest.updateContact( mutableContact )
} else {
mutableContact = CNMutableContact()
saveRequest.addContact( mutableContact, toContainerWithIdentifier:nil )
}
// Modify mutableContact
mutableContact.jobTitle = "Worker";
do {
// Will fails with error
try store.executeSaveRequest(saveRequest)
} catch let error as NSError {
BCRLog(error)
self.isFailed = true
} catch {
self.isFailed = true
}
On execute executeSaveRequest, I caught an error:
NSError with domain:CNErrorDomain, code:500 (witch is
CNErrorCodePolicyViolation), _userInfo: {"NSUnderlyingError" :
{"ABAddressBookErrorDomain" - code 0}} witch is
kABOperationNotPermittedByStoreError
The question: Is it possible to modify linked contact (not unified), and if it is, what i do wrong?
If I modifying not linked contact - all OK!
I have this error when the Contacts app is configured to store contacts in an Exchange account. When I choose an iCloud account as a default it immediately saves a contact well. I can check what is set on your device in Settings -> Contacts -> Default Account