Display only contacts with right phone number (Contacts Framework) - ios

I want to display only those contacts with particular phone numbers. The phone numbers are written in an array. I don't know how to display only these contacts. Therefore I tried to use the "predicateForEnablingContact" Method.
But with my code, all contacts are disabled, even the ones with the right number. I am using the Contacts Framework. Help would be much appreciated.
func picker () {
let numbers = ["555","8885555512"]
let pick = CNContactPickerViewController()
pick.displayedPropertyKeys = [CNContactPhoneNumbersKey]
pick.predicateForEnablingContact = NSPredicate(format: "phoneNumbers = %#", argumentArray: numbers)
pick.delegate = self
presentViewController(pick, animated: true, completion: nil)
}
func contactPicker(picker: CNContactPickerViewController, didSelectContacts contacts: [CNContact]) {
let Kontakte = contacts
print(Kontakte)
dismissViewControllerAnimated(true, completion: nil)
}

This code returns only the contacts that have a phone number in the given set:
let numbers = Set(["555","8885555512"])
let predicate = NSPredicate { (evaluatedObject, bindings) -> Bool in
guard let evaluatedContact = evaluatedObject as? CNContact else { return false }
return Set(evaluatedContact.phoneNumbers.map{ return ($0.value as! CNPhoneNumber).stringValue }).intersect(numbers).count > 0
}
Note that numbers is a Set. This is in order to use the intersect method later on. intersect is used to determine if the contact shares any phone numbers with the given numbers; if so, then the predicate results in true.

I used the KPKContacts package
Its as simple as calling this method
var contacts: [KPKContact]()
let contactStore = KPKContactStore()
//make sure you implement the delegate method that will notify contact authorisation changes
contactStore.delegate = self
self.kpkContactStore.findContactsWithValidNumbersOnly(){
kpkContacts in
if let contacts = kpkContacts {
self.contacts = contacts
self.tableView.reloadData()
}
}
It comes with a default regex to phone numbers formatted as ### ### ###, ###-###-###, (###) ###-####, #-###-###-###, ###-###-###, #########
You can alternatively get your own regex from regex resources and edit the PHONE_REGEX property in this method
private var regexPhoneNumberValidatorBlock: String -> Bool = { value in
let PHONE_REGEX = "^\\s*(?:\\+?(\\d{1,3}))?[-. (]*(\\d{3})[-. )]*(\\d{3})[-. ]*(\\d{4})(?: *x(\\d+))?\\s*$"
let phoneTest = NSPredicate(format: "SELF MATCHES %#", PHONE_REGEX)
let result = phoneTest.evaluateWithObject(value)
return result
}

Related

How to get the selected contactProperty from CNContactPickerViewController

