65

I'm using custom fonts in my app. They are copied to bundle and hardcoded to appName-info.plist. This fonts works perfectly in the whole app and in UIWebView.

Im loading htmlString [webView loadHTMLString:htmlString baseURL:nil]; I use this fonts in webView with css: fontFamily: fontName

But when i try to use WkWebView custom fonts not working. WkWebView displays random default fonts.

I tried to load it with main bundle path in base url and using font-face in css - WkWebView still displays random fonts.

How can I make custom fonts work in WKWebView?

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Sashke
  • 659
  • 1
  • 5
  • 4
  • How does it look like in iOS 9? WKWebView in iOS 9 can load local files now, is it also able to load fonts? – zavié Jul 19 '15 at 19:11

5 Answers5

59

I faced same issue, in my case i could fix it WITHOUT using base64 encoding and GCDWebServer.

Scenario:

  • WkWebView loading is through string html
  • WkWebView is using local .css
  • Fonts are local and are added at top level project
  • Entries for fonts are provided in appName-info.plist under key UIAppFonts / Fonts provided by application (Note: We need to make this dance because WKWebView ignores this key, but it is still a good idea to declare it so you can use the fonts in native controls)

Solution:

Add font face in .css at top level as follows

@font-face
{
    font-family: 'FontFamily';
    src: local('FontFamily'),url('FontFileName.otf') format('opentype');
}

DEMO PROJECT:

GitHub Project Link

NOTE: Fresh demo app run may take 2-3 sec, I have tested it for long html string it works same as UIWebView, no lags. Same font may look bit smaller in WKWebView than UIWebView.

catlan
  • 25,100
  • 8
  • 67
  • 78
Aditya Deshmane
  • 4,676
  • 2
  • 29
  • 35
44

Assuming you embed the font in your application as a resource that's copied to the target bundle, you can give the WKWebView access to the font by passing a NSURL to it's folder as the baseURL

NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
NSURL *bundleUrl = [NSURL fileURLWithPath:bundlePath];
[self.webView loadHTMLString:HTML baseURL:bundleUrl];

and define the font-face url without any preceeding path elements, which in turn makes WKWebKit prepend the baseURL

<style>
  @font-face { font-family: 'Custom Font'; src: url('CustomFont.ttf'); }
  ...
</style>
Henrik Hartz
  • 3,677
  • 1
  • 27
  • 28
  • what if there are multiple custom fonts ? – iAkshay Nov 21 '16 at 06:55
  • I have not tried that, but my solution doesn't limit itself to a specific font name, it just adds a search path if you will. – Henrik Hartz Nov 21 '16 at 07:01
  • 1
    For others trying this, IT WORKS!... but iOS font file names are case sensitive so make sure you match the font files camel case in the url used in the .css :) – Cliff Ribaudo Dec 13 '16 at 18:29
  • 1
    This solution WORKS. Also, if the font family has versions, in my case Ubuntu-R.ttf, Ubuntu-C.tff etc. you need to specifiy a single ttf as in: `@font-face { font-family: 'Ubuntu-R'; src: url('Ubuntu-R.ttf'); }` Works beautifully. Thank you! – oyalhi Aug 31 '17 at 09:00
  • 3
    Thank you for a sane solution. One suggestion: You can create the bundle url directly instead of using the path. e.g. `NSURL *bundleURL = [[NSBundle mainBundle] bundleURL];` – Jason Moore Nov 16 '17 at 21:04
  • 6
    @HenrikHartz I have a concern. Would setting `baseURL` to main bundle breaks all the relative urls in your rendered html? Because it would resolve onto main bundle url instead of a real API url. I think that's a big issue. – Hlung Apr 17 '18 at 04:30
22

Since I don't want to use another third party just for that and since I'm building the html string itself, I took the first part of using the font-face and instead of using a url to a remote or local file, i converted the fonts to base64.

The css looks like this:

@font-face {
    font-family: 'FONTFAMILY';
    src: url(data:font/ttf;base64,FONTBASE64) format('truetype');
}

You can replace the FONTFAMILY with the family that you need and the FONTBASE64 with the base 64 string that was generated from the font.

If you need to create the base64 string in your application, you can use this, just provide the filename and type (i used it to get other files as well so it's more generic, you can remove the ofType parameter and use @"ttf" instead):

- (NSString*)getBase64FromFile:(NSString*)fileName ofType:(NSString*)type
{
    NSString * filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:type];

    // Create NSData object
    NSData *nsdata = [NSData dataWithContentsOfFile:filePath];

    // Get NSString from NSData object in Base64
    NSString *base64Encoded = [nsdata base64EncodedStringWithOptions:0];

    return base64Encoded;
}

if you want to do it only one time and then save it in some file, you can use any of the online websites that converts files to base64, like this: http://www.opinionatedgeek.com/dotnet/tools/base64encode/

