46

I am successfully able to get HTML content and display into my UIWebView.

But want to customize the content by adding an external CSS file. I can only change the size of text and font. I tried every possible solution to make changes but it does not work - it shows no changes.

Below is my code

HTMLNode* body = [parser body];
HTMLNode* mainContentNode = [body  findChildWithAttribute:@"id" matchingName:@"main_content" allowPartial:NO];
NSString *pageContent = [NSString stringWithFormat:@"%@%@", cssString, contentHtml];
        [webView loadHTMLString:pageContent baseURL:[NSURL URLWithString:@"http://www.example.org"]];

-(void)webViewDidFinishLoad:(UIWebView *)webView1{
    int fontSize = 50;
    NSString *font = [[NSString alloc] initWithFormat:@"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '%d%%'", fontSize];
    NSString *fontString = [[NSString alloc] initWithFormat:@"document.getElementById('body').style.fontFamily=\"helvetica\""];

    [webView1 stringByEvaluatingJavaScriptFromString:fontString];
    [webView1 stringByEvaluatingJavaScriptFromString:font];
}

Please help me get the css stylesheet in my view.

Marcus Leon
  • 55,199
  • 118
  • 297
  • 429
cole
  • 3,147
  • 3
  • 15
  • 28

6 Answers6

71

You can do it like this:

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    NSString *cssString = @"body { font-family: Helvetica; font-size: 50px }"; // 1
    NSString *javascriptString = @"var style = document.createElement('style'); style.innerHTML = '%@'; document.head.appendChild(style)"; // 2
    NSString *javascriptWithCSSString = [NSString stringWithFormat:javascriptString, cssString]; // 3
    [webView stringByEvaluatingJavaScriptFromString:javascriptWithCSSString]; // 4
}

What this code does:

// 1 : Define a string that contains all the CSS declarations

// 2 : Define a javascript string that creates a new <style> HTML DOM element and inserts the CSS declarations into it. Actually the inserting is done in the next step, right now there is only the %@ placeholder. I did this to prevent the line from becoming too long, but step 2 and 3 could be done together.

// 3 : Combine the 2 strings

// 4 : Execute the javascript in the UIWebView

For this to work, your HTML has to have a <head></head> element.

EDIT:

You can also load the css string from a local css file (named "styles.css" in this case). Just replace step //1 with the following:

NSString *path = [[NSBundle mainBundle] pathForResource:@"styles" ofType:@"css"];
NSString *cssString = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];

As another option you can just inject a <link> element to the <head> that loads the CSS file:

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    NSString *path = [[NSBundle mainBundle] pathForResource:@"styles" ofType:@"css"];
    NSString *javascriptString = @"var link = document.createElement('link'); link.href = '%@'; link.rel = 'stylesheet'; document.head.appendChild(link)";
    NSString *javascriptWithPathString = [NSString stringWithFormat:javascriptString, path];
    [webView stringByEvaluatingJavaScriptFromString:javascriptWithPathString];
}

This solution works best for large CSS files. Unfortunately it does not work with remote HTML files. You can only use this when you want to insert CSS into HTML that you have downloaded to your app.

UPDATE: WKWebView / Swift 3.x

When you are working with a WKWebView injecting a <link> element does not work because of WKWebView's security settings.

You can still inject the css as a string. Either create the CSS string in your code //1 or put it in a local file //2. Just be aware that with WKWebView you have to do the injection in WKNavigationDelegate's webView(_:didFinish:) method:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    insertCSSString(into: webView) // 1
    // OR
    insertContentsOfCSSFile(into: webView) // 2
}

func insertCSSString(into webView: WKWebView) {
    let cssString = "body { font-size: 50px; color: #f00 }"
    let jsString = "var style = document.createElement('style'); style.innerHTML = '\(cssString)'; document.head.appendChild(style);"
    webView.evaluateJavaScript(jsString, completionHandler: nil)
}