I'm currently able to get a contact from the contacts app, but the problem I'm facing that I need to be able to select the contact I want to import to my app , if the contact have more than 1 phone number, I always get the first number, here is the code I'm using:
func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) {
let numbers = contactProperty.contact.phoneNumbers.first
let firstName = contactProperty.contact.givenName
let lastName = contactProperty.contact.familyName
let phoneNumber = (numbers?.value)?.stringValue ?? ""
/// Duplicate phone numbers will not be saved
if phoneNumbers.contains(phoneNumber) {
return
}
/// Saving selected contact in Core Data
CoreDataManager.sharedInstance.savePhoneNumberInCoreData(FirstName: firstName, LastName: lastName, PhoneNumber: phoneNumber)
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
The problem with line:
contactProperty.contact.phoneNumbers.first
There are two options only for contactProperty.contact.phoneNumbers .first or .last
If there is something like .selected, it would solve the problem.
There is something called Main telephone number that you could use
var phoneNumber: String?
if let mainNumber = numbers.first(where: { $0.label == CNLabelPhoneNumberMain }) {
phoneNumber = mainNumber.value.stringValue
} else {
phoneNumber = numbers.first?.value.stringValue //or some other default value
}
Note that I changed the definition of numbers to be the array of phone numbers
let numbers = contactProperty.contact.phoneNumbers
Full code:
func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) {
let numbers = contactProperty.contact.phoneNumbers
var phoneNumber: String?
if let mainNumber = numbers.first(where: { $0.label == CNLabelPhoneNumberMain }) {
phoneNumber = mainNumber.value.stringValue
} else {
phoneNumber = numbers.first?.value.stringValue //or some other default value
}
if phoneNumber == nil || phoneNumbers.contains(phoneNumber) {
return
}
let firstName = contactProperty.contact.givenName
let lastName = contactProperty.contact.familyName
CoreDataManager.sharedInstance.savePhoneNumberInCoreData(FirstName: firstName, LastName: lastName, PhoneNumber: phoneNumber)
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
I'm agree with solution of Joakim Danielson.
But there are one more solution to get specific phone number which is stored in mobile number like home, mobile, fax etc.
Get all numbers from contact and enumerate on every number and check labeled values. See following code.
let numbers = contact.phoneNumbers
numbers.forEach { (c) in
if let label = c.label {
let localizedLabel = CNLabeledValue<NSCopying & NSSecureCoding>.localizedString(forLabel: label)
print("\(localizedLabel)")
switch localizedLabel.lowercased() {
case "home":
let homeNumber = c.value
break
case "mobile":
let mobileNumber = c.value
break
default:
break
}
}
}
contactProperty.contacts is a back-reference to the CNContact the selected property lives in...
Each property is represented by an instance of CNContactProperty, which provides a tuple that can contain three or five values, depending on whether the property is a member of an array of labeled values.
CNContactProperty
So, you should use the property's Information vars directly:
For example, the phoneNumbers property is a member of an array of labeled values, so the CNContactProperty tuple contains the contact, key, value, identifier, and label.
CNContactProperty
NOTE: I learned this from reading another S-O answer, but I can't seem to find it right now. If appropriate, dupe or edit thus,

Cloudkit Fetch very slow

Running the below code to fetch data from Cloudkit, at the moment it is taking a long to populate a tableView, depending on how many results there are, but if there are over 15 results it takes 10 seconds plus. Are they any ways I can speed this up?
This is my fetch func:
func loadData() {
venues = [CKRecord]()
let location = locationManager.location
let radius = CLLocationDistance(500)
let sort = CKLocationSortDescriptor(key: "Location", relativeLocation: location!)
let predicate = NSPredicate(format: "distanceToLocation:fromLocation:(%K,%#) < %f", "Location", location!, radius)
let publicData = CKContainer.defaultContainer().publicCloudDatabase
let query = CKQuery(recordType: "Venues", predicate: predicate )
query.sortDescriptors = [sort]
publicData.performQuery(query, inZoneWithID: nil) { (results:[CKRecord]?, error:NSError?) in
if let venues = results {
self.venues = venues
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
self.refreshControl.endRefreshing()
self.tableView.hidden = false
})
}
}
}
This is my tableView func:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! NearMe2ViewCell
if venues.count == 0 {
return cell
}
let venue = venues[indexPath.row]
print(indexPath.row)
let venueLocation = venue["Location"] as? CLLocation
let venueTitle = (venue["Name"] as! String)
let venueImages = venue["VenuePhoto"] as! CKAsset
let userLocation = locationManager.location
let distanceBetween: CLLocationDistance = (venueLocation!.distanceFromLocation(userLocation!))
self.venueDistance = String(format: "%.f", distanceBetween)
cell.venueDistance?.text = venueDistance
cell.venueName.text = venueTitle
cell.venueImage?.image = UIImage(contentsOfFile: venueImages.fileURL.path!)
return cell
}
You should search for the record keys first, so a fetchOperation would include this directive.
fetchOperation.desiredKeys = ["record.recordID.recordName"]
That should be faster. Break your returned keys into the size you can display on the screen and go get them only. After you display them, go get the next batch in background thread, when you got that the next batch on background etc etc etc.
Should add perhaps, that fetching the asset should be done on a separate thread too if possible, updating the table as you pull in the assets by reloading the table repeatedly.
Here is method to search and return keys.
func zap(theUUID:String) {
var recordID2Zap: String!
let predicate = NSPredicate(format: "(theUUID = %#)",theUUID)
let query = CKQuery(recordType: "Blah", predicate: predicate)
let searchOperation = CKQueryOperation(query: query)
searchOperation.desiredKeys = ["record.recordID.recordName"]
searchOperation.recordFetchedBlock = { (record) in
recordID2Zap = record.recordID.recordName
}
if error != nil {
print("ting, busted",error!.localizedDescription)
} else {
print("ok zapping")
if recordID2Zap != nil {
self.privateDB.delete(withRecordID: CKRecordID(recordName: recordID2Zap), completionHandler: {recordID, error in
NSLog("OK or \(error)")
})
}
}
}
searchOperation.qualityOfService = .background
privateDB.add(searchOperation)
theApp.isNetworkActivityIndicatorVisible = true
}
}
As for your tableview, and images... use the completion in your icloud code to send a notification to the table view.
database.fetchRecordWithID(CKRecordID(recordName: recordId), completionHandler: {record, error in
let directDict = ["blah": "whatever"] as [String : String]
NotificationCenter.default.post(name: Notification.Name("blahDownloaded"), object: nil, userInfo: directDict)
}
And in the VC you register said notification.
NotificationCenter.default.addObserver(self, selector: #selector(blahDownloaded), name: Notification.Name("blahDownloaded"), object: nil)
func blahDownloaded(notification: NSNotification) {
if let userInfo = notification.userInfo as NSDictionary? as? [String: Any] {
//update you cell
//reload your table
}
Does that all make sense?
Your operation's qualityOfService is defaulting to .utility.
There is an important note in the documentation for CKOperation that states:
CKOperation objects have a default quality of service level of NSQualityOfServiceUtility (see qualityOfService). Operations at this level are considered discretionary, and are scheduled by the system for an optimal time based on battery level and other factors.
Because CKOperation inherits from NSOperation you can configure the qualityOfService property when your user is waiting on a request to finish. Here is some example code based off of yours above:
let queryOperation = CKQueryOperation(query: query)
queryOperation.recordFetchedBlock = ...
queryOperation.queryCompletionBlock = ...
queryOperation.qualityOfService = .userInteractive
publicData.add(queryOperation)
Notice that this example explicitly creates a CKQueryOperation instead of using the convenience API because it then gives you the flexibility to fully configure your operations before you enqueue them to be sent to the server.
In this case you can set the qualityOfService to .userInteractive because your user is actively waiting on the request to finish before they can use your app any further. Learn more about the possible values at https://developer.apple.com/library/content/documentation/Performance/Conceptual/EnergyGuide-iOS/PrioritizeWorkWithQoS.html

Swift 3: What's the safest way to unwrap optional values coming from an array?

First, I initialize the variables to hold the stock data
var applePrice: String?
var googlePrice: String?
var twitterPrice: String?
var teslaPrice: String?
var samsungPrice: String?
var stockPrices = [String]()
I fetch current stock prices from YQL, and put those values into an array
func stockFetcher() {
Alamofire.request(stockUrl).responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let json = JSON(responseData.result.value!)
if let applePrice = json["query"]["results"]["quote"][0]["Ask"].string {
print(applePrice)
self.applePrice = applePrice
self.tableView.reloadData()
}
if let googlePrice = json["query"]["results"]["quote"][1]["Ask"].string {
print(googlePrice)
self.googlePrice = googlePrice
self.tableView.reloadData()
}
if let twitterPrice = json["query"]["results"]["quote"][2]["Ask"].string {
print(twitterPrice)
self.twitterPrice = twitterPrice
self.tableView.reloadData()
}
if let teslaPrice = json["query"]["results"]["quote"][3]["Ask"].string {
print(teslaPrice)
self.teslaPrice = teslaPrice
self.tableView.reloadData()
}
if let samsungPrice = json["query"]["results"]["quote"][4]["Ask"].string {
print(samsungPrice)
self.samsungPrice = samsungPrice
self.tableView.reloadData()
}
let stockPrices = ["\(self.applePrice)", "\(self.googlePrice)", "\(self.twitterPrice)", "\(self.teslaPrice)", "\(self.samsungPrice)"]
self.stockPrices = stockPrices
print(json)
}
}
}
in cellForRowAt indexPath function I print to the label
if self.stockPrices.count > indexPath.row + 1 {
cell.detailTextLabel?.text = "Current Stock Price: \(self.stockPrices[indexPath.row])" ?? "Fetching stock prices..."
} else {
cell.detailTextLabel?.text = "No data found"
}
I'm running into the issue of printing Current Stock Price: Optional("stock price"), with the word optional. I gather that this is because I'm giving it an array of optional values, but I sort of have to since I actually don't know if there will be data coming from YQL, one of the 5 stocks might be nil while the others have data. From reading other similar questions I can see that the solution would be to unwrap the value with !, but I'm not so sure how to implement that solution when it's an array with data that might be nil, and not just an Int or something.
How can I safely unwrap here and get rid of the word Optional?
First off:
Any time you repeat the same block of code multiple times and only increase a value from 0 to some max, it is a code smell. You should think about a different way to handle it.
You should use an array to do this processing.
How about a set of enums for indexes:
enum companyIndexes: Int {
case apple
case google
case twitter
case tesla
//etc...
}
Now you can run through your array with a loop and install your values more cleanly:
var stockPrices = [String?]()
Alamofire.request(stockUrl).responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let json = JSON(responseData.result.value!)
let pricesArray = json["query"]["results"]["quote"]
for aPriceEntry in pricesArray {
let priceString = aPriceEntry["ask"].string
stockPrices.append(priceString)
}
}
}
And to fetch a price from the array:
let applePrice = stockPrices[companyIndexes.apple.rawValue]
That will result in an optional.
You could use the nil coalescing operator (??) to replace a nil value with a string like "No price available.":
let applePrice = stockPrices[companyIndexes.apple.rawValue] ?? "No price available"
or as shown in the other answer:
if let applePrice = stockPrices[companyIndexes.apple.rawValue] {
//we got a valid price
} else
//We don't have a price for that entry
}
I'm writing this outside of Xcode (so there might be typos), but this kind of logic should work.
if self.stockPrices.count > indexPath.row + 1 {
var txt = "Fetching stock prices..."
if let price = self.stockPrices[indexPath.row] {
txt = price
}
cell.detailTextLabel?.text = txt
} else {
cell.detailTextLabel?.text = "No data found"
}
For safe unwrap use that code:
if let currentStockPrice = self.stockPrices[indexPath.row]
{
// currentStockPrice available there
}
// currentStockPrice unavailable
If you need to unwrap multiple variables in one if after another it may lead to unreadable code. In such case use this pattern
guard let currentStockPrice = self.stockPrices[indexPath.row]
else
{
// currentStockPrice is unavailable there
// must escape via return, continue, break etc.
}
// currentStockPrice is available

