Matt McMurry

Posts Tagged ‘iphone

Pasting (simple)HTML into the Mail app (iOS)

with 4 comments

If you are not familiar with the UIPasteBoard class and how to copy and paste you should first get caught up on the basics. This post will explain a few steps that may be too advanced for some people but if you take your time, you should be able to get through it.

If you need to get started first with Copy and Paste, check out these guides: Apples Copy, Cut, Paste Guide , UIPasteboard Class Reference, and Understanding UTIs

Honestly I don’t think they are the best guides and I don’t think that there are any good ones out there.

Pasting HTML into the Mail app is a little tricky to figure out. You may have tried to just paste regular html into Mail as an html or text type of data and saw that all you get is the HTML code. The problem is, Mail expects an archive paste type as opposed to regular text or html type. Further the archive must be correctly put together.

This guide is only about copying strings of html code. You can copy images that are part of the html block you want to copy with the html too, but I haven’t spent anytime on that. I’ll give some tips at the end of where to start on copying the images contained in the html block.

Lets get to it.

Prepare your HTML

Get the HTML you want to copy but start on the highest most level of you HTML that is inside of the <body> tag. The HTML you are going to copy should not contain a Doctype, html, head, or body tag. (I am fairly certain it will get stripped if you try to put it on there). You are also going to have to inline ALL of you css.
The reason for this is that the Mail app Compose Message is already an HTML block, so you are pasting directly into the body. I know it can be frustrating to inline a bunch of css, but right now I know of no other way. Mobile safari took my css block and inlined all of it automatically, meaning it didn’t copy the the <style> block.

NSString *htmlString = [[NSString alloc] initWithString:@"<h1>This is a Header</h1>"];

Encode your html

Now we need to base64 encode all the html you want to copy. Objective-C doesn’t have a read base64 encode (if I wrong please tell me). Here is a category implementation of the NSData Method we will use to encode the data:
I store this category implementation in a class called Extensions.h/m where I do all my category implementations:

Extensions.h

@interface NSData (MBBase64)
 + (id)dataWithBase64EncodedString:(NSString *)string;
 - (NSString *)base64Encoding;
@end

Extensions.m

static const char encodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@implementation NSData (MBBase64)
 + (id)dataWithBase64EncodedString:(NSString *)string;
 {
  if (string == nil)
   [NSException raise:NSInvalidArgumentException format:@""];
  if ([string length] == 0)
   return [NSData data];
  static char *decodingTable = NULL;
  if (decodingTable == NULL)
  {
   decodingTable = malloc(256);
   if (decodingTable == NULL)
    return nil;
   memset(decodingTable, CHAR_MAX, 256);
   NSUInteger i;
   for (i = 0; i < 64; i++)
    decodingTable[(short)encodingTable[i]] = i;
  }
  const char *characters = [string cStringUsingEncoding:NSASCIIStringEncoding];
  if (characters == NULL)     //  Not an ASCII string!
   return nil;
  char *bytes = malloc((([string length] + 3) / 4) * 3);
  if (bytes == NULL)
   return nil;
  NSUInteger length = 0;
  NSUInteger i = 0;
  while (YES)
  {
   char buffer[4];
   short bufferLength;
   for (bufferLength = 0; bufferLength < 4; i++)
   {
    if (characters[i] == '')
     break;
    if (isspace(characters[i]) || characters[i] == '=')
     continue;
    buffer[bufferLength] = decodingTable[(short)characters[i]];
    if (buffer[bufferLength++] == CHAR_MAX)      //  Illegal character!
    {
     free(bytes);
     return nil;
    }
   }
   if (bufferLength == 0)
    break;
   if (bufferLength == 1)      //  At least two characters are needed to produce one byte!
   {
    free(bytes);
    return nil;
   }
  //  Decode the characters in the buffer to bytes.
  bytes[length++] = (buffer[0] << 2) | (buffer[1] >> 4);
  if (bufferLength > 2)
   bytes[length++] = (buffer[1] << 4) | (buffer[2] >> 2);
  if (bufferLength > 3)
   bytes[length++] = (buffer[2] << 6) | buffer[3];
 }
 realloc(bytes, length);
 return [NSData dataWithBytesNoCopy:bytes length:length];
}
- (NSString *)base64Encoding;
{
 if ([self length] == 0)
  return @"";
 char *characters = malloc((([self length] + 2) / 3) * 4);
 if (characters == NULL)
  return nil;
 NSUInteger length = 0;
 NSUInteger i = 0;
 while (i < [self length])
 {
  char buffer[3] = {0,0,0};
  short bufferLength = 0;
  while (bufferLength < 3 && i < [self length])
   buffer[bufferLength++] = ((char *)[self bytes])[i++];
  //  Encode the bytes in the buffer to four characters, including padding "=" characters if necessary.
  characters[length++] = encodingTable[(buffer[0] & 0xFC) >> 2];
  characters[length++] = encodingTable[((buffer[0] & 0x03) << 4) | ((buffer[1] & 0xF0) >> 4)];
  if (bufferLength > 1)
   characters[length++] = encodingTable[((buffer[1] & 0x0F) << 2) | ((buffer[2] & 0xC0) >> 6)];
  else characters[length++] = '=';
  if (bufferLength > 2)
   characters[length++] = encodingTable[buffer[2] & 0x3F];
  else characters[length++] = '=';
 }
 return [[[NSString alloc] initWithBytesNoCopy:characters length:length encoding:NSASCIIStringEncoding freeWhenDone:YES] autorelease];
}
@end