kalsky
  • 469
  • 4
  • 4
  • This works for me in a regular browser, if I am testing the HTML file, however, in WKWebView, it reverts to the default font. Any idea what could have gone wrong? I extracted the base64 values of the font and replaced it in the appropriate part in the css-code. – dezinezync Oct 10 '14 at 06:19
  • @dezinezync I have the same issue using https://github.com/christopherdro/react-native-html-to-pdf on react-native, this library is using WkWebView under the hood. Images with base64 data sources work fine, but "@font-face" with a src url with base64 data does not. Even though the exact same HTML displays fine in recent (2023) Safari and Chrome. – timotgl Jan 11 '23 at 15:51
12

Update: This is now possible using WKURLSchemeHandler.

@interface MySchemeHandler : NSObject <WKURLSchemeHandler>
@end

@implementation MySchemeHandler

- (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask
{
  NSURL *url = urlSchemeTask.request.URL;
  NSString *mimeType = [NSString stringWithFormat:@"text/%@", url.pathExtension]; //or whatever you need
  NSURLResponse *response = [[NSURLResponse alloc] initWithURL:url MIMEType:mimeType expectedContentLength:-1 textEncodingName:nil];
  [urlSchemeTask didReceiveResponse:response];
  NSData *data = [self getResponseData];
  [urlSchemeTask didReceiveData:data];
  [urlSchemeTask didFinish];
}
@end

And when configuring your WKWebView instance:

WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
MySchemeHandler *handler = [[MySchemeHandler alloc] init];
[config setURLSchemeHandler:handler forURLScheme:@"myScheme"];
//now pass the config to your WKWebView

------Old answer----------

My guess is that the WKWebView can no longer access fonts specific to the application because it's now in a separate process (XPC).

I got around this by adding the font with @font-face declarations in CSS. See here for details on MDN about how to do this.

Example:

@font-face
{
  font-family: "MyFontFace";
  src:url('url-to-font.ttf');
}

//And if you have a font with files with different variants, add this:
@font-face
{
  font-family: "MyFontFace";
  src:url('url-to-italic-variant.ttf');
  font-style:italic;
}

But this is going to reference a local file, which the WKWebView can't do (I assume you've already discovered this because you're loading an HTML string instead of the local file). As per a comment on this question, I was able to use GCDWebServer to get my local HTML file working. In your app delegate, after adding the relevant files to your project as per the GCDWebServer's wiki on GitHub:

GCDWebServer *server = [[[GCDWebServer alloc]init]autorelease];
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
[server addGETHandlerForBasePath:@"/"
   directoryPath:bundlePath indexFilename:nil
   cacheAge:0 allowRangeRequests:YES];
[server startWithPort:8080 bonjourName:nil];

Now you can reference an HTML file named test.html in your bundle like this:

NSString *path = @"http://localhost:8080/test.html";
NSURL *url = [NSURL URLWithString:path];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[myWebView loadRequest:request];

Put the aforementioned @font-face declaration in a style element in your HTML file (or in your HTML string if you really just need to load a string) and you should be good to go.

Tom Hamming
  • 10,577
  • 11
  • 71
  • 145
  • 1
    I tried @ font-face in the WKWebView and it does not seem to work when running on device (iOS 8.4). Works in the simulator though. Tried to use @ font-face with WOFF, SVG and also inlined as base64. – Robin Andersson Aug 18 '15 at 11:58
  • Looking in Safari dev tools it just say "An error occurred trying to load this resource", no HTTP error codes or anything. – Robin Andersson Aug 18 '15 at 11:59
  • 2
    `font-weight` instead of `font-style` works for me to support bold variant.. I hope will be helpful for someone... – albirrojo7 Mar 01 '18 at 18:46
  • I'm loading WKWebView with a string, and referencing local files from Documents folder (not bundle). Using WKURLSchemeHandler was the best solution for me. Only other way I got this working was with base64, but I got some big slow-downs in some cases with this. – Ernie Thomason Jun 30 '20 at 06:12
  • Woah! What is this `WKURLSchemeHandler` monstrosity!? `@font-face` worked just fine with my `WKWebView` [Swift 5 || iOS 14.5]. Ref: [answer-1](https://stackoverflow.com/a/36495850/2857130), [answer-2](https://stackoverflow.com/a/63294541/2857130), [answer-3](https://stackoverflow.com/a/49372182/2857130) – staticVoidMan Jun 14 '21 at 15:04
  • I had hoped this would be a solution that would also allow relative links (e.g. `href="/some/path"`) to work by configuring the view's baseURL to some http domain, but for some reason, when I set a baseURL, the scheme handler stops getting called. I was making custom urls like `font://MyFont.ttf` (which do work with baseURL=nil). – walkingbrad Apr 08 '23 at 01:58
7

Here is a swift 3 version of @Henrik Hartz awesome answer:

loadHTMLString(htmlString, baseURL: NSURL.fileURL(withPath: Bundle.main.bundlePath))
Dorad
  • 3,413
  • 2
  • 44
  • 71