How to access image resource in asset catalog from UIWebView (or WKWebView)

6.4k views Asked by At

We have a local html to show in UIWebView. And there, we want to show images that are defined in Asset Catalog.

I know that we can do similar thing if we had the image flat in the main bundle. The code snippet would be like this.

webView.loadHTMLString("<img src='target_image.png'/>", baseURL: NSBundle.mainBundle().bundleURL)

However, I am not sure what I can specify for "target_image.png" if the png file is packaged in asset catalog. (Furthermore, we want to specify pdf to take advantage of vector image support in Xcode 6)

Does anybody have any idea how to achieve this?

4

There are 4 answers

0
Alex Shaffer On BEST ANSWER

Since the asset catalog is stored in the bundle as a single file, there's no way to get an URL to a singular asset.

The simplest solution is to NOT use the asset catalog for images you'll show in a web view.

If keeping the images in the asset catalog is essential, your only option is to "unpack" the required asset(s) into distinct files to the documents directory of the application bundle, and then reference those files.

2
enigrify On

One way to do it is to convert the image into base64 and enter that into the HTML.

if let base64 = UIImage(named: "myImage")?.pngData()?.base64EncodedString() {
    webView.loadHTMLString("<img src='data:application/png;base64,(base64)'/>", baseURL: nil)
}
3
Alexander On

There is a way to access image resource in asset catalog from UIWebView. You should create NSUrlProtocol subclass, register your new protocol and manually respond for WebView images request.

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@end

@implementation AppDelegate
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        [NSURLProtocol registerClass:[ImageProtocol class]];    
        //... other code
        return YES;
    }
@end

@interface ImageProtocol : NSURLProtocol
@end

@implementation ImageProtocol

    + (BOOL)canInitWithRequest:(NSURLRequest *)request {
        NSString *imgName = (NSString *)[[request.URL.absoluteString componentsSeparatedByString:@"/"] lastObject];
        AppDelegate *appDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
        NSLog(@"imgName = %@",imgName);
        if ([UIImage imageNamed:imgName] != nil) {// or check for explicit name "target_image.png"
            return YES;
        }
        return NO;
    }

    + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
        return request;
    }

    + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b{
        return [super requestIsCacheEquivalent:a toRequest:b];
    }

    - (void)startLoading {
        NSString *imgName = (NSString *)[[self.request.URL.absoluteString componentsSeparatedByString:@"/"] lastObject];
        UIImage *img = [UIImage imageNamed:imgName];
        NSString *mimeType = @"image/png";
        @try {
            NSData *imageData = nil;

            if ([imgName hasSuffix:@".png"]) {
                imageData = UIImagePNGRepresentation(img);
            } else if ([imgName hasSuffix:@".jpg"]) {
                imageData = UIImageJPEGRepresentation(img, 1);
                mimeType = @"image/jpg";
            }

            NSString *encoding = @"utf-8";
            NSURLResponse *response = [[NSURLResponse alloc] initWithURL:self.request.URL
                                                        MIMEType:mimeType
                                           expectedContentLength:imageData.length
                                                textEncodingName:encoding];

            [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
            [self.client URLProtocol:self didLoadData:imageData];
            [self.client URLProtocolDidFinishLoading:self];
        } @catch (NSException *exception) {
            NSError *err = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil];
            [self.client URLProtocol:self didFailWithError:err];
            NSLog(@"%@",exception.debugDescription);
        }
    }

    - (void)stopLoading {
    // do nothing
    }

    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
        [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    }

    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.client URLProtocol:self didLoadData:data];
    }

    - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
        [self.client URLProtocolDidFinishLoading:self];
    }

    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
        [self.client URLProtocol:self didFailWithError:error];
    }

@end
0
clearlight On

Pretty easy to do.

func base64String(from image: UIImage?) -> String? {
    guard let image = image, let data = image.pngData() else { return nil }
    return data.base64EncodedString()
}


if let base64String = base64String(from: UIImage(named: "myAssetImage")) {

    let htmlString = """
    <html>
    <body>
        <img src="data:image/png;base64,\(base64String)"/>
    </body>
    </html>
    """

    webView.loadHTMLString(htmlString, baseURL: nil)
}

Note: SF Symbols can be incorporated this way too. Just create a UIImage from the SF Symbol of choice as you prefer, such as with:

let image = UIImage(systemName: "pencil")

Then use the same technique with that image as above. Although the default size won't match the scaling you're probably used to.

In that case, you can use inline CSS to scale the image:

    let scaledImageString = "<img src=\"data:image/png;base64,
          \(base64String!)\" style=\"width:20px; height:20px;\">"