base64 method source: http://www.cocoadev.com/index.pl?BaseSixtyFour

I must promptly apologize for not formatting that code better, however I imagine that you are less concerned with how the base64 encoding process works than you are being able to paste HTML.

For the sake of avoiding the answer to a much expected question, make sure you import this class Extensions.h (or whatever the name of your class is) into the class you are working in.
Now we convert the NSString to NSData:

NSData *htmlData = [htmlString dataUsingEncoding:NSUTF8StringEncoding];
[htmlString release];

Now we need to encode it:

NSString *htmlEncoded = [htmlData base64Encoding];

Add the Wrapper

The next step is to put the encoded data into the xml wrapper that will make this html into an archive.
You’ll need to create a template file. Mine is: webArchiveTemplate.txt
It contains this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
 <dict>
  <key>WebMainResource</key>
  <dict>
   <key>WebResourceData</key>
   <data>
    REPLACE_WITH_ENCODED_DATA
   </data>
   <key>WebResourceFrameName</key>
   <string></string>
   <key>WebResourceMIMEType</key>
   <string>text/html</string>
   <key>WebResourceTextEncodingName</key>
   <string>UTF-8</string>
   <key>WebResourceURL</key>
   <string>about:blank</string>
  </dict>
 </dict>
</plist>

Put the file in a string and replace the “REPLACE_WITH_ENCODED_DATA” with the encodedHTML:


NSMutableString *webArchiveTemplate = [[NSMutableString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"webArchiveTemplate" ofType:@"txt"]];
[webArchiveTemplate replaceOccurrencesOfString:@"REPLACE_WITH_ENCODED_DATA"
withString:encoded
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [webArchiveTemplate length])];

Finally. Add it To the paste board


NSString *htmlType = @"Apple Web Archive pasteboard type";
NSDictionary *item = [NSDictionary dictionaryWithObjectsAndKeys:webArchiveTemplate,htmlType,nil];
[webArchiveTemplate release];
UIPasteBoard  *pasteBoard = [UIPasteBoard generalPasteBoard];
pasteBoard.items = [NSArray arrayWithObject:item];

The way I am adding above is the way to support multiple representations of the data set so I stuck with it for this example to make it easy to expand on.

Go Paste your HTML into a Mail Message!

I will try to have a demo app put together soon.

If you implement this code in an as is state, you will not have to touch any “copy” button, this code just throws the example straight into the pasteboard.

How to Copy Images that are part of a webpage.

While I haven’t done this. I know how you can get an example archive that contains images.

Create a sample html file that represents what you will be copy and pasting. Be sure to include all the images that will be on the webpage.

Open the html file in Safari on your desktop. Save the webpage as an archive.

Open the archive file in something like TextWrangler. If you open it in Text Edit, it will attempt to render the archive as a webpage.

You should now be able to see how the archive xml links the encoded image data into the html page.

Good luck!


Advertisements

Written by mcmurrym

August 13, 2010 at 4:41 am

Posted in Uncategorized

Tagged with , , , , ,