I am trying to capture the contents of a UIWebView including that which is not visible to the user. i.e. The whole web page even though the user is only looking at the top.
Looking around I found the best way to capture a UIView is to retrieve its layer and use renderInContext.
However, UIWebView seems to be using its own CALayer implementation which is behaving a lot more like CATiledLayer, although it is still claiming to be a standard CALayer. When I call renderInContext I only get one portion of the web page, up to 940px down, as opposed to the whole page.
Has anyone got any ideas on how to either: force the UIWebView to scroll down another 940px (obviously that is far from ideal) or tell whatever breed of CALayer it is backing the WebView to render all of its content when I ask it to.
Cheers
EDIT: I should add that currently change the frame of the webview to fit the size of the page retrieved through javascript.
I've released an app (Web2Pic) doing that, and please trust me that UIGraphicsBeginImageContext(webView.frame.size);
can do nothing except getting a small image from the visible area in our UIWebView ;-(
The right way is a bit complex but it just works:
1.Use JavaScript in our UIWebView to get these float values:
//Whole page size in HTML coordinate
document.body.scrollWidth
document.body.scrollHeight
//UIWebView visible size in HTML coordinate
window.innerWidth
window.innerHeight
2.Now we can 'cut' the whole page into dozens of UIWebView-sized small pieces. Then we can capture every small pieces individually and save them into our Cache. I implemented this by calculating page-offsets and use UIGraphicsBeginImageContext(webView.frame.size); to get a array of images. In addition, you should cache the image array into the file system, or the app will eventually crash!
3.When we finally got all the small pieces, we can start a full-resolution context: UIGraphicsBeginImageContext(CGSizeMake(document.body.scrollWidth,document.body.scrollHeight));
4.Render every small images into the big context based on the coordinates. And be careful to the corners, the last image in every line/row may not be a full image.
5.There is still one step left: saving the big image. Do not save it into the PhotoAlbum, because iOS will automatically cut down the resolution of images in the album. Instead, we can save it into the file system and enable the app's iTunes File Share support, or even write a simple in-app photo manager.
Hope these can help ;-)
Yichao Peak Ji
It looks to me like UIWebView renders on demand (witness the checkerboard as you scroll downwards rapidly on a large page), so there won't be anything in the part of the layer below what you can reach until the user scrolls down there. The layer won't be able to write out what it doesn't have, so I don't think you'll be able to capture the whole area.
As far as scrolling, there aren't any obvious exposed API methods that I can think of to move it down. The web view seems to host a UIScrollView or something similar, which you could try to access by traversing the view hierarchy and use its scrollRectToVisible:animated:, but that doesn't sound like a good long-term solution.
Just to answer one of my questions, you can scroll the view by using javascript.
So you use stringByEvaluatingJavaScriptFromString: with a javascript of window.scrollTo(0,%i);, where %i is where you want to scroll to. You can also use scrollY to retrieve the current scroll value.
Resizing the Webview is the way I solved it. If you want a bigger image with more detail, you just have to zoom in before calling this method. Beware that large dimensions could infulence the performance.
- (UIImage*) imageFromWebview:(UIWebView*) webview{
//store the original framesize to put it back after the snapshot
CGRect originalFrame = webview.frame;
//get the width and height of webpage using js (you might need to use another call, this doesn't work always)
int webViewHeight = [[webview stringByEvaluatingJavaScriptFromString:#"document.body.scrollHeight;"] integerValue];
int webViewWidth = [[webview stringByEvaluatingJavaScriptFromString:#"document.body.scrollWidth;"] integerValue];
//set the webview's frames to match the size of the page
[webview setFrame:CGRectMake(0, 0, webViewWidth, webViewHeight)];
//make the snapshot
UIGraphicsBeginImageContextWithOptions(webview.frame.size, false, 0.0);
[webview.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//set the webview's frame to the original size
[webview setFrame:originalFrame];
//and VOILA :)
return image;
}
All UIViews have a size limit of 1024x1024, so you will probably have programmatically scroll, capture the page in chunks, and stitch them together somehow.
Set the height of webview equal to the contentsize of scrollview of webview.
-(UIImage *)captureImage:(UIWebView *)webvw
{
webvw.frame=CGRectMake(webvw.frame.origin.x, webvw.frame.origin.y, webvw.frame.size.width, webvw.scrollView.contentSize.height);
UIGraphicsBeginImageContextWithOptions(webvw.frame.size, NO, 0.0f);
[webvw.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
return screenshot;
}
Related
Environment:
I am creating a "photo mosaic" app, and I try to display 1024(32*32) pieces of small images(retina size->w:30px h:20px) on the screen same time. Which means on total, it is the same size as the full screen image size.
Issue:
I load 1024 UIImages, create 1024 UIImageViews, and add all of them to a UIView. When I scroll to this view, there is a big lag: test on iPhone4(iOS 5) and iPhone5(iOS 6). It's just appear on iPhone4, and on iPhone5 is fine. (Supposing iPhone5 have much more better CPU, so I think it is reasonable).
What I think:
Supposing all images have been already loaded from local dir in the memory(using method "imageNamed"), so I think the problem must be in the somewhere of the step display/render the images.
So any idea about it? Any, any idea will be helpful.
Thanks so much,
UPDATE
It is much better after I took the advice from #Antwan van Houdt . Here is the principle code:
-(void)updateCoverImageView:(UIImageView *)smallImage{
UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, 0.0f);
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self.coverImageView.layer renderInContext:ctx];
[smallImage.image drawInRect:smallImage.frame];
self.coverImageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
Then you just set the alpha value of smallImage to zero, so the system won't render them. And the cover image will replace it. That works for the lag issue caused by displaying a large amount of UIView same time.
Is every image in its own view?
In either case, for such a huge amount of images you should probably consider drawing them in 1 view and if that drawing method is still too slow you could draw it once, render the view to an image and then simply draw that image as it's cached display.
There are several guides available from apple that deal with the performance of custom view drawing and stuff like it.. you should give them a read.
Not knowing the full details of your code, any thing like this, should be done on a background thread. I would read up on graphics contexts, grand central dispatch and then use both those concepts to create the resulting image in another thread and then get the resulting image and display it in the main thread.
Infact since you have many images which can all be processed in parallel, it can be done very fast using GCD.
If you can post the code, I can help you optimize it.
I have a problem with stretchable images in my tableView.
Until Today I used static backgroundimages with 320px width.
But as I want to support landscape too, I thought I switch to stretchable images instead of using a seperate png file.
The first thing I noticed, was that the scrolling performance was really bad.
I didn't change anything else.
After that I started some time measuring, and removed everything from the drawing routine beside the stretchable image.
The "drawContentView" method needs factor 10 of the time, compared to using a non-stretched image with the same size of as the backroundRect.
With stretching the lags are easy to see. Without it scolls like a charm.
static UIImage *greyBackground = nil;
+ (void)initialize
{
greyBackground = [[[UIImage imageNamed:#"back_gray.png"]stretchableImageWithLeftCapWidth:65.0 topCapHeight:0.0]retain];
}
- (void)drawContentView:(CGRect)r {
CGRect backgroundRect = contentView.bounds;
[greyBackground drawInRect:backgroundRect];
}
Is the performance of this really that bad, or is something wrong here?
Tested on an iPhone 4, so should be powerful enough normally. :-/
I already thought about caching the correct sized image instead of the stretchable,
and recreate it with the new size after screen rotation.
But I know a lot app that use cell background images and different cell heights (e.g. Twitter a.k.a. Tweetie) and still scrolls amazing fast.
So, should it normally work better, or is stretchableImageWithLeftCapWidth something I better avoid?
I found a solution to solve the problem but still can't believe that this should be normal behavior of stretchableImageWithLeftCap:...
I sliced my backgroundPicture in 3 parts now.
- leftCap
- middlePart (1px width)
- rightCap
Left and right caps are drawn with drawAtPoint, middlePart ist drawn with drawAsPatternInRect.
Drawing this 3 cached images is more or less as fast as my original, fullsize backgroundImage was.
But I always thought stretchableImageWithLeftCap would do the same: Drawing 2 Caps and and 1px patterns between.
No idea why it's so slow for me.
I am trying to improve scrolling performance of a UITableView. I am loading the images using imageNamed: and dispatch_async, but scrolling is very good once the images have been loaded into the cells. I would like to fade in the images only if they are not in the system cache to reduce the jarring effect of the images "popping" into view.
Is there a way to know if an image is already in the system cache?
There is no documented way to look inside the UIImage to check such things.
I think the only way to know for sure that the image is available immediately, is to force the UIImage to be loaded. This can be done in the background, by creating the UIImage and accessing it's pixels, using CGImage functions. If you ensure that there is no rescaling needed (i.e. don't put a 3000x2000 image in a 30x20 space) then it should display without a glitch.
if I change the frame of a UIWebView (scalesPageToFit property is YES), what do I have to do that the zooming level of a currently displayed webpage persists?
Let's say I have a UIWebView frame with a width of 200 pixels, and has zoomed into a website so that only one column is visible. After changing the width to 300, I still see the column with the same size, and additional space at the left and right. But what I would need is that I still only see this column, but bigger.
Any ideas what I have to do to achive this? I tried a lot of things, but nothing worked so far.
By the way, the iPhone built in Safari browser does exactly this thing (with the same website, so it's not content related) when rotating the iPhone... I see the same content, bug bigger, NOT more content as it happens with my current version of code.
Thanks for helping!
Markus
EDIT: Better answer:
mjdth is correct, you can use the contentMode property on the UIWebView. Very simple:
webView.contentMode = UIViewContentModeRedraw;
Old, crap answer:
This worked for me, but I was working with local pages and small amounts of content:
-(void)layoutSubviews
{
// Cause web view to reload content in order to display at new frame size
[webView reload];
}
I'd love to see a better solution that doesn't involve reloading the page though!
What do you have set for the webview's UIViewContentMode (can also be set in IB under the View section's "Mode")? Since you're resizing the view it may have something to do with this. Just a shot in the dark since I haven't tried it but hopefully this helps.
You may also want to try turning off scalesPageToFit before the transition and turning it back on after.
I want to show some images as a thumbnail in View controller but i dont know how to do this. Can anyone please give me some ideas or sample code if possible.
Are you asking how to create thumbnails from larger images, or about how to build a view controller which displays them nicely?
Building a view controller:
You could use TTPhotoViewController from the Three20 project (description), which acts similarly to the iPhone's built in camera roll to view images.
You can look at the Scrolling sample code from apple, referred to in this question about using it for thumbnails.
If you want to build one yourself, you might consider using a GridView from the moriarty library, either as a large grid in a UIScrollView, or on a more efficient row-by-row basis in a UITableView. There's a previous question on optimized image loading in a UIScrollView.
Creating thumbnails from larger images:
The code-easiest way to scale down an image is to simply display it in a UIImageView with a frame set to the smaller size that you want - it's scaled for you.
If you want to save a thumbnail, or care about memory usage, you can create a hard-scaled version. The sample code below is taken from this blog post, and can be added to UIImage as a category method:
- (UIImage*) imageScaledToSize: (CGSize) newSize {
UIGraphicsBeginImageContext(newSize);
[self drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
Finally, here's a previous question on masking out round corners on an image, similar to the app icons used on the home screen.
Added
Using an image's built-in thumbnail:
There's a nice function called CGImageSourceCreateThumbnailAtIndex that understands built-in thumbnails in certain image data formats. You can see some useful sample code for this under Creating a Thumbnail Image from an Image Source in the Image I/O Programming Guide from Apple.
There are multiple issues with thumbnails; perhaps you could clarify which ones you are most concerned about?
How to display a smaller version of an existing image
How to speed up paging by caching the thumbnails (instead of just dynamically shrinking the originals)
How to allow the user to page through the thumbnails
How to synchronize the thumbnails with the originals, in the event that your images are editable or the user can add to them