Swift Using Contacts Framework, search using Identifier to match - swift

Trying unsuccessfully to get a contact match providing contact Identifier. I desire to return contact, then to use the image associated with it. I am getting a nil match. thank you. This code I got from a demo, I'm kinda new to programming
import Contacts
var contact = CNContact()
var contactStore = CNContactStore()
let foundContact = getContactFromID("94AAD3B1-E9E1-48C9-A796-F7EC1014230A")
func getContactFromID(contactID: String) -> CNContact {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let predicate = CNContact.predicateForContactsWithIdentifiers([contactID])
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactPhoneNumbersKey, CNContactImageDataKey, CNContactImageDataAvailableKey]
var contacts = [CNContact]()
var message: String!
let contactsStore = AppDelegate.getAppDelegate().contactStore
do {
contacts = try contactsStore.unifiedContactsMatchingPredicate(predicate, keysToFetch: keys)
if contacts.count == 0 {
message = "No contacts were found matching the given name."
}
}
catch {
message = "Unable to fetch contacts."
}
if message != nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
Utility.showAlert(nil, message: message)
})
} else {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.contact = contacts[0]
print("self.contact: \(self.contact)")
})
}
}
}
return self.contact
}

I solved it :), I removed the dispatch_async stuff, works now: here is fixed code.
func getContactFromID(contactID: String) -> CNContact {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let predicate = CNContact.predicateForContactsWithIdentifiers([contactID])
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactPhoneNumbersKey, CNContactImageDataKey, CNContactImageDataAvailableKey]
var contacts = [CNContact]()
var message: String!
let contactsStore = AppDelegate.getAppDelegate().contactStore
do {
contacts = try contactsStore.unifiedContactsMatchingPredicate(predicate, keysToFetch: keys)
if contacts.count == 0 {
message = "No contacts were found matching the given name."
}
}
catch {
message = "Unable to fetch contacts."
}
self.contact = contacts[0]
}
}
return self.contact
}

Related

Swift app crashing when attempting to fetch contacts

