14

I have a UIWebView which displays some local text and images. Either I've made a mistake somewhere, or the UIWebView doesn't automatically handle the @2x suffix for high resolution images.

So, my question is has anyone else successfully loaded @2x images into a UIWebView through the normal means, or do I need to do something special? Can I somehow detect if my user has a retina display?

gcamp
  • 14,622
  • 4
  • 54
  • 85
Ben Williams
  • 4,695
  • 9
  • 47
  • 72

6 Answers6

15

As per your request...

Test for functionality. If you want to know if you need to display a retina image or a regular image, then test if your device has a retina display, not that it's of a particular model (future proof your application as best you can, means you have to change less stuff when a new model comes out). To do this, you can use the following sample code:

if([[UIScreen mainScreen] respondsToSelector:@selector(scale)] &&
   [[UIScreen mainScreen] scale] == 2.0)
{
    /* We have a retina display. */
}
else
{
    /* We do not. */
}

This code is safe, as of the time I wrote this, on all models and on all firmware versions. It will be safe on future versions as well until Apple deprecates the scale method, which may never happen.

More on your question, I don't know how to do that in a webview without having the locations to a retina image and a non-retina image beforehand. Once I have that info, I have (in the past) used it to replace some known sentinel text that the web page expected me to replace, as in I would put something in the HTML that had say: {{{image_location}}} where I could download the HTML data, get it into string format, then do a replace on that string replacing that text, with the URL of the @2x image if we're on a retina display, with the appropriate scale factor, or the normal sized image if not (using the above code).

Hope this helps if nobody comes along with a better solution.

Piotr Byzia
  • 3,363
  • 7
  • 42
  • 62
jer
  • 20,094
  • 5
  • 45
  • 69
  • Given that no better answers have come in, I'm accepting this as a workable solution. Ideally the UIWebView should handle this itself for local images, but checking for the devices screen scale and adjusting the HTML accordingly works. – Ben Williams Sep 19 '10 at 12:18
  • On an iPad at 2x mode, the scale is also 2.0. – occulus May 27 '11 at 15:09
  • Sure, but that relates to iPhone applications anyway. iPad apps don't run in scaled mode. Therefore, if you handle this case properly on your iPhone app, not an issue. :) – jer May 27 '11 at 15:28
  • So what happens on iPhone 6 when the scale goes up to 7.0? No more retina? :-P How about just getting the scale factor, then asking the (web or other) app for the appropriate images. – Jonny Oct 20 '11 at 07:30
5

I use this Javascript hack to load scaled images when running on a retina device. It will change the src and width attribute on all images to use the corresponding scaled image. Note that I have only tested this with local images.

Setting display: none and then reset it on image load seams to fix some flickering. Also note that this code is probably not compatible with other browsers than WebKit.

document.addEventListener("DOMContentLoaded", function() {
  var scale = window.devicePixelRatio;
  // might want this to be scale != 2
  if (scale == undefined || scale == 1) {
    return;
  }

  var re = /^(.*)(\..*)$/;
  var images = document.getElementsByTagName("img");
  for (var i =  0; i < images.length; i++) {
    var img = images[i];
    var groups = re.exec(img.src);
    if (groups == null) {
      continue;
    }

    img.style.setProperty("display", "none");
    img.addEventListener("load", function(event) {
      event.target.width /= scale;
      event.target.style.setProperty("display", "");
    });
    img.src = groups[1] + "@" + scale + "x" + groups[2];
  }
});

Tips is to add this to a file called e.g. scaledimages.js and then use

<script type="text/javascript" src="scaledimages.js"></script>

Make sure the js-file is listed in "Copy Bundle Resources" and not in "Compiled Sources" in your targets "Build phases". Default Xcode seams detect Javascript files as something it likes to compile. Also note that the current script might break things if devicePixelRatio happen to be 3 or greater in the future, a precaution might be to change the (scale == undefined || scale == 1) to scale != 2 only load @2x for now.

