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
}
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).
In Swift 2 the following code was working:
let request = NSFetchRequest(entityName: String)
but in Swift 3 it gives error:
Generic parameter "ResultType" could not be inferred
because NSFetchRequest is now a generic type. In their documents they wrote this:
let request: NSFetchRequest<Animal> = Animal.fetchRequest
so if my result class is for example Level how should I request correctly?
Because this not working:
let request: NSFetchRequest<Level> = Level.fetchRequest
let request: NSFetchRequest<NSFetchRequestResult> = Level.fetchRequest()
or
let request: NSFetchRequest<Level> = Level.fetchRequest()
depending which version you want.
You have to specify the generic type because otherwise the method call is ambiguous.
The first version is defined for NSManagedObject, the second version is generated automatically for every object using an extension, e.g:
extension Level {
#nonobjc class func fetchRequest() -> NSFetchRequest<Level> {
return NSFetchRequest<Level>(entityName: "Level");
}
#NSManaged var timeStamp: NSDate?
}
The whole point is to remove the usage of String constants.
I think i got it working by doing this:
let request:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Level")
at least it saves and loads data from DataBase.
But it feels like it is not a proper solution, but it works for now.
The simplest structure I found that works in 3.0 is as follows:
let request = NSFetchRequest<Country>(entityName: "Country")
where the data entity Type is Country.
When trying to create a Core Data BatchDeleteRequest, however, I found that this definition does not work and it seems that you'll need to go with the form:
let request: NSFetchRequest<NSFetchRequestResult> = Country.fetchRequest()
even though the ManagedObject and FetchRequestResult formats are supposed to be equivalent.
Here are some generic CoreData methods that might answer your question:
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)")
}
}
Assuming that there is a NSManagedObject setup for Contact like this:
class Contact: NSManagedObject
{
#NSManaged var contactNo: Int
#NSManaged var contactName: String
}
These methods can be used in the following way:
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()
This is the simplest way to migrate to Swift 3.0, just add <Country>
(tested and worked)
let request = NSFetchRequest<Country>(entityName: "Country")
Swift 3.0 This should work.
let request: NSFetchRequest<NSFetchRequestResult> = NSManagedObject.fetchRequest()
request.entity = entityDescription(context)
request.predicate = predicate
I also had "ResultType" could not be inferred errors. They cleared once I rebuilt the data model setting each entity's Codegen to "Class Definition". I did a brief writeup with step by step instructions here:
Looking for a clear tutorial on the revised NSPersistentContainer in Xcode 8 with Swift 3
By "rebuilt" I mean that I created a new model file with new entries and attributes. A little tedious, but it worked!
What worked best for me so far was:
let request = Level.fetchRequest() as! NSFetchRequest<Level>
I had the same issue and I solved it with the following steps:
Select your xcdatamodeld file and go to the Data Model Inspector
Select your first Entity and go to Section class
Make sure that Codegen "Class Definition" is selected.
Remove all your generated Entity files. You don't need them anymore.
After doing that I had to remove/rewrite all occurences of fetchRequest as XCode seem to somehow mix up with the codegenerated version.
HTH
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func loadItemsCategory() {
let request: NSFetchRequest<Category> = Category.fetchRequest()
do {
categoryArray = try context.fetch(request)
} catch {
print(error)
}
tableView.reloadData()
}
I'm using this class that was written in Swift 1.2 and now I want to use it with Swift 2.0.
I get an error: Ambiguous use of 'subscript' # let artist = result["name"] as! String
} else if let jsonArtists = jsonResult["artists"] as? NSDictionary {
if let results:NSArray = jsonArtists["items"] as? NSArray {
dispatch_async(dispatch_get_main_queue(), {
self.searching = false
var suggestionResults: [spotifySearchResult] = []
for result in results {
let artist = result["name"] as! String
var sugresult = spotifySearchResult()
sugresult.artist = artist
if !suggestionResults.contains(sugresult) {
suggestionResults.append(sugresult)
}
}
handler(suggestionResults)
})
}
}
}
I tried different fixes such as let artist = (result["name"] as! String) or let artist = (result["name"] as! String) as! String
But nothing worked.
I know that the question was already post 4 times but, I can't find anyone explaining how to fix it in every case, only case by case.
Can someone explain me how to investigate to fix it? Not just only a fix for my case. I would prefer fix it by myself with your hints!
BTW what does mean subscript? Is subscript the thing between quotation mark? IMHO the error message is a bit vague...
EDIT:
class spotifySearchResult : NSObject {
var artist=""
var track=""
var duration=0
var spotifyURL = NSURL()
var spotifyURI = NSURL()
override func isEqual(theObject: AnyObject?) -> Bool {
if let myObject = theObject as? spotifySearchResult {
return (myObject.artist.uppercaseString == self.artist.uppercaseString && myObject.track.uppercaseString == self.track.uppercaseString)
}
return false
}
}
Subscription means to use the shorter syntax item["key"] for item.objectForKey["key"]
results seems to be an array of dictionaries so I suggest to cast down to a more specific type
if let results = jsonArtists["items"] as? [[String:AnyObject]] {
or even, if all values are guaranteed to be strings
if let results = jsonArtists["items"] as? [[String:String]] {
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.
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