CoreData attribute returns as nil

I am working on a app which uses core data contains multiple lists (each one is a separate tab on a tabViewController). Now i have some issues listed below.
There is only one entity with multiple attributes for all the user inputted data.
Each list tab is segregated by a bool as true/false. If it's true it's on the list, if false it's not.
However, if I attempt to fetch them on another tab (another VC) by fetchedResultsController and NSPredicate they return as nil, or give an error of not key coding compliant.
How do I get the attribute data to return on another list in order to change the data on the first list.
example: list 1 has item name and qty
list 2 needs to fetch the CoreData and recognize there is an item of the same name on list one and subtract list 2's qty from list 1's item's qty.
Any ideas on why this isn't working/ know how to fix it? thanks in advance
FetchRequest (on list 2):
func itemFetchRequest1() -> NSFetchRequest{
let fetchRequest = NSFetchRequest(entityName: "List")
fetchRequest.predicate = NSPredicate(format:"pitem =%#", item.pitem!)
return fetchRequest
}
Doing the math:
func subtractPqty(){
if (item.ringredients == item.pitem){
//get value of string
let stringNumber0 = item.rqty0
let stringNumber1 = item.pqty
//convert string to Int
let numberFromString0 = Int(stringNumber0!)
let numberFromString1 = Int(stringNumber1!)
//get sum of Int
let sum = (numberFromString1)! - (numberFromString0)!
let myInt:Int = sum
//convert back Int back to string
let myString:String = String(myInt)
//delclare string as qty.
item.pqty = myString
}else{
print(item.pitem)
print(item.ringredients)
}
}
If it makes more sense to see the func that creates an item, I have included it as well.
func createNewitem() {
guard self.item == nil else {
print("trying to create a new item while there is already one.")
return
}
// just creating an empty item and let give the job to filling it to editItem method.
let entityDescription = NSEntityDescription.entityForName("List", inManagedObjectContext: moc)
// assign the empty item to self.item.
let item = List(entity: entityDescription!, insertIntoManagedObjectContext: moc)
item.mplist = true
item.mpcross = false
// assign the new item to self.item
self.item = item
item.mpitem = Recipe.text
item.mpcategory = mealOfDay.text
item.ringredients = recipeItem.text
item.mpdate = dayOfWeek.text
item.rqty0 = rqty.text
itemFetchRequest1()
func subtractPqty()
}
The first list is created the same just with plist, pcross, pitem etc.
Your help is greatly appreciated!

