Skip to content

Instantly share code, notes, and snippets.

@marcomasser
Created May 9, 2022 18:56
Show Gist options
  • Save marcomasser/d9752544babd3d0a0179ee3c8ac8b8d6 to your computer and use it in GitHub Desktop.
Save marcomasser/d9752544babd3d0a0179ee3c8ac8b8d6 to your computer and use it in GitHub Desktop.
#!/usr/bin/env swift
/*
Contacts Location Action for LaunchBar
by Christian Bender (@ptujec)
2022-05-05
Copyright see: https://github.com/Ptujec/LaunchBar/blob/master/LICENSE
*/
import AppKit
import Contacts
import CoreLocation
import Foundation
func main() {
let arguments = Array(CommandLine.arguments.dropFirst())
// let arguments = [String]()
var argument = ""
if arguments.count == 0 {
argument = "My Location" // Should be current location
// https://www.zerotoappstore.com/how-to-get-current-location-in-swift.html
} else {
argument = arguments[0]
}
var resultJSON = [[String: Any]]()
let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPostalAddressesKey, CNContactOrganizationNameKey] as [CNKeyDescriptor]
var contacts = [CNContact]()
let request = CNContactFetchRequest(keysToFetch: keysToFetch)
let contactStore = CNContactStore()
do {
// // Only Contacts that fit argument
// let predicate = CNContact.predicateForContacts(matchingName: arguments[0])
// // let predicate = CNContact.predicateForContacts(matchingName: "Schneider") // change later to get all contacts and use argument for Address
// let contacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch)
// print(contacts)
// if contacts.count == 0 {
// exit(0)
// }
// All Contacts
try contactStore.enumerateContacts(with: request) {
contact, _ in
// Array containing all unified contacts from everywhere
contacts.append(contact)
}
var contactsBeingProcessed: Set<CNContact> = []
for contact in contacts {
// print(contact)
var contactName = ""
func didProcessContact(distance: String? = nil) {
DispatchQueue.main.async {
contactsBeingProcessed.remove(contact)
print("Did process contact: \(contactName), \(contactsBeingProcessed.count) more to go…")
let contactJSON = [
"title": contactName,
"subtitle": contactAddressString,
"badge": "Distance to \(argument): \(distance ?? "Unknown")",
"icon": "contactTemplate",
]
resultJSON.append(contactJSON)
if contactsBeingProcessed.isEmpty {
func dictSort(dict1: [String: Any], dict2: [String: Any]) -> Bool {
guard let i0 = dict1["title"] as? String, // change title to badge when there is an actuall value
let i1 = dict2["title"] as? String else { return false }
return i0 < i1
}
let sortedArray = resultJSON.sorted { dictSort(dict1: $0, dict2: $1) }
do {
// Serialize to JSON
let jsonData = try JSONSerialization.data(withJSONObject: sortedArray)
// Convert to a string and print
if let JSONString = String(data: jsonData, encoding: String.Encoding.utf8) {
print(JSONString)
}
exit(0)
} catch {
NSLog("Error converting to JSON: \(error)")
exit(1)
}
}
}
}
// TODO: Ersetzen mit PersonNameComponentsFormatter oder CNContactFormatter ? https://swiftwombat.com/how-to-use-personnamecomponentsformatter-in-swift/
// https://developer.apple.com/documentation/foundation/personnamecomponentsformatter
if contact.familyName.isEmpty && contact.givenName.isEmpty {
contactName = contact.organizationName
} else if !contact.familyName.isEmpty && !contact.givenName.isEmpty {
contactName = "\(contact.givenName) \(contact.familyName)"
} else if !contact.familyName.isEmpty && contact.givenName.isEmpty {
contactName = contact.familyName
} else if contact.familyName.isEmpty && !contact.givenName.isEmpty {
contactName = contact.givenName
}
let contactAddressCount = contact.postalAddresses.count
// print(contactAddressCount)
var contactAddressString = ""
if contactAddressCount > 0 { // wenn kein FamilyName dann Firma
let contactAddress = contact.postalAddresses[0]
contactAddressString = CNPostalAddressFormatter
.string(from: contactAddress.value, style: .mailingAddress)
.replacingOccurrences(of: "\n", with: ", ")
// Insert Code for Distance form Contact Address to Address enter by argument[0] or default (current address)
// --------------------------------------
func getCoordinates(from address: String, completion: @escaping ((CLLocationCoordinate2D?) -> Void)) {
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(address) { placemarks, _ in
DispatchQueue.main.async {
completion(placemarks?.first?.location?.coordinate)
}
}
}
var coo1: CLLocation = CLLocation(latitude: 0.0, longitude: 0.0)
contactsBeingProcessed.insert(contact)
getCoordinates(from: contactAddressString) { location in
guard let location = location else {
didProcessContact()
return
}
coo1 = CLLocation(latitude: location.latitude, longitude: location.longitude)
var coo2 = CLLocation(latitude: 0, longitude: 0)
getCoordinates(from: argument) { location in
guard let location = location else {
didProcessContact()
return
}
coo2 = CLLocation(latitude: location.latitude, longitude: location.longitude)
print(coo2)
let distanceInMeters = coo1.distance(from: coo2) // result is in meters
let distanceInKm = distanceInMeters / 1000
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 1
formatter.minimumFractionDigits = 0
let distanceNice = formatter.string(for: distanceInKm)
print("Distanz: \(distanceNice!) km")
didProcessContact(distance: distanceNice)
}
}
}
}
} catch {
NSLog("Failed to fetch contact, error: \(error)")
}
}
RunLoop.main.perform {
main()
}
RunLoop.main.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment