11

how to get ekevent EKparticipant email?

EKParticipant class does not have such a attribute.

Is it possible to render the native ios participants controller to show the list of participants?

Evan Lee
  • 738
  • 15
  • 36

6 Answers6

10

I had this same question and when I was at WWDC this year, I asked several Apple engineers and they had no clue. I asked a guy I met in line and he had the answer:

event.organizer.URL.resourceSpecifier

This works for any EKParticipant. I was cautioned NOT to use the description field because that may change at any time.

Hope this helps!

Moebius
  • 700
  • 7
  • 25
  • It should be `participant.URL.resourceSpecifier`, but not just `organizer` property in EKEvent, such as `attendees` property – likid1412 Feb 09 '17 at 07:10
5

None of the above solutions are reliable:

  1. URL may be something like /xyzxyzxyzxyz.../principal and obviously that's not an email.
  2. EKParticipant:description may change and not include email anymore.
  3. You could send emailAddress selector to the instance but that's undocumented, may change in the future and in the meantime might get your app disapproved.

So at the end what you need to do is use EKPrincipal:ABRecordWithAddressBook and then extract email from there. Like this:

NSString *email = nil;
ABAddressBookRef book = ABAddressBookCreateWithOptions(nil, nil);
ABRecordRef record = [self.appleParticipant ABRecordWithAddressBook:book];
if (record) {
    ABMultiValueRef value = ABRecordCopyValue(record, kABPersonEmailProperty);
    if (value
        && ABMultiValueGetCount(value) > 0) {
        email = (__bridge id)ABMultiValueCopyValueAtIndex(value, 0);
    }
}

Note that calling ABAddressBookCreateWithOptions is expensive so you might want to do that only once per session.

If you can't access the record, then fall back on URL.resourceSpecifier.

ierceg
  • 418
  • 1
  • 8
  • 11
  • Hi! I tried using your code, but the record variable is always nil in my case. The URL is as you mentioned (ending with principal). I found in documentation, that if participant is not found, then nil is returned. But I checked my address book and calendar and it exists (so it should be found). Any idea why ABRecordWithAddressBook: would return nil? – haluzak Aug 16 '15 at 12:16
  • @haluzak No idea, sorry. This API is frankly awful. I think we finally settled on sending undocumented `emailAddress` selector to the instance. – ierceg Aug 20 '15 at 11:24
  • I figured it out in the end, I didn't had the access to address book, so you have to request the access before you use the code you provided. Otherwise it worked well, thanks! But I agree the API is awful and almost unusable. – haluzak Aug 21 '15 at 07:17
5

Category for EKParticipant:

import Foundation
import EventKit
import Contacts

extension EKParticipant {
    var email: String? {
        // Try to get email from inner property
        if respondsToSelector(Selector("emailAddress")), let email = valueForKey("emailAddress") as? String {
            return email
        }

        // Getting info from description
        let emailComponents = description.componentsSeparatedByString("email = ")
        if emailComponents.count > 1 {
            let email = emailComponents[1].componentsSeparatedByString(";")[0]
            return email
        }

        // Getting email from contact
        if let contact = (try? CNContactStore().unifiedContactsMatchingPredicate(contactPredicate, keysToFetch: [CNContactEmailAddressesKey]))?.first,
            let email = contact.emailAddresses.first?.value as? String {
            return email
        }

        // Getting email from URL
        if let email = URL.resourceSpecifier where !email.hasPrefix("/") {
            return email
        }

        return nil
    }
}
Anton Plebanovich
  • 1,296
  • 17
  • 17
  • The respondsToSelector approach is a bad idea. It's using a private (internal) property. That may get your app rejected from the app store by Apple if they find out. Apple devs might also change what type of object this returns, as they know it's private and nobody should call this (could return a mailto URL for example), and then your app will crash trying to treat this URL as a string. – uliwitness May 20 '22 at 15:54
  • Hi @uliwitness , From my experience `valueForKey(_:)` calls do not considered as private API usage since the method is public and available for anyone to use. And an app won't crash because optional unwrapping is used to cast returned value to `String`. – Anton Plebanovich May 21 '22 at 17:28
  • `valueForKey` itself is fine to use in your app. Using `valueForKey` to access private properties _is_ using private API. Apple don't always notice it, but that's just because their tools aren't good enough. The crash does happen when the returned value is not an object. `valueForKey` is only implemented for Objective-C types. You get an NSNumber for int and the like, an object for any kind of object, buit I'm pretty sure that it can't know what to do with a C `char*`. It's not an object, it has no length information (Could be fixed-length char array, C string or P-string) – uliwitness May 23 '22 at 16:23
2

Another option might be to look up the EKParticipant's URL. The output should be a mailto URI like mailto:xyz@xyz.com. There's some sparse documentation here:

http://developer.apple.com/library/ios/#documentation/EventKit/Reference/EKParticipantClassRef/Reference/Reference.html

Rahul Jaswa
  • 509
  • 4
  • 17
1

The property is not exposed per API version 6.0 - i'm searching for the answer myself and have not found any other work around other than parsing the email address out of the object's description. Example:

EKParticipant *organizer = myEKEvent.organizer
NSString *organizerDescription = [organizer description]; 
//(id) $18 = 0x21064740 EKOrganizer <0x2108c910> {UUID = D3E9AAAE-F823-4236-B0B8-6BC500AA642E; name = Hung Tran; email = hung@sampleemail.com; isSelf = 0} 

Parse the above string into an NSDictionary pull the email by key @"email"

Hung Tran
  • 440
  • 4
  • 6
-1

Based on Anton Plebanovich's answer I've made this Swift 5 solution for this made up problem Apple introduced:

Swift 5

private let emailSelector = "emailAddress"
extension EKParticipant {
  var email: String? {
    if responds(to: Selector(emailSelector)) {
      return value(forKey: emailSelector) as? String
    }

    let emailComponents = description.components(separatedBy: "email = ")
    if emailComponents.count > 1 {
      return emailComponents[1].components(separatedBy: ";")[0]
    }

    if let email = (url as NSURL).resourceSpecifier, !email.hasPrefix("/") {
      return email
    }

    return nil
  }
}
Assaf Gamliel
  • 11,935
  • 5
  • 41
  • 56
  • The `responds(to:)` approach is a bad idea. It's using a private (internal) property. That may get your app rejected from the app store by Apple if they find out. Apple devs might also change what type of object this returns, as they know it's private and nobody should call this. If they change it to return a plain C string for example, which isn't an object at all, your app will crash trying to treat this non-object as an object with `as?`. Similarly, there is no guarantee what `description` for an `EKParticipant` may return, so you may get garbage. – uliwitness May 20 '22 at 16:00