What to use instead of indexPath.row? for non-table?

I'm working with a mapview not a tableview, but I don't know what to use to replace indexPath.row.
I have a mapview with annotations, when the info button of an annotation is pressed I then query my CK database and return the record that has a name field matching the name of the annotation pressed. This returns an [CKRecord] with a single record, as there are no matching names.
At this point, with a tableview I would do the following to access the data...
let placeInfo = selectedData[indexPath.row]
let placeName = placeInfo.objectForKey("Name") as! String
let placeCity = placeInfo.objectForKey("City") as! String
However, since I'm not using a tableview, I don't have an indexPath to use. Since my [CKRecord] object only contains a single record, I thought I could replace indexPath.row with the array location of the record...
let placeInfo = selectedPlace[0] //also tried 1
That lines produces an Index out of range error.
I've tried everything that I know, and as you may imagine, I'm not exactly great at swift or programming in general at this point.
Here is the full mapView function I am using...
func mapView(mapView: MKMapView, annotationView: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let cloudContainer = CKContainer.defaultContainer()
let publicData = cloudContainer.publicCloudDatabase
let tappedPlace = annotationView.annotation!.title!! as String
let predi = NSPredicate(format: "Name = %#", tappedPlace)
let iquery = CKQuery(recordType: "Locations", predicate: predi)
publicData.performQuery(iquery, inZoneWithID: nil, completionHandler: {
(results, error) -> Void in
if error != nil {
print(error)
return
}
if let results = results {
print("Downloaded data for selected location for \(tappedPlace)")
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.selectedPlace = results
}
}
})
let placeInfo = selectedPlace[0]
let placeName = placeInfo.objectForKey("Name") as! String
//returns Index out of range error for placeInfo line
//need data before segue
//performSegueWithIdentifier("fromMap", sender: self)
}
Your problem is that, you try to access selectedPlace before it is actually signed by your completion handler. Your 'publicData.performQuery' seems to be an asynchronous operation, and this means that, the control will come out from this call even before the completion handler gets executed(this is expected in case of an asynchronous call). And you reach the line immediately-
let placeInfo = selectedPlace[0]
But the data is not ready yet, and you get the exception. Now to solve this, move place info extraction, and perform segue code inside the completion handler as shown-
publicData.performQuery(iquery, inZoneWithID: nil, completionHandler: {
(results, error) -> Void in
if error != nil {
print(error)
return
}
if let results = results {
print("Downloaded data for selected location for \(tappedPlace)")
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.selectedPlace = results
if(results.count > 0){
let placeInfo = selectedPlace[0]
let placeName = placeInfo.objectForKey("Name") as! String
//Do any other computations as needed.
performSegueWithIdentifier("fromMap", sender: self)
}
}
}
})
This should fix your problem.

Resources