how to remove countryCode from fetched Contacts in swift4? - swift3

func fetchContacts(){
let key = [CNContactGivenNameKey, CNContactPhoneNumbersKey] as [CNKeyDescriptor]
let request = CNContactFetchRequest(keysToFetch: key)
try! contactStore.enumerateContacts(with: request) { (contact, stoppingPointer) in
let name = contact.givenName
let number = contact.phoneNumbers.first?.value.stringValue
let contactToAppend = ContactStruct(name: name, number: number)
self.contacts.append(contactToAppend)
self.queryPhoneNumber()
}
tableView.reloadData()
}
In this way I'm getting contact numbers(with country code) and names of the contacts. How to get the contacts without country code.please help me in solving this problem. Thank you.

Related

Run CKQueryOperation with results from previous CKQueryOperation

I have an app that is a shopping list. I can store prices per product and vendor in my app, the model is
Product
Vendor
Price
One product can have multiple prices from different vendors.
I store the price information with references to the product and vendor (CKRecord.Reference).
Now I am using the below to fetch all the prices related to a product:
public func fetchDataByProduct(product: Product, completionHandler: #escaping (Bool) -> Void){
self.pricesBuffer = []
let cloudContainer = CKContainer.init(identifier: "iCloud.XYZ")
let publicDatabase = cloudContainer.publicCloudDatabase
let reference = CKRecord.Reference(recordID: product.recordID, action: .deleteSelf)
let predicate = NSPredicate(format: "priceToProduct == %#", reference)
let query = CKQuery(recordType: "Price", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.recordFetchedBlock = { record in
let price = Price()
price.recordID = record.recordID
price.grossPrice = record.object(forKey: "grossPrice") as? Double
let dummy = record.object(forKey: "priceToVendor") as! CKRecord.Reference
price.vendorRecordID = dummy.recordID
self.pricesBuffer.append(price)
}
operation.queryCompletionBlock = { [unowned self] (cursor, error) in
self.pricesBuffer.forEach({price in
price.retrieveVendor()
})
DispatchQueue.main.async {
if error == nil {
self.prices = self.pricesBuffer
completionHandler(true)
} else {
}
}
}
publicDatabase.add(operation)
}
My problem is now that I cannot retrieve the vendor name which is part of the Vendor object (Vendor.name).
I have tried to loop over the pricesBuffer and run this one per price but the problem seems to be that CloudKit first completes the initial request to fetchDataByProduct() and then afterwards fetches the vendor data but then its too late because that updated data does not get pushed to my View (SwiftUI).
publicDatabase.fetch(withRecordID: self.vendorRecordID, completionHandler:  {[unowned self] record, error in
if let record = record {
print(record)
self.vendor.recordID = record.recordID
self.vendor.name = record["name"] as! String
print(self.vendor.name)
}
})
Any ideas how to solve this? I believe I have to add a second CKQueryOperation to the mix and use the .addDependency() but I cannot wrap my head around how that should look like in the end.
Let's say you use the operation to fetch prices like above.
let predicate = NSPredicate(format: "priceToProduct == %#", reference)
let query = CKQuery(recordType: "Price", predicate: predicate)
let pricesOperation = CKQueryOperation(query: query)
pricesOperation.database = publicDatabase // not required if you don't use OperationQueue
Then you can construct operation to fetch vendors, I will create simple operation for demo purposes.
let vendorQuery = CKQuery(recordType: "Vendor", predicate: predicate)
let vendorsOperation = CKQueryOperation(query: vendorQuery)
vendorsOperation.database = publicDatabase // not required if you don't use OperationQueue
Then you can set dependency, first fetch prices than vendors.
vendorsOperation.addDependency(pricesOperation)
And lastly submit those operations to OperationQueue
let operationQueue = OperationQueue()
operationQueue.addOperations([pricesOperation, vendorsOperation], waitUntilFinished: false)
Edit: If you don't want to use OperationQueue, simply submit those operations to database, but first set dependency before submitting the operations to be executed.
vendorsOperation.addDependency(pricesOperation)
publicDatabase.add(pricesOperation)
publicDatabase.add(vendorsOperation)

Cannot fetch multiple CKReference records from public Database in a for loop

I have a contact CKRecord with many location CKRecords ( 1 to many relationship)
Both contact CKRecord and Location CKRecord are created in public Database. I add CKReference fro contact to locaiotn via a field named owningContact on location.
ckRecord["owningContact"] = CKReference(record: contactRecord!, action: .deleteSelf)
I go to cloudKit dashboard and verify both the records exist. The location CKRecord has field owningContact that has the recordName of the contact CKRecord. I defined a function to get locations like this:
private func iCloudFetchLocations(withContactCKRecord: CKRecord, completionHandler: #escaping ([CKRecord]?, Error?) -> Void) {
var records = [CKRecord]()
let recordToMatch = CKReference(recordID: withContactCKRecord.recordID, action: .deleteSelf)
let predicate = NSPredicate(format: "owningContact == %#", recordToMatch)
// Create the query object.
let query = CKQuery(recordType: "location", predicate: predicate)
let queryOp = CKQueryOperation(query: query)
queryOp.resultsLimit = 1
queryOp.qualityOfService = .userInteractive
queryOp.recordFetchedBlock = {
records.append($0)
print($0)
}
queryOp.queryCompletionBlock = { (cursor, error) in
guard error == nil else {
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
}
return
}
if (cursor != nil) {
let newOperation = CKQueryOperation(cursor: cursor!)
newOperation.resultsLimit = queryOp.resultsLimit
newOperation.recordFetchedBlock = queryOp.recordFetchedBlock
newOperation.queryCompletionBlock = queryOp.queryCompletionBlock
self.publicDB?.add(newOperation)
}
completionHandler(records, error)
}
self.publicDB?.add(queryOp)
}
Then I call the code to fetch location CKRecord based on contact CKRecord like this:
let predicate = NSPredicate(format: "TRUEPREDICATE")
let query = CKQuery(recordType: Cloud.Entity.Contact, predicate: predicate)
publicDB?.perform(query, inZoneWith: nil, completionHandler: { (records, error) in
guard error == nil else {
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
}
return
completion(false)
}
if let contactRecords = records {
for aContactRecord in contactRecords {
// fetch Location Data
self.iCloudFetchLocations(withContactCKRecord: aContactRecord, completionHandler: { records, error in
guard error == nil else {
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
}
return
completion(false)
}
if let locationRecords = records {
}
})
}
}
})
I have two contacts the first one has been CKReferenc'ed to the location, where as the second contact is still not yet CKReferenc'ed to the location.
I think here is the problem: First time in the loop contact CKRecord information is sent by calling iCloudFetchLocations which returns immediately without waiting for cloud response, and the for loop sends the second contact and calls iCloudFetchLocations again. Since the second contact has no CKReference to the location, the call fails and I can never get to the first contact's location since it hasn't returned yet.
How to fix this?
I found that I had not set the CKReference field: owningContact as Queryable. The way I found out is printing error like this: 
if let ckerror = error as? CKError {
print(ckerror.userInfo)
print(ckerror.errorUserInfo)
self.aErrorHandler.handleCkError(ckerror: ckerror)
}
As soon as I did that it started working, Since I was in a for loop it was timing out on previous fetch I think.

