views:

696

answers:

2

I'm consuming a web service in my iPhone app. The web service method returns a response which has several fields (eg. ID, Description, etc..). One of these fields contains binary image data which I need to convert to UIImage in my iPhone application.

I'm using a NSXMLParser successfully to extract data from the XML response. In the parser:foundCharacters: selector of the XMLParser, it gives a NSString* pointing to the string within each field. Since this is a string, this is what I do to read image data when i encounter the image field:

UIImage *img = [[UIImage alloc] initWithData:[string dataUsingEncoding:NSUTF8StringEncoding]];

But img variable is still "nil" after this line. Seems like the data from XML string is not compatible with conversion. What am I doing wrong here? (I'm capable of reading other fields into my variables but not this image data field)

Thanks in advance..

+1  A: 

The trick with NSString is that there is an implicit encoding associated with the data it contains. The image you are receiving, however, is likely in a format that will not convert properly, as it is either binary data or some lossless encoding of binary data (e.g., base64). The trick, then, is to make sure you don't let NSString perform any encoding conversions at all, otherwise your image data will be corrupted. Instead of using dataUsingEncoding: I would try an API more like getBytes:maxLength:usedLength:encoding:options:range:remainingRange. While more complex than dataUsingEncoding: I think it'll give you the flexibility you need to get just the data from the NSString and nothing more.

fbrereto
Thanks for your valuable idea, but then again, I would need to know the number of bytes in the string. How can I know that? both `[string length]` and `[string lengthOfBytesUsingEncoding]` involves character encoding. If I can know the byte length, I might be able to use [string getCString] to get the raw bytes. Thanks again.
ravinsp
+1  A: 

Following fbrereto's answer, I managed to convert the string into NSData using a code sample at this link: http://www.cocoadev.com/index.pl?BaseSixtyFour (scroll to the code sample by MiloBird user). Now I can construct the image successfully.

It uses objective-c categories to add the selector + (id)dataWithBase64EncodedString:(NSString *)string; to the NSData class. Here's the necessary code sample I extracted from the above link:

//MBBase64.h

@interface NSData (MBBase64)

+ (id)dataWithBase64EncodedString:(NSString *)string;     //  Padding '=' characters are optional. Whitespace is ignored.

@end


//MBBase64.m

static const char encodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

@implementation NSData (MBBase64)

+ (id)dataWithBase64EncodedString:(NSString *)string;
{
    if (string == nil)
        [NSException raise:NSInvalidArgumentException format:nil];
    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] == '\0')
                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];
}

@end

Thank you all!

ravinsp