3

My app is an eReader for viewing books on iPhone/iPad. User has an option of downloading the book which we save on device as an .epub file (zipped content) - this enables the book viewing when offline. The page content is in html.

Some assets, like videos, are not in this .epub content though. When user navigates to such a page I have to detect the external asset which is not in .ePub and display a simple placeholder with text "This content is unavailable when offline".

These videos could be embedded in an iFrame html tag or an object tag.

Right now we have a class which inherits from NSURLProtocol abstract class and I am using it to handle this requirement. I have implementation for these 3 methods:

+ (BOOL) canInitWithRequest:(NSURLRequest *)request

- (void)startLoading

- (void) sendResponse:(NSData *)data mimetype:(NSString *)mimetype url:(NSURL *)url

My problem right now is that I have a simple .png that shows "This content is unavailable when offline" but it’s ugly because sometime the area of the iFrame is smaller than the image. How do I scale it to fit? Or is there a better approach? It doesn’t have to be an image as long as I can show this message that content is not available. My current code in startLoading method:

- (void) startLoading
{
    …
    NSString *bundlePath = [NSString stringWithFormat:@"%@/%@",[[NSBundle mainBundle] bundlePath],@"PxeReaderResources.bundle"];
    NSString *pathToPlaceholder = [NSString stringWithFormat:@"%@/%@",bundlePath,@"OfflinePlaceholder.png"]; //my placeholder image
    NSLog(@"Path to placeholder is: %@", pathToPlaceholder);

    data = [[NSFileManager defaultManager] contentsAtPath:pathToPlaceholder];

    … 

    [self sendResponse:data mimetype:@"application/octet-stream" url:self.request.URL];
}

- (void) sendResponse:(NSData *)data mimetype:(NSString *)mimetype url:(NSURL *)url
{
    NSDictionary *headers = @{@"Content-Type" : mimetype, @"Access-Control-Allow-Origin" : @"*", @"Cache-control" : @"no-cache"};
    NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url statusCode:200 HTTPVersion:@"HTTP/1.1" headerFields:headers];

    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    [self.client URLProtocol:self didLoadData:data];
    [self.client URLProtocolDidFinishLoading:self];
}

Thank you for any suggestions.

rich
  • 461
  • 2
  • 5
  • 13

2 Answers2

1

I was able send a simple custom html div tag in place of the external asset. Here is the code - hope this will help someone. NOTE: all this code is in my custom class that inherits from NSURLProtocol

#define EXTERNAL_REQUEST_KEY @"ExternalRequest"

+ (BOOL) canInitWithRequest:(NSURLRequest *)request
{
     NSString* filePath = [[PxePlayer sharedInstance] getBaseURL]; //gets base path to the epub zipped folder

    if ([Reachability isReachable])
    {
        return filePath != nil && [@"file" caseInsensitiveCompare:request.URL.scheme] == NSOrderedSame;
    }
    else
    {
        if ([request.URL.scheme isEqualToString:@"https"] || [request.URL.scheme isEqualToString:@"http"]) //these schemes won't work if offline
        {
            [NSURLProtocol setProperty:@YES forKey:EXTERNAL_REQUEST_KEY inRequest:(NSMutableURLRequest*)request]; //marking those request so that we will only provide placeholder for these requests
        }
        return filePath != nil && ([@"file" caseInsensitiveCompare:request.URL.scheme] == NSOrderedSame ||
                               [@"https" caseInsensitiveCompare:request.URL.scheme] == NSOrderedSame ||
                               [@"http" caseInsensitiveCompare:request.URL.scheme] == NSOrderedSame);
    }
}

- (void) startLoading
{
    ...

    if ([NSURLProtocol propertyForKey:EXTERNAL_REQUEST_KEY inRequest:self.request])
    {   
        custMimeType = @"text/html";

        data = [self prepareDataForPlaceholderForExternalAsset];
    }

    ...

    if (data)
    {
        //Now ready to send the placeholder as custom response
        [self sendResponse:data mimetype:custMimeType url:self.request.URL];
    }
}

//method to create a simple div tag as NSData
- (NSData*) prepareDataForPlaceholderForExternalAsset
{
    NSString *placeholder =
    @"<div style=\"background-color:#efefef; border:1px solid #999999; text-align:center; width:100%; height:100%\"> \
        <font font-family=\"Helvetica Neue\" font-weight=\"Medium\" size=\"5\" color=\"#b3b3b3\"> \
          <div style=\"display:inline-block; margin-top:5%\"> \
            This content is unavailable when offline or printing \
          </div> \
        </font> \
      </div>";

    return [placeholder dataUsingEncoding:NSUTF8StringEncoding];
}

- (void) sendResponse:(NSData *)data mimetype:(NSString *)mimetype url:(NSURL *)url
{
    NSDictionary *headers = @{@"Content-Type" : mimetype, @"Access-Control-Allow-Origin" : @"*", @"Cache-control" : @"no-cache"};
    NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url statusCode:200 HTTPVersion:@"HTTP/1.1" headerFields:headers];

    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    [self.client URLProtocol:self didLoadData:data];
    [self.client URLProtocolDidFinishLoading:self];
}
rich
  • 461
  • 2
  • 5
  • 13
0

Instead of trying to intercept the resource loads, you could use (inject) JavaScript to traverse the DOM looking for these object/iframe tags and replace them with your pretty "Not available offline" messaging. You would likely do this once the page loaded, or you could attempt to parse and modify the HTML before it was passed to the WebView.

TomSwift
  • 39,369
  • 12
  • 121
  • 149
  • Thank you Tom. I initially thought of this approach but then I realized that parsing the html may be a bit too much since I don't even know what all the tags are those urls may be in. Once I realized I can post back custom html snippet for a given url request the NSURLProtocal route became a pretty easy solution. – rich Sep 18 '15 at 04:34