if let ISO8601DateFormatter from String

I have a response that returns the date of an object and Sometimes it returns the year and sometimes returns the date with the ISO8601DateFormatter style (1995-01-01T00:00:00Z)
The question is how can i get a if let that can identify when the value is a year as String or a ISO8601Date from a string.
I ended up doing this:
let pulledDate = "1995-01-01T00:00:00Z"
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withYear, .withMonth, .withDay, .withTime, .withDashSeparatorInDate, .withColonSeparatorInTime]
if let date = dateFormatter.date(from: pulledDate ?? ""){
let units: Set<Calendar.Component> = [.year]
let comps = Calendar.current.dateComponents(units, from: date)
print("It is a date year = \(comps.year)")
}
else{
print("It is not a date")
}

How to get two data from FMDB and return a String combined two of them in swift?

I have two records in my database, 'FirstName and LastName and I wanna get two of them then return a FullName, showing FullNames in tableView. Using Swift3 and Xcode8.2.1, FMDB
here's the func of getFullName
func getFullName() -> String{
StudentDataBase.getInstance()
sharedInstance.database!.open()
let result : FMResultSet! = sharedInstance.database!.executeQuery("SELECT FirstName, LastName FROM Student_info", withArgumentsIn: nil)
let fullName = (result?.string(forColumn: "FirstName"))! + " " + (result?.string(forColumn: "LastName"))!
return fullName
}
sharedInstance is a global one, StudentDataBase is s singleton class:
class StudentDataBase : NSObject {
var database: FMDatabase? = nil
var pathToDB: String!
class func getInstance() -> StudentDataBase{
if((sharedInstance.database) == nil)
{
sharedInstance.database = FMDatabase(path: Utility.getPath("data.db"))
}
return sharedInstance
}
and my Student.swift goes like this:
import UIKit
//the model class to fetch the data from data.db
class Student : NSObject {
var studentID: String = String()
var fstName: String = String()
var lstName: String = String()
var phoneNum: String = String()
}
I got a thread goes like this enter image description here
Thank you so much if anyone can help
After editing the Query into
let result : FMResultSet! = sharedInstance.database!.executeQuery("SELECT ifnull(FirstName,'') as FirstName, ifnull(LastName,'') as LastName FROM Student_info", withArgumentsIn: nil)
There's another error in combining them into one String:
enter image description here
You are getting this crash because either you are getting nil for FirstName or LastName, and you can use ifnull with your select query and return empty string if it is nil. So change your query like this way.
let result : FMResultSet! = sharedInstance.database!.executeQuery("SELECT ifnull(FirstName,'') as FirstName, ifnull(LastName,'') as LastName FROM Student_info", withArgumentsIn: nil)
Note: You haven't added WHERE clause with query so that this will give you all the records form Student_info table also it is batter if you check result.next() to confirm result contains records or not.

Try! throwing fatal error in Swift 3, issues updating from Swift 2

I am trying to parse the JSON data from my server and I am getting an error when it hits the try! statement and it is crashing. It is telling me
Code=3840 "Invalid value around character 0.
It my be because I have not updated my code correctly to Swift 3. I was having an issue with if let parse for the longest time until I switched the as to as?
#IBAction func registerButtonTapped(_ sender: Any) {
let userEmail = userEmailTextField.text;
let userPassword = userPasswordTextField.text;
let userRepeatPassword = repeatPasswordTextField.text;
// Check for empty fields
if((userEmail?.isEmpty)! || (userPassword?.isEmpty)! || (userRepeatPassword?.isEmpty)!){
//Display alert message
displayMyAlertMessage(userMessage: "All fields are required");
return;
}
//Check if passwords matech
if(userPassword != userRepeatPassword){
// Display alert message
displayMyAlertMessage(userMessage: "Passwords do not match");
return;
}
// Send user data to server side
let myUrl = URL(string: "http://");
let request = NSMutableURLRequest(url:myUrl!);
request.httpMethod = "Post";
let postString = "email=\(userEmail)&password=\(userPassword)";
//adding the parameters to request body
request.httpBody = postString.data(using: String.Encoding.utf8);
//creating a task to send the post request
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
if error != nil{
print("error=\(error)")
return
}
//parsing the reponse
//converting response to Any
let json = try! JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) as? [String:Any]
//parsing JSON
if let parseJSON = json{
let resultValue = parseJSON["status"] as? String
print("result: \resultValue)")
var isUserRegistered:Bool = false;
if(resultValue=="Success") { isUserRegistered = true;}
var messageToDisplay:String = parseJSON["messsage"] as! String;
if(!isUserRegistered)
{
messageToDisplay = parseJSON["message"] as! String;
}
DispatchQueue.main.async {
//Display alert message with confirmation.
let myAlert = UIAlertController(title:"Alert", message:messageToDisplay, preferredStyle: UIAlertControllerStyle.alert);
let okAction = UIAlertAction(title:"ok", style:UIAlertActionStyle.default){ action in
self.dismiss(animated: true, completion:nil);
}
myAlert.addAction(okAction);
self.present(myAlert, animated:true, completion:nil);
};
}
}
task.resume()
}
Please help, thanks
The reason of the error is that you are sending literal "Optional(Foo)" strings to the server via String Interpolation. userEmail and userPassword will never match and the server sends no data back. In Swift 3 you have to explicitly unwrap even implicit unwrapped optional strings.
The solution is a waterproof error handling with optional bindings
#IBAction func registerButtonTapped(_ sender: AnyObject) {
// Check for empty fields
guard let userEmail = userEmailTextField.text, !userEmail.isEmpty,
let userPassword = userPasswordTextField.text, !userPassword.isEmpty,
let userRepeatPassword = repeatPasswordTextField.text, !userRepeatPassword.isEmpty else {
//Display alert message
displayMyAlertMessage(userMessage: "All fields are required")
return
}
...
Now all relevant optionals are safely unwrapped and the server will get the right data.
Further trailing semicolons and parentheses around if conditions are not needed in Swift and use URLRequest rather than NSMutableURLRequest in Swift 3
var request = URLRequest(url:myUrl!) // var is mandatory if properties are going to be changed.
PS: In any case – as already mentioned in the comments – never use carelessly try! when receiving data from a server.