I am creating an onboarding portion of an app which gets the user's contacts to check which already have the app to add as friends. I'm using the CNContact framework. I have created several methods I'm using to get a full list of the users' contacts, check if they have the app, and enumerate them in a UITableView. However, when the view loads, the app crashes with the error "A property was not requested when contact was fetched." I already make a fetch request with the keys CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, ad CNContactImageDataKey. I've included all my code here:
import Foundation
import Contacts
import PhoneNumberKit
struct ContactService {
static func createContactArray() -> [CNContact] {
var tempContacts = [CNContact]()
let store = CNContactStore()
store.requestAccess(for: .contacts) { (granted, error) in
if let _ = error {
print("failed to request access to contacts")
return
}
if granted {
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, CNContactImageDataKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
request.sortOrder = CNContactSortOrder.familyName
do {
try store.enumerateContacts(with: request, usingBlock: { (contact, stop) in
tempContacts.append(contact)
})
} catch {
print("unable to fetch contacts")
}
print("created contact list")
}
}
return tempContacts
}
static func createFetchedContactArray(contactArray: [CNContact], completion: #escaping ([FetchedContact]?) -> Void) -> Void {
var temp = [FetchedContact]()
getNumsInFirestore { (nums) in
if let nums = nums {
for c in contactArray {
let f = FetchedContact(cnContact: c, numsInFirebase: nums)
temp.append(f)
}
return completion(temp)
} else {
print("Error retrieving numbers")
}
}
return completion(nil)
}
static func getNumsInFirestore(_ completion: #escaping (_ nums : [String]?) -> Void ) -> Void {
var numsInFirebase = [String]()
let db = FirestoreService.db
db.collection(Constants.Firestore.Collections.users).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting user documents: \(err)")
completion(nil)
} else {
for doc in querySnapshot!.documents {
let dict = doc.data()
numsInFirebase.append(dict[Constants.Firestore.Keys.phone] as! String)
}
completion(numsInFirebase)
}
}
}
static func getPhoneStrings(contact: CNContact) -> [String] {
var temp = [String]()
let cnPhoneNums = contact.phoneNumbers
for n in cnPhoneNums {
temp.append(n.value.stringValue)
}
return temp
}
static func hasBump(contact: CNContact, completion: #escaping (_ h: Bool) -> Void ) -> Void {
let contactNums = ContactService.getPhoneStrings(contact: contact)
ContactService.getNumsInFirestore { (nums) in
if let nums = nums {
return completion(contactNums.contains(where: nums.contains))
} else {
print("Error retrieving numbers from firestore")
return completion(false)
}
}
}
static func anyContactsWithBump(completion: #escaping (_ yes: Bool) -> Void) {
let contacts = createContactArray()
var tempBool = false
let workgroup = DispatchGroup()
for c in contacts {
workgroup.enter()
hasBump(contact: c) { (has) in
if has {
tempBool = true
}
workgroup.leave()
}
}
workgroup.notify(queue: .main) {
completion(tempBool)
}
}
}
Then I call the methods in the view controller class:
import UIKit
import Contacts
import FirebaseDatabase
import Firebase
import FirebaseFirestore
class AddContactsVC: UIViewController {
var fetchedContactsWithBump: [FetchedContact] = []
override func viewDidLoad() {
let cnContacts = ContactService.createContactArray()
var contactsWithBump = [CNContact]()
let workgroup = DispatchGroup()
for contact in cnContacts {
workgroup.enter()
ContactService.hasBump(contact: contact) { (has) in
if has {
contactsWithBump.append(contact)
}
workgroup.leave()
}
}
workgroup.notify(queue: .main) {
print("Number of contacts with Bump: \(contactsWithBump.count)")
ContactService.createFetchedContactArray(contactArray: contactsWithBump) { (fetchedContacts) in
if let fetchedContacts = fetchedContacts {
self.fetchedContactsWithBump = fetchedContacts
} else {
print("Error creating fetchedContacts array.")
}
}
}
I also get the message "Error creating fetchedContacts array" in the console, so I know something is going wrong with that method, I'm just not sure what. Any help is appreciated!
Edit: The exception is thrown at 3 points: 1 at the first line of my FetchedContact init method, which looks like this:
init(cnContact: CNContact, numsInFirebase: [String]) {
if cnContact.imageDataAvailable { self.image = UIImage(data: cnContact.imageData!) }
It also points to the line let f = FetchedContact(cnContact: c, numsInFirebase: nums) in createFetched contact array, and finally at my completion(numsInFirebase) call in getNumsInFirestore.
To start with
let contacts = createContactArray()
will always return an empty array.
This function has a return statement outside the closure so will immediately return an empty array.
Change createContactArray to use a completion handler like the other functions you have to populate contacts from inside the closure.

Swift core data change bool stored

I am making and expanding an app for lists and adding extra data as I go. I have added a bool to mark the item as completed.
I want to change the value of a bool stored in core data. I can add and delete orders but now I'm looking to change properties. Please can I have help on the best way to change these?
I have made a func changeCompleated, but can't work out how to get it to work in my core data manager.
class CoreDataManager {
static let shared = CoreDataManager(moc: NSManagedObjectContext.current)
var moc: NSManagedObjectContext
private init(moc: NSManagedObjectContext) {
self.moc = moc
}
private func fetchOrder(name: String) -> Order? {
var orders = [Order]()
let request: NSFetchRequest<Order> = Order.fetchRequest()
request.predicate = NSPredicate(format: "name == %#", name)
do {
orders = try self.moc.fetch(request)
} catch let error as NSError {
print(error)
}
return orders.first
}
func changeCompleated(name:String, completed: Bool) {
do {
if let order = fetchOrder(name: name) {
self.moc.perform {
}
try self.moc.save()
}
} catch let error as NSError {
print(error)
}
}
func deleteOrder(name: String) {
do {
if let order = fetchOrder(name: name) {
self.moc.delete(order)
try self.moc.save()
}
} catch let error as NSError {
print(error)
}
}
func getAllOrders() -> [Order] {
var orders = [Order]()
let orderRequest: NSFetchRequest<Order> = Order.fetchRequest()
do {
orders = try self.moc.fetch(orderRequest)
} catch let error as NSError {
print(error)
}
return orders
}
func saveOrder(id: UUID, name: String, type: String, qty: Double, urgent: Bool, complete: Bool) {
let order = Order(context: self.moc)
order.id = id
order.name = name
order.type = type
order.qty = qty
order.urgent = urgent
order.complete = complete
do {
try self.moc.save()
} catch let error as NSError {
print(error)
}
}
}
Almost, just add a line to change the value, the perform block is not needed
func changeCompleated(name: String, completed: Bool) {
guard let order = fetchOrder(name: name) else { return }
do {
order.complete = completed
try self.moc.save()
} catch {
print(error)
}
}
And you can also shorten fetchOrder
private func fetchOrder(name: String) -> Order? {
let request: NSFetchRequest<Order> = Order.fetchRequest()
request.predicate = NSPredicate(format: "name == %#", name)
do {
return try self.moc.fetch(request).first
} catch {
print(error)
}
}

How to set contact as a favorite contact programmatically in Swift?

I am developing an application in which I need to fetch all the contacts from device and then set it to favorite contact on button press. I am able to fetch all contacts using [CNContact] in iOS 9 and 10. But don't know how to set it as a favorite contact.
Can we set CNContact as a favorite contact?
Can we make changes in CNContact?
You can store Favourites to Realm DB. Like This,
class FavouriteList: Object {
let favouriteList : List<FavouriteContact> = List<FavouriteContact>()
}
class FavouriteContact: Object {
dynamic var identifier : String? = ""
override class func primaryKey() -> String? {
return "identifier"
}
}
// Add Favourite Contact in Realm
class func add(identifier: String) -> Bool {
var realm: Realm!
do {
realm = try Realm()
realm.beginWrite()
} catch {
print(error.localizedDescription)
}
let realmTask: FavouriteList= FavouriteList()
let favContact: FavouriteContact = FavouriteContact()
// Check ID Exist or Not
let idExists: FavouriteContact? = realm.object(ofType: FavouriteContact.self, forPrimaryKey: identifier)
if idExists?.identifier != nil {
realm.cancelWrite()
return false
} else {
favContact.identifier = identifier
realmTask.favouriteList.append(favContact)
realm.add(realmTask)
}
// Realm Commit
do {
try realm.commitWrite()
} catch {
print("Realm Task Write Error : ", error.localizedDescription)
}
return true
}
// Remove Favourite Contact
class func remove(identifier: String) -> Bool {
var realm: Realm!
do {
realm = try Realm()
realm.beginWrite()
} catch {
print(error.localizedDescription)
}
// Check ID Exist or Not
let idExists: FavouriteContact? = realm.object(ofType: FavouriteContact.self, forPrimaryKey: identifier)
if idExists?.identifier != nil {
realm.delete(idExists!)
} else {
realm.cancelWrite()
return false
}
// Realm Commit
do {
try realm.commitWrite()
} catch {
print("Realm Task Write Error : ", error.localizedDescription)
}
return true
}
// Get Favourite List
class func get(completionHandler: #escaping (_ result: [CNContact]) -> ()) {
var favourites: [CNContact] = [CNContact]()
do {
let realm = try Realm()
let dataRealmContacts: Results<FavouriteList> = realm.objects(FavouriteList.self)
for item in dataRealmContacts {
for contactID in item.favouriteList {
if contactID.identifier != nil {
favourites.append(getContactFromID(identifier: contactID.identifier!))
}
}
}
completionHandler(favourites)
} catch {
print(error.localizedDescription)
completionHandler(favourites)
}
}

CloudKit CKFetchRecordsOperation blocks

I'm fetching some cloud records using CKFetchRecordsOperation.
operation.perRecordCompletionBlock closure has a query to get some children records, but i noticed that operation.completionBlock closure is called before perRecordCompletionBlock completes his job. So, where should i call my completion closure which informs everything is done with that operation?
func getResidents(completion: #escaping (_ residents: [Resident]?, _ error: Error?) -> Void) {
var residents = [Resident]()
let recordNames = ["a56e39ad-9078-43b2-b699-5b9faa233f0d",
"e842daaa-3ff2-434b-9d50-3f14eb7e46d5",
"5cf075ad-d78a-4cea-872e-6b062781f14c",
"8e9fdbbb-cf06-486f-9fae-8be13abe2357",
"78b0cc19-9e33-4755-b199-7098fa319368",
"2d9bee21-d268-46cc-84e1-f7784495aac2",
"ae1da81b-bc1d-4e83-b145-c06882dc2203",
"3e13ed1e-e007-4ece-9a6c-8c09d638d6b3",
"78c224ab-e836-48c9-8c1c-f10bd4907457"
]
func getRecordIDs(names: [String]) -> [CKRecordID] {
var IDs = [CKRecordID]()
for name in names {
IDs.append(CKRecordID(recordName: name))
}
return IDs
}
let operation = CKFetchRecordsOperation(recordIDs: getRecordIDs(names: recordNames))
operation.perRecordCompletionBlock = { record, recordID, error in
if error != nil {
completion(nil, error)
} else {
if let record = record {
// Query debts (resident children)
let predicate = NSPredicate(format: "Resident = %#", record.recordID)
let query = CKQuery(recordType: Constants.RecordType.Debt, predicate: predicate)
self.publicCloudDB.perform(query, inZoneWith: nil) { records, error in
if error != nil {
// Retain the record IDs for failed fetches
completion(nil, error)
} else {
var debts = [Debt]()
if let debtRecords = records {
for item in debtRecords {
if let debt = Debt.parse(record: item) {
debts.append(debt)
}
}
}
if var resident = Resident.parse(record: record) {
resident.debts = debts
residents.append(resident)
print(resident)
}
}
}
}
}
}
operation.completionBlock = {
completion(residents, nil)
}
operation.allowsCellularAccess = true
operation.database = publicCloudDB
operation.start()
}

iPhone AppDelegate only returning first object of an array to Watch OS 2 iOS 9

I've just learned how to create Watch apps from a Watch OS 1 tutorial and most of it translates to OS 2 except for communication from the Watch and iPhone. Here are the steps I'm taking:
1 - Watch InterfaceController.swift - create a WCSession, sendMessage to iPhone if user is logged in or not. If yes ask for table data, if no show a label to go to phone.
2 - I correctly get whether user is logged in or not, then I ask for an array of dog names to populate my table.
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
if (WCSession.isSupported()) {
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
print("Activated Session ----")
if session.reachable == true {
session.sendMessage(["content" : "isLoggedIn"], replyHandler: { (result) -> Void in
if let response = result["isLoggedIn"] as? String {
if response == "true" {
userLoggedOn = true
self.session.sendMessage(["content":"getDogNames"], replyHandler: { (result) -> Void in
if let nameRequest = result as? [String:[String]] {
if let dogData = nameRequest["names"] {
if dogData.count > 0 {
dogNames = dogData
self.logInLabel.setHidden(true)
self.loadTableData()
}
else {
self.logInLabel.setHidden(false)
self.logInLabel.setText("Add dog profiles from iPhone")
}
print(dogData)
}
}
}, errorHandler: { (error) -> Void in
print("got names error")
})
} else {
userLoggedOn = false
self.logInLabel.setText("Create a profile from iPhone")
}
}
}, errorHandler: { (error) -> Void in
print("error:------ /(error)")
})
}
}
}
3 - iPhone AppDelegate.swift - In my result, I'm only getting the first object of an array queried from my parse.com database.
#available(iOS 9.0, *)
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
if let watchMessage = message as? [String : String] {
if let content = watchMessage["content"] {
if content == "isLoggedIn" {
if PFUser.currentUser() != nil {
replyHandler(["isLoggedIn":"true"])
}
else {
replyHandler(["isLoggedIn":"false"])
}
} else if content == "getDogNames" {
let query = PFQuery(className: "dogInfo")
query.whereKey("userId", equalTo: (PFUser.currentUser()?.objectId)!)
query.orderByAscending("dogName")
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if error == nil && objects!.count > 0 {
for object in objects! {
var dogsArray = [String]()
if let message = object["dogName"] as? String {
dogsArray.append(message)
}
replyHandler(["names":dogsArray])
}
}
})
}
}
}
}
I've tested this by manually putting in
var dogsArray = ["dog1","dog2","dog3"]
and it results in all 3 items coming back. I know for a fact there are 7 items that I should be getting but only getting the first one. Very strange, any help is much appreciated!
There seems to be a logical error in your code, try changing it to this:
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if error == nil && objects!.count > 0 {
var dogsArray = [String]() // moved this
for object in objects! {
if let message = object["dogName"] as? String {
dogsArray.append(message)
}
}
replyHandler(["names":dogsArray]) // and this
}
})
where I've moved the call to the replyHandler and the creation of the dogsArray outside of the for loop so that it accumulates all the dog names, and then afterwards calls the replyHandler.