Update: There is also a jQuery-Retina-Display-Plugin that does something similar but requires you to set the width attribute and seams to also use a HEAD request to see if the image exist, not sure how that will work for local files.

Mattias Wadman
  • 11,172
  • 2
  • 42
  • 57
  • I don't why this was down voted. I've used this in an app myself and it works fine, at least with small HTML pages with not a lot of images. But i confess it is a ugly hack :) – Mattias Wadman Apr 16 '12 at 16:05
  • I'm sorry, it was me. I thought I Up voted it but accidentally must have pressed the Down button, and stackoverflow is not letting me change it anymore. Please update the answer, even if you have a add an extra space :) That'll let me change the status, i guess. I tested it myself, and it works like a charm. I also used it with small HTML pages. – Mustafa Apr 17 '12 at 04:57
  • :) I have made the same mistake myself. Try now. – Mattias Wadman Apr 17 '12 at 05:57
4

I supposed this can also be achieved by using some CSS or Javascript magic to display only the appropriate picture. The main idea is to insert the two pictures, normal and high resolution and then to set the CSS attribute display to none for the pictures to mask :

HTML file

<link href="default.css" rel="stylesheet" media="screen" type="text/css" />
<link href="highresolution.css" media="screen and (-webkit-min-device-pixel-ratio:2)" type="text/css" rel="stylesheet" />

...

<img src="image.png"    width="..."   height="..."    class="normalRes" />    
<img src="image@2x.png" width="..."   height="..."    class="highRes" />

CSS file : default.css

.normalRes { display:block } /* or anything else */
.highRes   { display:none  }

CSS file : highresolution.css

.normalRes { display:none  }
.highRes   { display:block }  /* or anything else */

I've test it a little bit and it worked well. Need more tests now. I've found the solution here : http://www.mobilexweb.com/blog/iphone4-ios4-detection-safari-viewport

UPDATE 2012-06-24

Here is another solution with no need for highresolution.css file.
Add the following code in default.css.

@media all and (-webkit-min-device-pixel-ratio: 2) {

   .normalRes { display:none  }
   .highRes   { display:block }

   ...
Dominique Vial
  • 3,729
  • 2
  • 25
  • 45
  • __width__ and __height__ attributes for __img__ tag must have the save values for normal and retina images. In others words, only __src__ filename and __class__ value change from normal image to retina image. – Dominique Vial Jun 24 '12 at 12:08
2

Just always do:

<img src="image@2x.png" width={half-width} />

for CSS referenced background images, use -webkit-background-size to half-size them.

Disadvantage: Non-retina devices will download oversized images.

Hafthor
  • 16,358
  • 9
  • 56
  • 65
1

The best solution is to use the high-res images for both retina and non-retina based WebViews. Then set the size of the image (in points) using the width and height attribute.

<img src="hi-res-icon.png" width="10px" height="10px" />
mc7h
  • 202
  • 2
  • 8
0

As for the detection, you can use this code sample. I’ve just updated it to also detect iPhone 4.

zoul
  • 102,279
  • 44
  • 260
  • 354
  • 1
    -1 for recommending code that tests for device, and not for functionality. Recommend checking that `UIScreen` responds to `scale`, and if so, call `scale` on `UIScreen`s `mainScreen` and check if its value is 2.0. If those conditions are true, retina display. – jer Sep 16 '10 at 06:22
  • Well in this case you can’t get a false positive, so that I would not hesitate. But the screen check is better, yes. – zoul Sep 16 '10 at 06:34
  • Undid the downvote for the acknowledgement above. Just to add one more thing, the reason I downvoted it is because these types of hardware checks work great for a moment in time, but in 5 months when Apple releases a new device (probably with a retina display), we have to edit our code to make it work there, whereas if we check scale, it works automagically. – jer Sep 16 '10 at 06:41
  • Thanks, but I'm still hoping someone comes through with a way to do this automatically in a UIWebView. Checking for the device is a clunky solution whichever way it's done. That said, @jer could you post your solution in another comment, so I can accept as the answer if that's the only good option I end up having? – Ben Williams Sep 16 '10 at 07:15