IOS Swift AddressBook Contacts - swift2

I have A weird problem, I am trying to get all contacts from IOS addressbook API, while I am trying to get all the values of properties (First name, Last name, Emails and phone numbers of each contact I am g etting "fatal error: unexpectedly found nil while unwrapping an Optional value"), just when i run the the command on my device, but when I am running the command on the xcode simulator everything works fine?
here is my sample code:
func getContactNames() {
let people = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() as NSArray
for person in people {
let firstName = ABRecordCopyValue(person,
kABPersonFirstNameProperty).takeRetainedValue() as! String
let lastName = ABRecordCopyValue(person,
kABPersonLastNameProperty).takeRetainedValue() as! String
let email: ABMultiValueRef = ABRecordCopyValue(person,kABPersonEmailProperty).takeRetainedValue()
println("First name = \(firstName)")
println("Last name = \(lastName)")
println("Email = \(email)")
}
}

You may find the problem is with your use of as! String. If any entries in your address book do not have a First Name or a Last Name, then you are attempting to retain a nil, hence the error.
My solution to this problem has been to use code like this:
public func stringPropertyValue(record: ABRecord, id:ABPropertyID) -> String! {
var result: String! = nil
if let v = ABRecordCopyValue(record, id) {
result = v.takeRetainedValue() as! String
}
return result
}
The you can set up variables like this:
let firstName = stringPropertyValue(person, id:kABPersonFirstNameProperty)
let lastName = stringPropertyValue(person, id:kABPersonLastNameProperty)
The resulting variables are implied optionals, but they won't cause the unwrapping errors. The above function can be easily reworked into a class structure along the lines of the new Contacts framework in iOS 9 / Xcode 7

Related

Swift Firestore: Getting all data from firestore and inputing data into Table View