func insertContentsOfCSSFile(into webView: WKWebView) {
    guard let path = Bundle.main.path(forResource: "styles", ofType: "css") else { return }
    let cssString = try! String(contentsOfFile: path).trimmingCharacters(in: .whitespacesAndNewlines)
    let jsString = "var style = document.createElement('style'); style.innerHTML = '\(cssString)'; document.head.appendChild(style);"
    webView.evaluateJavaScript(jsString, completionHandler: nil)
}
joern
  • 27,354
  • 7
  • 90
  • 105
  • 1
    Sorry for my late reply :( Could you explain what this line means ? NSString *javascriptString = @"var style = document.createElement('style'); style.innerHTML = '%@'; document.head.appendChild(style)"; It does not work for me ..! – cole Oct 16 '15 at 09:49
  • Hi Cole, I've updated my answer with some explanations on what the code does. – joern Oct 16 '15 at 09:58
  • Hi, Thanks for making me understand your code !! But its not working.. i have created separate css file for the app. – cole Oct 18 '15 at 11:18
  • Could you help me if i use a local css file. How to call in webViewDidFinishLoad: ? I tried few methods online and it does not work – cole Oct 18 '15 at 11:28
  • Sure, you can load the contents of a local CSS file into `cssString` and inject it into the HTML with the same method. See my edited answer – joern Oct 18 '15 at 11:48
  • I did same thing and it did not worked. The layout is same :( – cole Oct 18 '15 at 12:10
  • What HTML page are you loading? Can you post the URL or the HMTL file if it's a local file? Also please show me the CSS you are trying to inject. – joern Oct 18 '15 at 12:13
  • Sorry i cannot share my URL link on public site but can share css file. If you like i can send it directly to your mail and you can check yourself ? – cole Oct 18 '15 at 12:42
  • send, please check !! – cole Oct 18 '15 at 12:57
  • That's quite a big CSS file. It is better if you insert it into the HTML via a `` element. See updated answer. Also your file had encoding problem. I send you a clean version of your file via mail – joern Oct 18 '15 at 13:18
  • Thanks for fixing it. I send you a personal message and let me know your thoughts. I am going to accept your answer. – cole Oct 18 '15 at 21:32
  • @joern is it possible with WKWebView? I'm not getting success into it . . . – Naman Vaishnav Feb 26 '17 at 04:10
  • @NamanVaishnav The method using a `` element does not work with `WKWebView`. But you can still inject a CSS String. Please see my updated answer. – joern Feb 26 '17 at 12:52
  • This code works for me but only till iOS 10.2 , it's not working in iOS 10.3. Anyone is facing same issue ? or help me ? – Vivek Shah Jun 02 '17 at 11:18
  • @joern how to do it, if my html is having div element not head? – Mansuu.... Aug 02 '17 at 08:41
  • Do you load your HTML from a server or do you have it locally as a String? – joern Aug 02 '17 at 08:47
  • I get it from server @joern – Mansuu.... Aug 02 '17 at 09:03
  • @Mansuu.... You can load the HTML into a String (`String(contentsOf: URL)`), add the style tag with the CSS to that String and then load the WebView with that String (using `loadHTMLString(_:baseURL:)`). Just make sure to load the HTML into the String asynchronously to avoid blocking the UI. – joern Aug 02 '17 at 18:31
  • @joern I am trying to add css from external file added in to bundle resource(build phase) and adding like let mainbundle = Bundle.main.bundlePath let bundleURL = NSURL(fileURLWithPath: mainbundle) postTitleWebView.isHidden=false postTitleWebView.loadHTMLString(title!, baseURL:bundleURL as URL) but it not working – Mansuu.... Aug 03 '17 at 05:02
  • @joern I also tried the same as You suggested in this answer, but still not working – Mansuu.... Aug 03 '17 at 06:49
  • 1
    @Mansuu.... Try putting the contents of the CSS file (as a String) into your HTML string. Don't forget to embed the CSS into a Style element `` – joern Aug 03 '17 at 07:38
  • @joern so do I need to enclose my css content within style tag in css file? – Mansuu.... Aug 03 '17 at 08:06
  • @Mansuu.... Yes. You want 1 String containing the HTML AND the CSS (enclosed in a style element). Don't use an external file for the css. – joern Aug 03 '17 at 08:12
  • @joern But I get html from server and it may have different html tags every time so how can I make single string using my html and css? – Mansuu.... Aug 03 '17 at 08:22
  • @Mansuu.... please check my above comment where I explain how to load the HTML into a String etc. It doesn't matter if the server HTML changes. Just make 1 String by adding the CSS String to the HTML String. I am currently on the road and only have my phone on me so I cannot provide you with more detail. I hope you figure it out. Otherwise maybe open a new question for your problem if you have the feeling that this answer doesn't solve your problem. – joern Aug 03 '17 at 09:01
  • @joern Hi have tried your code but javascript is not working in WKWebview. I want to load css and js after WKWebview finish load. – Ekta Padaliya Aug 27 '17 at 12:18
  • @EktaPadaliya This question is only about inserting CSS into a WebView, but have a look at this answer regarding the injection of JS into a WKWebView: https://stackoverflow.com/a/40730365/4891259 – joern Aug 27 '17 at 16:17
  • @joern : can you please answer my question ? https://stackoverflow.com/questions/46321254/how-to-load-css-file-in-uiwebview – Nirav Sep 21 '17 at 09:56
  • @Nirav Have you tried the insertContentsOfCSSFile() approach in my answer? It should work for your case. – joern Sep 21 '17 at 18:06
  • 4
    I think webView:didFinish:navigation: isn't the best inject time. – 0xxxD Jan 05 '18 at 14:23
  • 3
    It works. But I have to wrap cssString into **single** line. The multi-line by `"""` doesn't work. – AechoLiu Mar 15 '19 at 09:20
  • @AechoLiu You can use a multi-line `"""` string if you surround the `style.innerHTML = '\(cssStyle)'` with back ticks `\``. So that part of code would then be `style.innerHTML = \`\(cssStyle)\`;`. This makes `\(cssStyle)` a multiline Javascript string matching the Swift multi-line string. – Rob Barber May 06 '20 at 16:39
  • 2
    Inserting `` also works in WKWebView if you set wkWebView to use: `wkWebView.loadHTMLString(html, baseURL: Bundle.main.resourceURL)` – Alain Stulz Feb 12 '21 at 10:02
29

Since UIWebView is deprecated in iOS 12, I'll only answer for WKWebView. I've implemented CSS loading like it was described in the accepted answer. The problem was that sometimes the transition from HTML with no CSS applied to HTML with CSS was visible.
I think a better approach is to use the WKUserScript to inject the CSS like this:

lazy var webView: WKWebView = {
    guard let path = Bundle.main.path(forResource: "style", ofType: "css") else {
        return WKWebView()
    }

    let cssString = try! String(contentsOfFile: path).components(separatedBy: .newlines).joined()
    let source = """
      var style = document.createElement('style');
      style.innerHTML = '\(cssString)';
      document.head.appendChild(style);
    """

    let userScript = WKUserScript(source: source,
                                  injectionTime: .atDocumentEnd,
                                  forMainFrameOnly: true)

    let userContentController = WKUserContentController()
    userContentController.addUserScript(userScript)

    let configuration = WKWebViewConfiguration()
    configuration.userContentController = userContentController

    let webView = WKWebView(frame: .zero,
                            configuration: configuration)
    return webView
}()

You can read more about this approach in this blog post.

Amer Hukic
  • 1,494
  • 1
  • 19
  • 29
4

Instead of applying css with style tag it's better to apply it with the link tag:

func insertContentsOfCSSFile2(into webView: WKWebView) {
    guard let path = Bundle.main.path(forResource: "resource", ofType: "css") else { return }                
    let csFile = "var head = document.getElementsByTagName('head')[0];var link = document.createElement('link'); link.rel = 'stylesheet';link.type = 'text/css';link.href = '\(path)';link.media = 'all';head.appendChild(link);"

    webView.evaluateJavaScript(csFile) {(result, error) in
        if let error = error {
            print(error)
        }
    }
}

I have tested it. it is working fine.

1

You need to add this header before apple style to HTML

let fontName = UIFont.systemFont(ofSize: 17.0).fontName
    let htmlContent = """
    <header>
    <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no'>
    </header>
    <style type='text/css'>
    img{max-height: 100%; min-height: 100%; height:auto; max-width: 100%; width:auto;margin-bottom:5px;}
    p{text-align:left|right|center; line-height: 180%; font-family: '\(fontName)'; font-size: 17px;}
    iframe{width:100%; height:250px;}
    </style> \(html)
    """
    webView.loadHTMLString(htmlContent, baseURL: Bundle.main.bundleURL)
Chea Sambath
  • 1,305
  • 2
  • 13
  • 16
1

I tried Amer Hukic answer. But did not work just the way it is. I added below code between my html head tags.

<link rel="stylesheet" href="style.css">
harun karaca
  • 134
  • 5
0

See @joern's accepted answer for more complete details. I'm adding this answer because I ran into a weird edge case. My particular use case needed to add styling to a div with a class="dialog". For some reason styling using .dialog and div weren't working though other types of styling were working. In the end I used the following to set the width of the dialog

let width = Int(webView.bounds.width)
let script = "document.getElementsByClassName(\"dialog\")[0].style.width = \"\(width)px\""
webView.evaluateJavaScript(script)
SuperGuyAbe
  • 525
  • 1
  • 5
  • 5