Swift OSX Adressbuch Daten auslesen

    Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

    • Swift OSX Adressbuch Daten auslesen

      Hallo,

      ich versuche gerade alle Datensätze aus meinem Adressbuch auszulesen. Aber irgendwie erhalte ich nicht alle Daten eines Datensatzes.

      Quellcode

      1. ...
      2. let keysToFetch = [CNContactOrganizationNameKey, CNContactGivenNameKey, CNContactFamilyNameKey] as [CNKeyDescriptor]
      3. let storeCall = CNContactStore()
      4. do {
      5. let contacts = try storeCall.unifiedContacts(matching: predicate, keysToFetch: keysToFetch)
      6. print("Fetched contacts: \(contacts)")
      7. } catch {
      8. print("Failed to fetch contact, error: \(error)")
      9. }
      Obiger Code liefert mir nachfolgendes Ergebnis (wurde zugunsten der Übersicht gekürzt):

      Fetched contacts: [
      <CNContact: 0x...: identifier=...:ABPerson, givenName=John, familyName=Do, organizationName=(not fetched), phoneNumbers=(not fetched), ...>,
      <CNContact: 0x...: identifier=...:ABPerson, givenName=Jane, familyName=Do, organizationName=(not fetched), phoneNumbers=(not fetched), ...>]

      Folgende Daten eines Datensatzes benötige ich unbedingt:
      - Firmennamen (wird nicht ausgeliefert)
      - Vornamen (wird geliefert)
      - Nachnamen (wird geliefert)
      - usw.

      Wie muss ich Zeile 2 keysToFetch = [.....] as ... ändern, damit ich nicht alle Attribute einzeln eintragen muss und warum erhalte ich den Firmennamen trotz expliziter Angabe nicht?

      Wäre prima wenn mir jemand mitteilen kann, was ich da außer Acht lasse. :/
    • Hm. Dein (gekürzter) Quelltext sieht soweit OK aus. Damit sollte das Abrufen des Firmennamens eigentlich funktionieren, der Key wurde ja explizit angegeben.

      Bei den KeyDescriptors kannst du die Klassenmethode descriptorForRequiredKeys()der CNContactVCardSerialization Klasse als Abkürzung missbrauchen:

      Quellcode

      1. let keysToFetch = [CNContactVCardSerialization.descriptorForRequiredKeys()]

      Für Iterationen über viele Kontakte hinweg ist es aber aus Speicher- und Performance-Gründen nicht angebracht, das Contacts-Framework jedesmal alle Felder liefern zu lassen.
    • plasmatron schrieb:

      Hm. Dein (gekürzter) Quelltext sieht soweit OK aus. Damit sollte das Abrufen des Firmennamens eigentlich funktionieren, der Key wurde ja explizit angegeben.

      Bei den KeyDescriptors kannst du die Klassenmethode descriptorForRequiredKeys()der CNContactVCardSerialization Klasse als Abkürzung missbrauchen:
      ...


      Für Iterationen über viele Kontakte hinweg ist es aber aus Speicher- und Performance-Gründen nicht angebracht, das Contacts-Framework jedesmal alle Felder liefern zu lassen.
      Erst einmal vielen Dank für Deinen Lösungsansatz. :thumbup:

      Also ich konnte den Fehler bisher nicht lokalisieren, aber Dein Hinweis liefert mir nun auch den Firmennamen; also soweit alles im grünen Bereich.

      Was Deinen Hinweis angeht - ich lerne gerne dazu. Welche weitere Vorgehensweise ermöglicht es später nicht mit einer Speicher und Performance-Problematik konfrontiert zu werden?
    • Verwende enumerateContacts(with:usingBlock:) von CNContactStore und verarbeite dann den jeweiligen CNContact.

      Apple Docs schrieb:

      This method can fetch all contacts without keeping all of them at once in memory, which is expensive.

      Du musst hier ein CNFetchRequest übergeben, bei dem du dann einfach das Predicate weglässt. So werden Account-übergreifend alle Kontakte herausgesucht.

      JavaScript-Quellcode

      1. do {
      2. let keysToFetch = [...] as [CNKeyDescriptor]
      3. let store = CNContactStore()
      4. let cnFetchRequest = CNContactFetchRequest(keysToFetch: keysToFetch)
      5. cnFetchRequest.unifyResults = true
      6. cnFetchRequest.mutableObjects = false
      7. cnFetchRequest.sortOrder = .userDefault
      8. cnFetchRequest.predicate = nil // -> fetch all contacts
      9. try store.enumerateContacts(with: cnFetchRequest, usingBlock: { (cnContact, stopEnumerating) in
      10. // do something with cnContact
      11. })
      12. } catch {
      13. print("An error occured:", error)
      14. }
      Alles anzeigen
    • plasmatron schrieb:

      Quellcode

      1. do {
      2. let keysToFetch = [CNContactVCardSerialization.descriptorForRequiredKeys()] as [CNKeyDescriptor]
      3. let store = CNContactStore()
      4. let cnFetchRequest = CNContactFetchRequest(keysToFetch: keysToFetch)
      5. cnFetchRequest.unifyResults = true
      6. cnFetchRequest.mutableObjects = false
      7. cnFetchRequest.sortOrder = .userDefault
      8. cnFetchRequest.predicate = nil // -> fetch all contacts
      9. try store.enumerateContacts(with: cnFetchRequest, usingBlock: { (cnContact, stopEnumerating) in
      10. print("Fetched contacts: \(cnContact)")
      11. })
      12. } catch {
      13. print("An error occured:", error)
      14. }
      Alles anzeigen

      Hmmm, obiger Quellcode funktioniert zwar - liefert jedoch nur folgendes Ergebnis:

      Fetched contacts:
      <CNContact: ...: identifier=...:ABPerson, givenName=John, familyName=Do, organizationName=, phoneNumbers=(not fetched), ...>
      <CNContact: ...: identifier=...:ABPerson, givenName=Jane, familyName=Do, organizationName=, phoneNumbers=(not fetched), ...>

      Im Grunde entspricht dies meinem Ursprungsproblem. Ich habe das Gefühl ich drehe mich im Kreis. ;(

      Kann es sein, dass es an meinem Adressbuch liegt? Falls dies so wäre, wie kann ich dies beheben? Wo muss ich den Namen des zu verwenden Adressbuches/Verzeichnisses eintragen. Beispiel - ich verwende ein lokales und ein zentrales (dies liegt auf einem Server und ist im Adressbuch eingetragen) Verzeichnis.
      Mit obigem Quellcode lese ich grundsätzlich das lokale Verzeichnis des Adressbuches aus. Mir wäre es jedoch wichtig, dass ich selbst vorgebe welches Verzeichnis ausgelesen werden soll. Vielleicht liegt es ja daran?

      Danke mal vorab.
    • Bei meinem ersten Code-Beispiel werden **alle** Kontakte **aller** Accounts abgerufen (bzw. sollten sie das), da dem fetchRequest kein Predicate übergeben wurde. In deinem Fall sollten also nicht nur die Kontakte des lokalen Adressbuchs zurückgeliefert werden, sondern auch die des CardDAV-Accounts. Eventuell hast du ja in beiden Accounts einige Kontakte doppelt, die dann durch das Unifying zusammengefasst werden. Dass bei dir solche Effekte trotz explizitem Fetchen der korrekten Keys auftreten, ist etwas mysteriös. Ändert sich etwas, wenn du die Code-Beispiele in einem Playground ausprobierst?

      Wie auch immer: Um gezielt nur Kontakte einzelner Container oder Adressbuch-Gruppen abzurufen, muss man sich der verschiedenen Methoden und den entsprechenden Predicates bedienen, die etwas verstreut in den jeweiligen CN-Klassen angeboten werden. Die verschiedenen Adressbuch-Accounts werden durch die Klasse CNContainer repräsentiert. Über deren Property type bekommt man den Account-Typ heraus (lokal, Exchange-Server, CardDAV). CNContactStore liefert dir z.B. eine Liste aller Container, während CNContact ein Predicate für alle Kontakte eines Containers feilbietet.

      Das erweiterte Beispiel illustriert das Vorgehen:

      JavaScript-Quellcode

      1. do {
      2. let store = CNContactStore()
      3. // Lokales Adressbuch heraussuchen
      4. if let localContainer = try store.containers(matching: nil).filter({ $0.type == .local }).first {
      5. // Predicate für alle Kontakte dieses Containers geben lassen
      6. let localContactsOnlyPredicate = CNContact.predicateForContactsInContainer(withIdentifier: localContainer.identifier)
      7. let keysToFetch = [CNContactVCardSerialization.descriptorForRequiredKeys()] as [CNKeyDescriptor]
      8. let cnFetchRequest = CNContactFetchRequest(keysToFetch: keysToFetch)
      9. cnFetchRequest.unifyResults = true
      10. cnFetchRequest.mutableObjects = false
      11. cnFetchRequest.sortOrder = .userDefault
      12. cnFetchRequest.predicate = localContactsOnlyPredicate // <- Predicate zuweisen
      13. try store.enumerateContacts(with: cnFetchRequest, usingBlock: { (cnContact, stopEnumerating) in
      14. print("Fetched contact:", cnContact)
      15. // Können Telefonnummern abgerufen werden?
      16. print(" -- Allowed to fetch phone numbers?", cnContact.isKeyAvailable(CNContactPhoneNumbersKey))
      17. // Wenn nicht, sollte folgende Zeile eine CNContactPropertyNotFetchedException provozieren:
      18. print(" -- phone numbers:", cnContact.phoneNumbers)
      19. })
      20. } else {
      21. print("Kein lokales Adressbuch gefunden.")
      22. }
      23. } catch {
      24. print("An error occured:", error)
      25. }
      Alles anzeigen
    • Vielen Dank für Deine Unterstützung. Prima erklärt, weitere Informationen konnte ich nun ebenfalls finden. :thumbsup:

      Im Playground habe ich den Code ebenfalls ausprobiert, bedauerlicherweise mit den selben Resultaten. Nun habe ich mal alle Berechtigungen im Filesystem prüfen und berichtigen lassen und siehe da es funktioniert tadellos. ?( Warum dies notwendig war kann ich gerade nicht erklären, notwendig war es - dafür sprechen nun die Resultate.

      Nun hätte ich noch eine weitere Frage? Hast Du Infomaterial (Links, Tutorials oder gar ein Codebeispiel) wie man ein PlugIn für das Adressbuch erzeugt. Hintergrund ist, dass ich ein weiteres Icon hinzufügen möchte, welches mir die ausgewählte Telefonnummer des gewählten Kontaktes über mein VoIp-Telefon wählt.
    • Das Stichwort für Adressbuch-Plugins heisst ABActionDelegate und ist ein Protocol, dass sich im ABAddressBook Framework versteckt.

      Das ABAddressBook Framework ist allerdings schon seit längerem deprecated und mit Xcode 11.4 unter Catalina bekommt man nun auch eine deutliche Warnung, wenn man es verwendet. Es würde mich nicht wundern, wenn Apple mit 10.16 ein PrivateFramework daraus macht (ARM-Mac, ick hör dir trapsen!). Damit wäre dann die stressfreie Pflege eines nativen Plugins perspektivisch nur auf älteren macOS-Versionen möglich.

      Offenbar kann man aber Adressbuch-Plugins auch über schnöde AppleScripts realisieren. Hier hat jemand praktischerweise ein paar Skripte veröffentlicht, die IP-Telefone von der Kontakte-App aus Anrufe initiieren lassen können.
    • plasmatron schrieb:

      Das Stichwort für Adressbuch-Plugins heisst ABActionDelegate und ist ein Protocol, dass sich im ABAddressBook Framework versteckt.

      Das ABAddressBook Framework ist allerdings schon seit längerem deprecated und mit Xcode 11.4 unter Catalina bekommt man nun auch eine deutliche Warnung, wenn man es verwendet. Es würde mich nicht wundern, wenn Apple mit 10.16 ein PrivateFramework daraus macht (ARM-Mac, ick hör dir trapsen!). Damit wäre dann die stressfreie Pflege eines nativen Plugins perspektivisch nur auf älteren macOS-Versionen möglich.

      Offenbar kann man aber Adressbuch-Plugins auch über schnöde AppleScripts realisieren. Hier hat jemand praktischerweise ein paar Skripte veröffentlicht, die IP-Telefone von der Kontakte-App aus Anrufe initiieren lassen können.
      Nun ja, da ich die PlugIns nicht zum laufen bekam, habe ich ein wenig recherchiert und bin auf folgende Aussage gestossen:
      Addressbook Applescript-Plugins no longer working in Mojave

      Fazit:
      Wenn obige Informationen richtig sind und weder die PlugIns noch das Framework für die Kontakte Applikation weiterhin unterstützt werden, ist es wohl sinnvoller diesen Lösungsansatz nicht weiter zu verfolgen. Oder gibt es hier noch Alternativen?
    • Ah, ok. Offenbar ist die Schnittstelle von Apple mit 10.14 beerdigt worden. Auf deiner verlinkten SO-Seite steht weiter unten:

      StackOverflow schrieb:

      I heard back from a Developer Technical Support Engineer at Apple and he said:

      "I’ve asked the engineering team about this and indeed, we have removed support for both compiled and AppleScript-based AddressBook plugins in Mojave 10.14."


      His suggestion was to file an enhancement request via their Bug Reporter. Sorry for the bad news.

      Bleibt noch die Möglichkeit, das ganze als System-Service zu implementieren, vielleicht via Automator oder bereitgestellt von deiner App (Apple-Doku). Das hätte dann den Vorteil, dass es aus jeder App heraus funktionieren würde.
    • plasmatron schrieb:

      Ah, ok. Offenbar ist die Schnittstelle von Apple mit 10.14 beerdigt worden.
      ...
      Bleibt noch die Möglichkeit, das ganze als System-Service zu implementieren, vielleicht via Automator oder bereitgestellt von deiner App (Apple-Doku). Das hätte dann den Vorteil, dass es aus jeder App heraus funktionieren würde.
      Das mit dem System-Service hört sich gut an. Werde mich da mal einlesen.

      @plasmatron: Vielen Dank.