The goal here is to retrieve all the documents of a Firestore Database, then putting the data of those retrieved documents into a Table View. I'm using Xcode and Firebase.
For example, if I had a Firestore database with this data and documents:
Document 1
Name: Bob
Email: bob#gmail.com
Phone: 408-111-1234
Document 2
Name: Joe
Email: joe#yahoo.com
Phone: 408-338-4321
I would like to have it all in a table view, maybe something like this:
Bob - bob#gmail.com - 408-111-1234
Joe - joe#yahoo.com - 408-338-4321
So far, I have this set up:
TableViewController:
class PeopleViewController: UITableViewController {
#IBOutlet var table: UITableView!
var peopleArray = [] as [Array<Any>]
private var document: [DocumentSnapshot] = []
override func viewDidLoad() {
super.viewDidLoad()
table.tableFooterView = UIView(frame: CGRect.zero)
self.table.delegate = self
self.table.dataSource = self
loadData()
}
And the loadData() func:
func loadData() {
FirebaseFirestore.root.collection("users").getDocuments { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let email = data["email"] as? String ?? ""
let phone = data["phone"] as? String ?? ""
let newPerson = ["name": name, "email": email, "phone": phone]
self.peopleArray.append(newPerson)
print(self.peopleArray)
}
self.table.reloadData()
}
}
}
}
The first thing wrong here is that there is this error:
Cannot convert value of type '[String : String]' to expected argument type '[Any]'
When I try to append a newPerson in the PeopleArray. I have searched this up, but none of the answers matched this specific error. The second thing is that I have no clue how to even start adding the data from the PeopleArray into the Table View.
I would love some help for both the error and the Table View. Anything is appreciated!
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
After doing what Vadian recommended, I am stuck with a new error.
attempt to insert row 2 into section 0, but there are only 1 rows in section 0 after the update
This is the updated func loadData:
func loadData() {
FirebaseFirestore.root.collection("users").getDocuments { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
self.table.beginUpdates()
let data = document.data()
let name = data["name"] as? String ?? ""
let zip = data["zip"] as? String ?? ""
let sport = data["sport"] as? String ?? ""
let newPerson = Person(name: name, zip: zip, sport: sport)
self.people.append(newPerson)
let newIndexPath = IndexPath(row: self.people.count, section: 0)
self.table.insertRows(at: [newIndexPath], with: .automatic)
self.table.endUpdates()
}
DispatchQueue.main.async {
self.table.reloadData()
}
}
}
}
}
I've read about this on another question about how the rows are off by 1, but even when I let indexPath equal to people.count+1 or people.count+2, the error persists. What am I doing wrong?
First of all the syntax
var peopleArray = [] as [Array<Any>]
is bad practice. If you want to declare an empty array write
var peopleArray : [Array<Any>] = []
or
var peopleArray = [Array<Any>]()
Second of all your array is a nested array which is not intended. You mean
var peopleArray = Array<Any>()
which is more descriptive using the alternative syntax
var peopleArray = [Any]()
Third of all as your data is clearly a more specific type than [Any] declare the array
var peopleArray = [[String:String]]()
This fixes the error.
Fourth of all the recommended data source is a custom struct
struct Person {
let name, email, phone: String
}
In this case declare the array (we know that people is plural so the suffix array is redundant).
var people = [Person]()
And populate it (you might reload the table view on the main thread)
for document in snapshot.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let email = data["email"] as? String ?? ""
let phone = data["phone"] as? String ?? ""
let newPerson = Person(name: name, email: email, phone: phone)
self.people.append(newPerson)
}
DispatchQueue.main.async {
self.table.reloadData()
}
I researched a lot and fixed the edited part in the question I posted. A simple -1 in the insert rows fixed the problem.
let newIndexPath = IndexPath(row: self.people.count-1, section: 0)
Vadian also helped me with the original problem, which was using
var peopleArray = [[String:String]]()
instead of the other incorrect format. I was then able to find how to insert rows by just simply using the table.insertrows function.
(I'm just posting this for a simplified answer for anyone else experiencing problems).

Getting data like name or image of the contact out of ABRecord using ABPeoplePickerNavigationControllerDelegate

I want to give the users of my app the possibility to get their contacts out of their address book.
I can present the ABPeoplePickerNavigationController and the user can select a person and then the ABPPNC dismiss again.
Thats the code I used to present it:
let peopleController = ABPeoplePickerNavigationController()
peopleController.delegate = self
peopleController.predicateForSelectionOfPerson = NSPredicate(value: true)
self.presentViewController(peopleController, animated: true, completion: nil)
And the peoplePickerNavigationController didSelectPerson function is called but I don't get how to get Information out of the person of type ABRecord. At https://developer.apple.com/library/ios/documentation/AddressBookUI/Reference/ABPeoplePickerNavigationController_Class/index.html#//apple_ref/doc/constant_group/Address_Book_Properties Apple says there are some Address Book Properties like: ABPersonNamePrefixProperty or ABPersonMiddleNameProperty but I don't get how to work with it.
So basically I want to do something like this:
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecord!) {
// What I want to do:
let name = person.lastName
let firstName = person.firstName
let image = person.image
}
After a long search I found an answer that worked for me:
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecord!) {
var firstName: String?
if ABRecordCopyValue(person, kABPersonFirstNameProperty) != nil{
firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty).takeRetainedValue() as? String
}
var lastName: String?
if ABRecordCopyValue(person, kABPersonLastNameProperty){
lastName = ABRecordCopyValue(person, kABPersonLastNameProperty).takeRetainedValue() as? String
}
var pImage: UIImage?
if person != nil && ABPersonHasImageData(person){
pImage = UIImage(data: ABPersonCopyImageData(person).takeRetainedValue())
}
}
it would be easy if you use "ABAddressBookRef" and get all contacts . then you can show it to user as you want and will help in future too.

Unexpectedly found nil [duplicate]

