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
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).
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.
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)")
}
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
}
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.