I am fetching user's information like his name,phone number and email id from contacts.But it is only showing first contact number.IF a person has more than one contact number,it didnt show that second number.Can someone help?I am using this function
where EVContactProtocol is part of Library
func didChooseContacts(_ contacts: [EVContactProtocol]?) {
var conlist : String = ""
if let cons = contacts {
for con in cons {
if let fullname = con.fullname(),let email1 = con.email , let phoneNumber = con.phone {
conlist += fullname + "\n"
print("Full Name: ",fullname)
print("Email: ",email1)
print("Phone Number: ",phoneNumber)
}
}
self.textView?.text = conlist
} else {
print("I got nothing")
}
let _ = self.navigationController?.popViewController(animated: true)
}
You should try this:
import Contacts
class ViewController: UIViewController
{
lazy var contacts: [CNContact] =
{
let contactStore = CNContactStore()
let keysToFetch = [
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
CNContactEmailAddressesKey,
CNContactPhoneNumbersKey] as [Any]
// Get all the containers
var allContainers: [CNContainer] = []
do
{
allContainers = try contactStore.containers(matching: nil)
}
catch
{
print("Error fetching containers")
}
var results: [CNContact] = []
// Iterate all containers and append their contacts to our results array
for container in allContainers
{
let fetchPredicate = CNContact.predicateForContactsInContainer(withIdentifier: container.identifier)
do
{
let containerResults = try contactStore.unifiedContacts(matching: fetchPredicate, keysToFetch: keysToFetch as! [CNKeyDescriptor])
results.append(contentsOf: containerResults)
}
catch
{
print("Error fetching results for container")
}
}
return results
}()
override func viewDidLoad()
{
super.viewDidLoad()
print(contacts[0].givenName)
print(contacts[0].phoneNumbers)
print(contacts[0].emailAddresses)
print(contacts)
}
}
Related
Here is my async function class:
class MoviesViewModel: ObservableObject {
#Published var topRated: [Movie] = []
#Published var popular: [Movie] = []
#Published var upcoming: [Movie] = []
func getUpcomingMovies() {
if let movies = getMovies(path: "upcoming") {
DispatchQueue.main.async {
self.upcoming = movies
}
}
}
func getPopularMovies() {
if let movies = getMovies(path: "popular") {
DispatchQueue.main.async {
self.popular = movies
}
}
}
func getTopRatedMovies() {
DispatchQueue.main.async {
if let movies = self.getMovies(path: "top_rated") {
self.topRated = movies
}
}
}
func getMovies(path: String) -> [Movie]? {
var movies: [Movie]?
let urlString = "https://api.themoviedb.org/3/movie/\(path)?api_key=\(apiKey)&language=en-US&page=1"
guard let url = URL(string: urlString) else { return [] }
let session = URLSession.shared
let dataTask = session.dataTask(with: url, completionHandler: { data, _, error in
if error != nil {
print(error)
}
do {
if let safeData = data {
let decodedData = try JSONDecoder().decode(NowPlaying.self, from: safeData)
DispatchQueue.main.async {
movies = decodedData.results
}
}
}
catch {
print(error)
}
})
dataTask.resume()
return movies
}
}
When I printed the movies in getMovies function, I can get movies from api without problem. However, UI does not update itself. I used DispatchQueue.main.async function but it did not solve my problem. What can I do in this situation?
dataTask works asynchronously. Your code returns nil even before the asynchronous task is going to start. You have to use a completion handler as described in Returning data from async call in Swift function.
I highly recommend to use async/await in this case. You get rid of a lot of boilerplate code and you don't need to care about dispatching threads.
#MainActor
class MoviesViewModel: ObservableObject {
#Published var topRated: [Movie] = []
#Published var popular: [Movie] = []
#Published var upcoming: [Movie] = []
func getUpcomingMovies() async throws {
self.upcoming = try await getMovies(path: "upcoming")
}
func getPopularMovies() async throws {
self.popular = try await getMovies(path: "popular")
}
func getTopRatedMovies() async throws {
self.topRated = try await getMovies(path: "top_rated")
}
func getMovies(path: String) async throws -> [Movie] {
let urlString = "https://api.themoviedb.org/3/movie/\(path)?api_key=\(apiKey)&language=en-US&page=1"
guard let url = URL(string: urlString) else { throw URLError(.badURL) }
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(NowPlaying.self, from: data).results
}
}
I am having initialization trouble with an exchange rate structure. In the method getRates I have been trying to implement dictionary key / value logic to copy exchange rates into an ordered array. I am getting the error "Variable 'moneyRates' used before being initialized". I tried adding a memberwise initializer but was unsure how to initialize the rate array. I have also been wondering if I should move the instance of MoneyRates to the top of the class instead of in the getRates method.
var currCode: [String] = ["usd", "afn", "all", "dzd", "amd", "ang", "aoa", "ars", "aud", "awg", "azn",
/* b */ "bsd", "bhd", "bdt", "bbd", "byr", "bzd", "bmd", "btn", "bob", "bam", "bwp", "brl", "bnd", "bgn", "bif",
/* c */ "cad", "khr", "cve", "xcd", "kyd", "xaf", "xof", "xpf", "clf", "clp", "cnh", "cny", "cop", "kmf", "cdf", "crc", "hrk", "cup", "czk"]
struct MoneyRates {
let date: String
let base: String
let rates: [String: Double]
}
class CurrencyRates: ObservableObject {
#Published var rateArray = [Double] ()
init() {
if UserDefaults.standard.array(forKey: "rates") != nil {
rateArray = UserDefaults.standard.array(forKey: "rates") as! [Double]
} else {
rateArray = [Double] (repeating: 0.0, count: 170)
UserDefaults.standard.set(self.rateArray, forKey: "rates")
}
}
// retrieve exchange rates for all 150+ countries from internet and save to rateArray
func updateRates(baseCur: String) {
print("doing update")
let baseUrl = "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/"
let requestType = ".json"
guard let url = URL(string: baseUrl + baseCur + requestType) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do {
let result = try JSONSerialization.jsonObject(with: data) as! [String:Any]
var keys = Array(result.keys)
if let dateIndex = keys.firstIndex(of: "date"),
let date = result[keys[dateIndex]] as? String, keys.count == 2 {
keys.remove(at: dateIndex)
let base = keys.first!
print("base = \(base)")
print("date = \(date)")
let rates = MoneyRates(date: date, base: base, rates: result[base] as! [String:Double])
print(rates)
self.getRates(rates: rates, baseCur: baseCur)
}
} catch {
print(error)
}
}
}.resume()
}
// copy rates from MoneyRates to rateArray
func getRates (rates: MoneyRates, baseCur: String) -> () {
var moneyRates: MoneyRates
var currencyCode: String = ""
// loop through all available currencies
// the rates are stored in the same order as currCode
for index in 0..<currCode.count {
currencyCode = currCode[index]
// special handling for base currency
if currencyCode == baseCur {
rateArray[index] = 1.0000
} else {
print(currencyCode)
if moneyRates.rates[currencyCode] != nil { // error here
let unwrapped = (moneyRates.rates[currencyCode]!) // and here (same)
print( unwrapped)
rateArray[index] = 1.0 / unwrapped // want inverse exchange rate
}
}
UserDefaults.standard.set(self.rateArray, forKey: "rates")
}
}
}
The error you are getting is because you declare the variable "moneyRates"
but you do not instantiate it to something.
var moneyRates: MoneyRates
In other words there is nothing in "moneyRates",
but you are trying to get something from it in:
if moneyRates.rates[currencyCode] != nil {
...
}
So populate "moneyRates" with some data.
I need help with currency exchange rate lookup given a key (3 digit currency code). The JSON object is rather unusual with no lablels such as date, timestamp, success, or rate. The first string value is the base or home currency. In the example below it is "usd" (US dollars).
I would like to cycle through all the currencies to get each exchange rate by giving its 3 digit currency code and storing it in an ordered array.
{
"usd": {
"aed": 4.420217,
"afn": 93.3213,
"all": 123.104693,
"amd": 628.026474,
"ang": 2.159569,
"aoa": 791.552347,
"ars": 111.887966,
"aud": 1.558363,
"awg": 2.164862,
"azn": 2.045728,
"bam": 1.9541,
"bbd": 2.429065,
"bch": 0.001278
}
}
In a slightly different formatted JSON object I used the following loop to copy exchange rates to an ordered array.
for index in 0..<userData.rateArray.count {
currencyCode = currCode[index]
if let unwrapped = results.rates[currencyCode] {
userData.rateArray[index] = 1.0 / unwrapped
}
}
The follow code is the API used to get the 3 digit currency codes and the exchange rates (called via UpdateRates).
class GetCurrency: Codable {
let id = UUID()
var getCurrencies: [String : [String: Double]] = [:]
required public init(from decoder: Decoder) throws {
do{
print(#function)
let baseContainer = try decoder.singleValueContainer()
let base = try baseContainer.decode([String : [String: Double]].self)
for key in base.keys{
getCurrencies[key] = base[key]
}
}catch{
print(error)
throw error
}
}
}
class CurrencyViewModel: ObservableObject{
#Published var results: GetCurrency?
#Published var selectedBaseCurrency: String = "usd"
func UpdateRates() {
let baseUrl = "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/"
let baseCur = selectedBaseCurrency // usd, eur, cad, etc
let requestType = ".json"
guard let url = URL(string: baseUrl + baseCur + requestType) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do{
let decodedResponse = try JSONDecoder().decode(GetCurrency.self, from: data)
DispatchQueue.main.async {
self.results = decodedResponse
// this prints out the complete table of currency code and exchange rates
print(self.results?.getCurrencies["usd"] ?? 0.0)
}
} catch {
//Error thrown by a try
print(error)//much more informative than error?.localizedDescription
}
}
if error != nil {
//data task error
print(error!)
}
}.resume()
}
}
Thanks lorem ipsum for your help. Below is the updated ASI logic that copies the exchange rates to the rateArray using key/value lookups.
class CurrencyViewModel: ObservableObject{
#Published var results: GetCurrency?
#Published var rateArray = [Double] ()
init() {
if UserDefaults.standard.array(forKey: "rates") != nil {
rateArray = UserDefaults.standard.array(forKey: "rates") as! [Double]
}else {
rateArray = [Double] (repeating: 0.0, count: 160)
UserDefaults.standard.set(self.rateArray, forKey: "rates")
}
}
func updateRates(baseCur: String) {
...
DispatchQueue.main.async {
self.results = decodedResponse
// loop through all available currencies
for index in 0..<currCode.count {
currencyCode = currCode[index]
// spacial handling for base currency
if currencyCode == baseCur {
self.rateArray[index] = 1.0000
} else {
let homeRate = self.results?.getCurrencies[baseCur]
// complement and save the exchange rate
if let unwrapped = homeRate?[currencyCode] {
self.rateArray[index] = 1.0 / unwrapped
}
}
}
}
} catch {
//Error thrown by a try
print(error)//much more informative than error?.localizedDescription
}
}
if error != nil {
//data task error
print(error!)
}
}.resume()
}
}
I'm trying to get contacts (swift 3) in ios 9
func getInnerContacts()-> Observable<[CNContact]>{
return Observable<[CNContact]>.create({ (observer) -> Disposable in
var contacts = [CNContact]()
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName), CNContactPhoneNumbersKey] as [Any]
self.contactStore.requestAccess(for: .contacts, completionHandler: { (granted, error) -> Void in
if granted {
let predicate = CNContact.predicateForContactsInContainer(withIdentifier: self.contactStore.defaultContainerIdentifier())
do {
contacts = try self.contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch as! [CNKeyDescriptor])
contacts = newContacts.filter({ (contact) -> Bool in
return !contact.phoneNumbers.isEmpty && contact.givenName != "" || !contact.phoneNumbers.isEmpty && contact.familyName != ""
})
observer.onNext(contacts)
observer.onCompleted()
}catch {
observer.onError(error)
}
}
})
return Disposables.create()
})
}
but it doesn't work, moreover, I can't see permission alert.
Before executing i first check the iOS version, like this:
if #available(iOS 10 , *){
}else if #available(iOS 9, *){
}
then i added permission tag Privacy - Contacts Usage Description into Info.plist file. This works fine in ios 10, any ideas how can I achieve this in ios 9
It will work fine in Swift 3.
var store = CNContactStore()
var contacts = [CNContact]()
Try this in viewDidLoad():
let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, CNContactEmailAddressesKey, CNContactOrganizationNameKey, CNContactImageDataKey]
let request = CNContactFetchRequest(keysToFetch: keysToFetch as [CNKeyDescriptor])
do {
try self.store.enumerateContacts(with: request) { contact, stop in
self.contacts.append(contact)
}
DispatchQueue.main.async(execute: {
for contact in self.contacts {
print("contact:\(contact)")
let firstName=String(format:"%#",contact.givenName)
// print("first:\(firstName)")
self.givenNameArray.append(firstName)
let lastName=String(format:"%#",contact.familyName)
// print("last:\(lastName)")
self.familyNameArray.append(lastName)
let comapny=String(format:"%#",contact.organizationName)
// print("company:\(comapny)")
self.organizationNameArray.append(comapny)
//get all phone numbers
// for ContctNumVar: CNLabeledValue in contact.phoneNumbers
// {
// let MobNumVar = (ContctNumVar.value ).value(forKey: "digits") as? String
// print("ph no:\(MobNumVar!)")
// get one phone number
let MobNumVar = (contact.phoneNumbers[0].value ).value(forKey: "digits") as! String
// print("mob no:\(MobNumVar)")
self.phonenosArray.append(MobNumVar)
// get all emails
// for ContctNumVar1: CNLabeledValue in contact.emailAddresses
// {
// print("email \(ContctNumVar1.value(forKey: "value") as! String)")
// }
// get one email
let email = (contact.emailAddresses[0]).value(forKey: "value") as! String
// print("email:\(email)")
self.emailsArray.append(email)
let imagedat=contact.imageData
// print("image data:\(imagedat)")
let image=UIImage(data: imagedat!)
// print("image:\(image)")
self.imagesArray.append(image!)
}
})
} catch {
print(error)
}
I use a general CoreData query method in my project.
func query(table: String, searchPredicate: NSPredicate) -> [AnyObject]
{
let context = app.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: table)
fetchRequest.predicate = searchPredicate
let results = try! context.fetch(fetchRequest)
return results
}
In Swift 3 this doesn't work. I found this on Apple's web site:
func findAnimals()
{
let request: NSFetchRequest<Animal> = Animal.fetchRequest
do
{
let searchResults = try context.fetch(request)
... use(searchResults) ...
}
catch
{
print("Error with request: \(error)")
}
}
Using the Apple example, how would I pass Animal in to the method as a parameter to make findAnimals more generic?
I haven't tried this but I think something like this would work...
func findCoreDataObjects<T: NSManagedObject>() -> [T] {
let request = T.fetchRequest
do
{
let searchResults = try context.fetch(request)
... use(searchResults) ...
}
catch
{
print("Error with request: \(error)")
}
}
You have to make the entire function generic and so you have to tell it what type T is when calling it.
someObject.findCoreDataObjects<Animal>()
I think that should do the job. Not entirely certain though as I'm new to generics myself :D
How about this.
func query<T: NSManagedObject>(table: String, searchPredicate: NSPredicate) -> [T] {
let context = app.managedObjectContext
let fetchRequest: NSFetchRequest<T> = NSFetchRequest(entityName: table)
fetchRequest.predicate = searchPredicate
let results = try! context.fetch(fetchRequest)
return results
}
Here is the final result that may help someone:
import Foundation
import Cocoa
func addRecord<T: NSManagedObject>(_ type : T.Type) -> T
{
let entityName = T.description()
let context = app.managedObjectContext
let entity = NSEntityDescription.entity(forEntityName: entityName, in: context)
let record = T(entity: entity!, insertInto: context)
return record
}
func recordsInTable<T: NSManagedObject>(_ type : T.Type) -> Int
{
let recs = allRecords(T.self)
return recs.count
}
func allRecords<T: NSManagedObject>(_ type : T.Type, sort: NSSortDescriptor? = nil) -> [T]
{
let context = app.managedObjectContext
let request = T.fetchRequest()
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}
func query<T: NSManagedObject>(_ type : T.Type, search: NSPredicate?, sort: NSSortDescriptor? = nil, multiSort: [NSSortDescriptor]? = nil) -> [T]
{
let context = app.managedObjectContext
let request = T.fetchRequest()
if let predicate = search
{
request.predicate = predicate
}
if let sortDescriptors = multiSort
{
request.sortDescriptors = sortDescriptors
}
else if let sortDescriptor = sort
{
request.sortDescriptors = [sortDescriptor]
}
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}
func deleteRecord(_ object: NSManagedObject)
{
let context = app.managedObjectContext
context.delete(object)
}
func deleteRecords<T: NSManagedObject>(_ type : T.Type, search: NSPredicate? = nil)
{
let context = app.managedObjectContext
let results = query(T.self, search: search)
for record in results
{
context.delete(record)
}
}
func saveDatabase()
{
let context = app.managedObjectContext
do
{
try context.save()
}
catch
{
print("Error saving database: \(error)")
}
}
Call it with:
let name = "John Appleseed"
let newContact = addRecord(Contact.self)
newContact.contactNo = 1
newContact.contactName = name
let contacts = query(Contact.self, search: NSPredicate(format: "contactName == %#", name))
for contact in contacts
{
print ("Contact name = \(contact.contactName), no = \(contact.contactNo)")
}
deleteRecords(Contact.self, search: NSPredicate(format: "contactName == %#", name))
recs = recordsInTable(Contact.self)
print ("Contacts table has \(recs) records")
saveDatabase()
I use that way in my projects:
static func retrieveRecords<T: NSManagedObject>(table: String, sortDescriptorKey: NSSortDescriptor? = nil) -> [T] {
do {
let fetchRequest: NSFetchRequest<T> = NSFetchRequest(entityName: table)
fetchRequest.sortDescriptors = [sortDescriptorKey!]
let results = try context.fetch(fetchRequest)
print("\(results)")
return results
} catch let error {
print("Could not fetch \(error.localizedDescription)")
return []
}
}
And to call it:
personen = retrieveRecords(table: "Person", sortDescriptorKey: NSSortDescriptor(key: #keyPath(Person.nachname), ascending: true, selector: #selector(NSString.localizedCompare)))