This question already has answers here:
Swift - Checking unmanaged address book single value property for nil
(3 answers)
Closed 7 years ago.
I'm working with contacts in my app, user can choose a contact name…
but if first name or last name being empty, I will get this common error:
fatal error: unexpectedly found nil while unwrapping an Optional value
I know my question maybe duplicate, but I have read some articles but I couldn't find out how to solve mine.
Here is my code:
let firstName: ABMultiValueRef? =
ABRecordCopyValue(person,
kABPersonFirstNameProperty).takeRetainedValue() as ABMultiValueRef
let lastName: ABMultiValueRef? =
ABRecordCopyValue(person,
kABPersonLastNameProperty).takeRetainedValue() as ABMultiValueRef
titleField.text = ("\(firstName) \(lastName)")
I want to fill textfield anyway.
EDIT:
I have found this solution from related question:
var name:String = ""
if let first = ABRecordCopyValue(person, kABPersonFirstNameProperty)?.takeRetainedValue() as? String {
name += first
}
if let last = ABRecordCopyValue(person, kABPersonLastNameProperty)?.takeRetainedValue() as? String {
name += last
}
titleField.text = name
}
Sprinkle some ?'s around to account for the nil, e.g.,
let firstName: ABMultiValueRef? =
ABRecordCopyValue(person,
kABPersonFirstNameProperty)?.takeRetainedValue() as? ABMultiValueRef
You should also be prepared for the possibility than firstName may be nil, and, e.g., use optional binding with if let firstName: ABMultiValueRef = …, and put any code relying on firstName in the then-branch of the if. The whole thing would look something like:
if let firstName: ABMultiValueRef = ABRecordCopyValue(person,
kABPersonFirstNameProperty)?.takeRetainedValue() as? ABMultiValueRef,
lastName: ABMultiValueRef = ABRecordCopyValue(person,
kABPersonLastNameProperty)?.takeRetainedValue() as? ABMultiValueRef {
titleField.text = "\(firstName) \(lastName)"
} else {
titleField.text = "" // <- handle the failure case?
}
Try this,
if((let first = ABRecordCopyValue(person, kABPersonFirstNameProperty)?.takeRetainedValue() as? String) && (let last = ABRecordCopyValue(person, kABPersonLastNameProperty)?.takeRetainedValue() as? String)) {
titleField.text = ("\(firstName) \(lastName)")
}

Get Contact Image Data using ABPersonCopyImageData

I'm trying to get the contact details out of the address book on the Mac. I can get the first name and last name fields etc, but I'm struggling with the syntax for ABPersonCopyImageData.
Now according to the documentation ABPersonCopyImageData takes a single parameter of type ABPerson.
Here is my code:
import AddressBook
let thisPerson : ABPerson
let addressBook = ABAddressBook.sharedAddressBook()
rec = addressBook.recordForUniqueId("0005A360-327F-4E12-BBB9-24A842497E12:ABPerson")
let firstName = rec.valueForProperty(kABFirstNameProperty) as! String
let lastName = rec.valueForProperty(kABLastNameProperty) as! String
println("\(firstName) \(lastName)")
let contactImage = ABPersonCopyImageData(thisPerson)
The last line stops the compiler with an error: Cannot invoke 'ABPersonCopyImageData' with an argument list of type (ABPerson). As far as I can tell thisPerson is of type ABPerson. What is going wrong?
I found out how to do this in ElCapitan:
import Contacts
func getContactImage(name:String) -> NSImage?
{
let store = CNContactStore()
do
{
let contacts = try store.unifiedContactsMatchingPredicate(CNContact.predicateForContactsMatchingName(name), keysToFetch:[CNContactImageDataKey])
if contacts.count > 0
{
if let image = contacts[0].imageData
{
return NSImage.init(data: image)
}
}
}
catch
{
}
return nil
}

How do I view the actual string variable of a variable/constant?

Environment: Debugging Parse return object within Xcode 6.1
I can see the text within the object structure but can't adequately view its assigned variable.
Here's the code:
func retrieveAllMediaFromParse() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let myData:PFQuery = PFQuery(className: kParseMedia)
myData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]!, error:NSError!)->Void in
if !(error != nil){
// for object in objects {
let myObject = objects[0] as PFObject
let format = myObject.objectForKey("format") as String
let access = myObject.objectForKey("access") as String
let media = myObject.objectForKey("media") as String
let owner = myObject.objectForKey("owner") as String
let text = myObject.objectForKey("text") as String
let thumbNail = myObject.objectForKey("thumbnail") as NSString
}
}
});
}
let text = myObject.objectForKey("text") as String
When I take the 'po' of the command string I get the correct interpretation:
However, when I do the same for the assigned variable (constant), I get the following:
How do I view the variable/constant to display the actual string?
When program is paused in the debugger, you can find the values of PFObject fields by going to the estimatedData line in the debugger.

Resources