UIWebView: Tracking screen updates (dirty regions) - iphone

I'm trying to detect animations and other screen updates as they happen inside a UIWebView. I would like to get the rectangles of areas in the UIWebView that have been modified since the last refresh.
I think really what I'm looking for is for a way to "trap" the calls that UIWebView makes to setNeedsDisplayInRect. Is there a way to do that? Can I somehow subclass UIWebView's underlying CALayer object in a way that would allow me to catch those calls as they come in from UIWebView?

There's no good way of doing that. You can try grabbing a snapshot of the UIWebView's CALayer and comparing it to the previous snapshot, but I've had a lot of trouble getting reliable snapshots of UIWebViews.

Use an Objective-C category (#implementation CALayer (MyCALayer)) - like you're already doing based on your update - to trap the calls going from UIWebView to CALayer.
Then, use Method Swizzling to relay your category overrides to the original CALayer object.

Related

On iOS, if a subclass of CALayer is used with an empty display method, then app seems to go into infinite loop?

If a Single View app is created, with a FooView that subclasses UIView, and do a
NSLog(#"hello");
in drawRect, then it is printed.
And if I create a subclass of CALayer called CoolLayer, and add this method to FooView.m:
+(Class) layerClass {
return [CoolLayer class];
}
and at the end of FooView.m's drawRect, do a
NSLog(#"layer's class is %#", self.layer.class);
then CoolLayer is printed. So now the view's underlaying layer is CoolLayer.
But when the following is added to CoolLayer.m:
-(void) display {
}
which is the method that is automatically called to redraw the layer (similar to drawRect), then no NSLog whatsoever was printed. It might be that the app went into an infinite loop. (even my touchesBegan that prints out NSLog messages is not printing). If a breakpoint is set at display, it will stop there once but when I continue the program, it will never arrive at display again. What is wrong with this and how can it be fixed?
The layer's display method will not be called again unless the layer is dirty, i.e. set to need a redisplay. This is usually a good thing, and is why you don't see the method being called more than once.
Also, the normal implementation of display will call the drawInContext: method. Since you override this in your subclass, the drawRect: method of the view is never called. You need to either replicate the standard behavior of CALayer, or call the superclass' display method in your own implementation.
That does not sound like an infinite loop. If you were in an infinite loop your app would freeze, and after a few seconds the springboard app would kill it for being unresponsive.
Call setNeedsDisplay or setNeedsDisplayInRect on your layer to make it "dirty" and require drawing again. Note that you don't want to call setNeedsDisplay any more than you have to, because it takes a lot of work to re-render the layer and push it's contents onto the screen. Only display when something has changed
You're not seeing an infinite loop here. If you were, as Duncan points out, you'd eventually crash either due to the watchdog timer or from an infinite recursion that would be immediately obvious in the stack trace you'd see in the debugger.
If you put an NSLog into your UIView's -drawRect: method, then override its default layer class with your own custom CALayer that does its own drawing, your UIView's -drawRect: would no longer be called. Drawing would now be handled by your custom backing layer.
As described in the "Providing CALayer Content by Subclassing" subsection of the Core Animation Programming Guide, you typically override -display in your CALayer if you want to somehow customize the contents CGImageRef for your layer. Normally, you'll be overriding -drawInContext: if you want to render custom Quartz drawing within your CALayer. Making your overridden -display method totally blank, and not writing anything to the contents property, is not standard behavior, so I'm not surprised that you're seeing odd results from doing that.
Based on your series of recent questions, I highly recommend you stop and spend some time reading the Core Animation Programming Guide and looking at some sample code involving CALayers before proceeding further.

loading image asynchronously with spinner at the back

I see many apps which have an image frame that loads the image asynchronously and while the image is not loaded yet, it shows either a spinner at the back or a default image. Is there a library for doing that? If yes then what is it? And please no three20 (I've had a bad experience with it). If not then how do you create one.
The spinner is called UIActivityIndicatorView, which is a subclass of UIView, and as such, can be placed anywhere on the screen.
It has startAnimating and stopAnimating methods, which begin and end the "spin".
So create the UIActivityIndicatorView, place it with addSubview where you want, and call startAnimating (this, as UI activity, has to be done on the main thread). Then call the image loading code asynchronously, and when the image finishes loading, call stopAnimating on the spinner, put the loaded image on top of it, and then remove the spinner with removeFromSuperview.
AFAIK, there is no built-in Cocoa API to do this in a call or two (which sounds like what you want).
I personally set up a singleton image cache manager that uses the built-in NSURLConnection class to load the images, save them to the filesystem, then call delegate methods on the requesting class (which includes a little subclass of UIImageView) to hand it the loaded image data.
People on SO report success with ASIHTTPRequest as well, for similar tasks. Worth checking out, I imagine (though I don't use it).

Force redraw with CADisplayLink

I'm currently using CADisplayLink to show an OpenGL animation which works great. Sometimes, however a parameter changes and I need to redraw the view immediately and can't wait until the next frame is requested by CADisplayLink. If I don't do that, I get one frame wrong which looks really bad in my case.
So, how can I force a redraw of an EAGLView without interfering with the CADisplayLink stuff?
If your CADisplayLink is calling method drawFrame, for example, then just call drawFrame yourself when you need to. No reason you need to wait for CADisplayLink if you don't want to.
Your question suggests that you're storing your data in your view rather than in a data object. You should be able to change your data at any time, and your view should update when its needed to display. Move the data to a model object, and have the EAGLView draw itself based on the data when requested from the CADisplayLink rather than redrawing itself when the data changes.

Create a UIImage by rendering UIWebView on a background thread - iPhone

Does someone know of a way, or has a creative idea as to how to obtain an UIImage that is rendered from a UIWebView? The catch is, that is must be on a background thread.
I'll elaborate:
I'm trying to obtain the image from multiple UIWebViews every second+-, and display it on screen (iPhone's of course). Because rendering a layer to a CGContext is a CPU consuming job, I wouldn't like to use the main thread, so not to hang the UI.
My attempts so far were:
I tried to use renderInContext on the webView's layer to the UIGraphicalContext, but the _WebTryThreadLock error crashed the webviews.
I tried creating a CGBitmapContext, and render the webview's layer to it, but got the same result.
I tried implementing a copy method (by adding a Category) to CALayer, that deep copied all of the public properties, and sublayers. Afterwards, I tried to renderInContext the layer I copied. I got a UIImage that was partially "correct" - meaning, not all of the layers were rendered, so for example, i would get only the website header (or footer, or body, or search bar, or just a some of the frames). The UIWebview's layer consists of all sort of subclassed CALayers, so this is probably why this approached didn't work.
I tried setting the kCATransactionDisableActions in a CATransaction, but it didn't appear to change this behavior (neither of them).
I'm pretty close to giving up.
Is there a savior among you people?
UIWebView hates, and I mean really hates, having anything done to it on a background thread. UIKit is not fully thread safe. Drawing to a graphics context is (this was added in iOS 4), but not creating UIViews on a secondary thread.
Are you creating your UIWebViews off the main thread? Do you perhaps have some code to share? I would suspect your issues are being caused by the fact you're trying to perform operations to a UIWebView on a secondary thread. The drawing operation to render the view's contents as an image can happen off the main thread, but creating the view itself can't.
I've been working on this, too.
In a thread I create a view hierarchy, loop until the web-view has finished loading content.
The webview I create is inside of a UIViewController's viewdidload-- I've tried doing
if ([NSThread isMainThread] == NO) {[self performselectorOnMainThread: #selector(viewDidLoad)return;)}
And I've done the same for dealloc'ing the webView.
But that didn't work.. I've only found that we avoid UIWebView exceptions UNTIL we hit the autorelease pool...
I'm using instruments to figure out why.
Here's my attack strategy...
I'm going to perform the render operation on the main thread with an off-screen view, having a separate thread running some sort of queue to manage them. I'm worried about UI lag, so it'll have to be fairly efficient.

NSOperation + Objective-C Categories = Bad Idea?

I've set up an Objective-C category for an iPhone app's UIImageView class. The category's mission is to help load URL-based images asynchronously with memory/disk caching.
Now, in UIImageView+Cache.m I have access to an NSOperationQueue so I can kick off a loading thread. I create an NSOperation-derived object, initialized with the image URL and the target UIImageView, and a selector to perform on the target once the operation is complete. In the selector method, we set our freshly-loaded image (or, if not found, we set an alternate placeholder image), and we're done!
This works fine, until a UIImageView happens to be removed before the NSOperation completes. For instance, I have a previous/next segmented control in my UI that causes these UIImageViews to be removed and added anew (they're part of a larger "item" that is being viewed in the app), so it's very easy to tap these in rapid succession.
So if you decide to start tapping away before all the images are loaded - KABLAM! Unhappy thread has an invalid object and doesn't know it. :(
The closest thing I can find to help mitigate this is NSOperation's cancel and isCancelled methods, except you can't keep track of which operation object to cancel within a Category, because - if I understand correctly - Categories can't add IVARs to objects!
Maybe that means Categories aren't a good idea here? (Whines: "But I liiiiike Categories! Waaah!")
Advisement appreciated!
I probably wouldn't use a category for this situation. Categories are useful, but are usually unnecessary. I'd only use a category if you have a really good reason to. What exactly are you putting in the category?
I think you could implement the whole thing in the NSOperation subclass, which would be the best solution. Put a retain on the image view so it doesn't get deallocated before the image is downloaded, and cancel the download if the view is not visible anymore. If that's not possible, then subclass UIImageView instead of using a category.
I would say there is no harm in moving this into your own UIImageView subclass. Sure, you may like categories - but if they don't do the job then why hesitate in moving to a design that does?
Are you retaining the UIImageView by the NSOperation? Otherwise the imageView might be freed before the NSOperation completes, leading to kablooi central. You should do a retain and then, once you've done the setImage, do a release.