I don't understand why my code doesn't compile with Swift.
I am trying to convert this Objective-C code:
CFErrorRef error = NULL;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error);
if (addressBook != nil) {
NSLog(@"Succesful.");
NSArray *allContacts = (__bridge_transfer NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBook);
}
This is my current rendition in Swift:
var error:CFErrorRef
var addressBook = ABAddressBookCreateWithOptions(nil, nil);
if (addressBook != nil) {
println("Succesful.");
var allContacts:CFArrayRef = ABAddressBookCopyArrayOfAllPeople(addressBook);
}
but, Xcode reports:
'Unmanaged' is not convertible to 'CFArrayRef'
Do you guys have an idea ?
Obviously, if targeting iOS version 9 or greater, you shouldn't use the AddressBook
framework at all, and instead use the Contacts
framework instead.
So,
Import Contacts
:
import Contacts
Make sure to supply a NSContactsUsageDescription
in your Info.plist
.
Then, you can then access contacts. E.g. in Swift 3:
let status = CNContactStore.authorizationStatus(for: .contacts)
if status == .denied || status == .restricted {
presentSettingsActionSheet()
return
}
// open it
let store = CNContactStore()
store.requestAccess(for: .contacts) { granted, error in
guard granted else {
DispatchQueue.main.async {
self.presentSettingsActionSheet()
}
return
}
// get the contacts
var contacts = [CNContact]()
let request = CNContactFetchRequest(keysToFetch: [CNContactIdentifierKey as NSString, CNContactFormatter.descriptorForRequiredKeys(for: .fullName)])
do {
try store.enumerateContacts(with: request) { contact, stop in
contacts.append(contact)
}
} catch {
print(error)
}
// do something with the contacts array (e.g. print the names)
let formatter = CNContactFormatter()
formatter.style = .fullName
for contact in contacts {
print(formatter.string(from: contact) ?? "???")
}
}
Where
func presentSettingsActionSheet() {
let alert = UIAlertController(title: "Permission to Contacts", message: "This app needs access to contacts in order to ...", preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "Go to Settings", style: .default) { _ in
let url = URL(string: UIApplicationOpenSettingsURLString)!
UIApplication.shared.open(url)
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert, animated: true)
}
My original answer for AddressBook framework is below.
A couple of observations:
If you want to use error
parameter of ABAddressBookCreateWithOptions
, define it to be Unmanaged<CFError>?
.
If it fails, take a look at the error object (doing takeRetainedValue
so you don't leak).
Make sure to takeRetainedValue
of the address book, too, so you don't leak.
You probably shouldn't just grab the contacts, but you probably should request permission first.
Pulling that all together you get:
// make sure user hadn't previously denied access
let status = ABAddressBookGetAuthorizationStatus()
if status == .Denied || status == .Restricted {
// user previously denied, so tell them to fix that in settings
return
}
// open it
var error: Unmanaged<CFError>?
guard let addressBook: ABAddressBook? = ABAddressBookCreateWithOptions(nil, &error)?.takeRetainedValue() else {
print(error?.takeRetainedValue())
return
}
// request permission to use it
ABAddressBookRequestAccessWithCompletion(addressBook) { granted, error in
if !granted {
// warn the user that because they just denied permission, this functionality won't work
// also let them know that they have to fix this in settings
return
}
if let people = ABAddressBookCopyArrayOfAllPeople(addressBook)?.takeRetainedValue() as? NSArray {
// now do something with the array of people
}
}