Monday, February 21, 2011

How to Find MFMailComposeViewController Email Addresses

One of the missing iOS APIs is a way to find out whom did user send email to. Otherwise MFMailComposeViewController is excellent: easy to setup, easy to launch, easy to use and easy to close. You just have no idea what user was doing with it!

I really needed to know who were email recipients. There are several good reasons for this and I just had one of those. So it was time to do some experimenting!

Write this (thrice now), which is close, but just not good enough. Maybe someone can continue from here? First standard didFinishWithResult callback, nothing special there. Bit simplified for this case:
- (void)mailComposeController:(MFMailComposeViewController *)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError*)error
{   
    if (result == MFMailComposeResultSent)
    {
        NSString *email = [self findEmailAddresses:controller.view depth:0];
        NSLog(@"%@", email);
        [self.viewController dismissModalViewControllerAnimated:YES];
    }
}
Here's the interesting code, which can be used to peek what's inside the MFMailComposeViewController blackbox (UPDATE: easier way to check view hierarchy). Pretty common view hierarchy walking code, now especially looking for text strings:
- (NSString *)findEmailAddresses:(UIView *)view depth:(NSInteger)count
{
    NSString *eAddress = nil;
    if (!view)
        return eAddress;
   
    NSMutableString *tabString = [NSMutableString stringWithCapacity:depth];
    for (int i = 0; i < depth; i++)
        [tabString appendString:@"-- "];
    NSLog(@"%@%@", tabString, view);
   
    if ([view isKindOfClass:[UITextField class]])
    {
        // MAGIC: debugger shows email address(es) in first textField
        // but only if it's about max 35 characters
        if (((UITextField *)view).text)
        {
            eAddress = [NSString stringWithString:((UITextField *)view).text];
            NSLog(@"FOUND UITextField: %@", eAddress ? eAddress : @"");
        }
    }
   
    NSArray *subviews = [view subviews];
    if (subviews) {
        for (UIView *view in subviews)
        {
            NSString *s = [self findEmailAddresses:view depth:depth+1];
            if (s) eAddress = s;
                }
    }
   
    return eAddress;
}
Problems: it finds the recipient email address(es), but only if that fits in about 35 character text string. Otherwise you find only a summary string like "aaa@aaa.aa & 2 more...". So where are the actual email addresses in this case?

12 comments:

  1. Great post. I'm dealing with the same thing right now. I was just recursively going through all the subviews until I found the MFRecipientTextField view... but that leads to the same "& 2 more..." problem. Did you find a way to get this to work?

    Also, I don't quite understand what your code is doing. Is "depth" only needed for debugging purposes, or is it actually needed for you to find the email address? I'm doing basically the same thing, but I'm never using appendString or depth. Thanks!

    ReplyDelete
  2. Nope, I gave up. We thought that getting email recipient addresses this way might get app rejected, so code was never used for real. But I'm still interested, in case you solve the "& 2 more..." :)

    Depth is used to make debug output more readable, nothing else.

    ReplyDelete
  3. I am sending email using default mailComposer using MFMailComposerViewController. I don't know how to set the from email address and where to set its credentials....Can anyone help me out please

    ReplyDelete
  4. That info comes from your Mail settings. Can't change them in code by yourself.

    ReplyDelete
  5. Can we just get From Email address.. I dont want to change that address

    ReplyDelete
  6. From Email address depends on user's Mail settings, don't know how to get it. Why would you even need it ar client side, I'm wondering.. Anyway, once your server receives the email, then you know it :)

    ReplyDelete
  7. Hey Jouni did u got the solution of the above said problem, am in need to know the no. of email adresses the user has entered. Pls do update if u got any solution for the same

    ReplyDelete
  8. Sorry, but no. Still no idea where to find the "remaining" email addresses.

    ReplyDelete
    Replies
    1. Hey thanks for the Quick reply, I am using ur code in my App is there any chance that Apple reviewers may catch me accessing their private APis, if yes please do reply me.

      Thanks n Regards

      Delete
  9. Will Apple catch you using private APIs: YES! They have automated tools to do such things :)

    However going through object hierarchy is not using any private APIs, so it depends on app UI. If you make it obvious that you're doing something which shouldn't be done, you risk rejection.

    ReplyDelete
  10. Hi Juoni, this is a great site and tutorial.
    I am preparing an application where i will accept member users. I want to verify the email of signing user. To do so, i want to send an activasion code to the user and write this code and mail adress matching into sqlite database. After as user checks the mail account and write the activasion code i want to accept as user. This way i will be sure that this mail adress is real and the guy who wants to sign in is the real user.

    How to let user take an activasion code to his/her mail adress??? This mail will be generated automatically and will be sent from one source.

    Please share your ideas cuz could not find any tutorial.

    ReplyDelete
  11. Easy: you request user email address and send that internally to your server. Server sends the account verification email containing an URL pointing back to your server. That URL contains same (or rather some other unique code generated from original) activation code as parameter :)

    In other words, client does not send the email. To create a client-server system without a server would be... interesting. Lot of challenges.

    ReplyDelete