In an iPad app, I have a bunch of UIImageViews inside a bigger UIView. Each UIImageView contains a thumbnail that is generated in a separate thread (so as not to freeze the application). After thumbnail has been successfully generated I call setNeedsDisplay on main thread, however, it doesn't update the UIImageViews as the thumbs become available (I can see them in the log), rather it takes about 5 seconds and then displays all of them at once.
here's what I am doing when a thumbnail has been created in a separate thread:
[self performSelectorOnMainThread:#selector(setNeedsDisplay)
withObject:nil
waitUntilDone:NO];
any ideas?
What's your main thread up to? If your application waits until all the thumbnails are available before redrawing, then it sounds like maybe you are inadvertently blocking on your main thread until your thumbnailing queue has emptied. How are you setting up the threads?
The problem is that you can't tell cocoa that it have to redraw "now".
With setNeedsDisplay you can onely order a redraw, because drawing is ra rather expensive procces.
setNeedsDisplay
You can use this method or the
setNeedsDisplayInRect: to notify the
system that your view’s contents need
to be redrawn. This method makes a
note of the request and returns
immediately. The view is not actually
redrawn until the next drawing cycle,
at which point all invalidated views
are updated.
Mybe its a better and more performant solution to wait for all generated thumbnials?!
I would use NSNotification. Send a notification from your thread loading the images when an image is ready. Your view controller can observe these notifications and update the view as they arrive.
Related
I am working on an app that does image processing and displays the resulting image. Im using UIScrollView to let user scroll all images, because the image is not a standard jpg or png, it takes time to load. so i want to use a thread to load image, and then update the views.
For now, I use a timer in the background thread to check whether there are any images that are needed to be loaded. but it is not working so well. I want to know whether there is a way to notify the background thread when there are some images that are needed to be loaded in the main thread or any other suggestions?
Thanks in advance.
Provide a method in your class that controls the scrollView, lets call in 'processImage'. In your background thread, when you have an image, send it to the UI class as follows:
dispatch_async(dispatch_get_main_queue(), ^{ [uiClass processImage:theImage] } );
The background object should keep a weak reference to the uiClass (which is a delegate in this example). The idea is to do the image processing in the background, but provide it to the UI class on the main thread.
I'm trying to understand how things work in regards to concurrent programming and calling setNeedsDisplay. I basically have Three objects.
Main View - container with different UIView objects, the main one being a UIScrollView
Small Map View - a small UIView that draws a miniature version of one of the other UIView items on screem
Processor - a delegate of the Main View that calculates what's on screen and calls the Main View back with what's in view.
So a simple use case of what's going on is the user touches the ScrollView and then the processor updates what's in view of the scrollView (like calculating coordinates, center point, etc) It does this using blocks and does it asynchronously. This then posts a notification to the MainView object.
When the MainView receives the notification, it just calls
[smallMap setNeedsDisplay]; // example 1
I put some logs around this call, and I do see it gets called right away. However, the drawRect: of this function does not get called right away. It gets called after 2 seconds or so.
I remember reading that setNeedsDisplay just marks the view for redraw to happen on the next event of the run loop.
But if I add this code instead:
// example 2
dispatch_async(dispatch_get_main_queue(), ^{
[smallMap setNeedsDisplay];
});
My view gets redrawn right away.
I guess I'm confused as to why I have to ask for the main event loop to call setNeedsDisplay to immediately redraw something. Like in example 1, by me calling setNeedsDisplay, is that done in the background or something and that's why it doesn't get redrawn right away? I'm trying to understand the difference in what's going on behind the scenes so I know what to look for the in future. Like should I have all my calls that need to be immediately redrawn in something similar to the example 2 block? Or is it because I'm processing my data asynchronously that I need to then ask for the main queue? Thanks!
My guess is 1 of 2 things:
Your code that is running on a separate thread is calling your MainView methods from the separate thread instead of using performSelectorOnMainThread or a GCD call that invokes the code on the main thread. Thus your call to setNeedsDisplay is actually taking place on a background thread, which is a no-no, as the other poster said.
The second possibility is that your MainView code is running on the main thread, but it gets busy doing time-consuming processing, or waiting for a synchronous call to another thread to finish, and doesn't service the event loop.
You can rule out the first possibility by setting a breakpoint on your call to setNeedsDisplay and looking at the call trace in the debugger to see what thread it's running from.
Figuring out the second possibility will take a little more work. You might need to delve into instruments.
setNeedsDisplay is a UIKIT API call and has to be called from the main thread of the application, also known as the UI thread. That's why calling it in a background thread doesn't have any immediate effect and scheduling it on the main queue has immediate effects.
See this related question https://stackoverflow.com/a/6988115/172690 for a more detailed answer.
I know and heard most UI related shouldn't be done in thread other than main.
I also know that you can update non-view related data member(that you added) of UIView derived class.
I wonder if the below operations are fine or not to do in background thread.
allocing UIView
init UIView with/without(CGRectZero or just init) frame info
modifying frame/image(UIImageView's) property of UIView
modifying image property of NSObject derived class. (treating UIImage as data)
accessing subviews with subviews method
etc.. Is there a well defined documentation on this issue?
Thank you
You shouldn't be doing anything view related in a background thread. All of the items you listed should not be done in a background thread. If you're breaking your app up correctly for MVC, the view should only contain items that dictate how it is displayed. So anything relating to one should only be in the main thread.
All of your data manipulation should be residing in your model. It can be threaded as needed for performance. Just be careful that you send any messages to update the UI for the data manipulation on the main thread. This includes notifications. They gets sent on the same thread they were created on. So it's easy to forget to switch into mainThread when sending one.
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.
My app looks like this:
I have a UIScrollView and there is imageview inside. I implemented the scrollViewDidScroll delegate so that if the scroll did scroll, I will create a NSURLConnection and download a data from some url, for only once.
The problem is that the NSURLConnection connectionDidFinishLoading only be called or run after I release my finger on the scroll view.
To be more clearly, If I use my finger to scroll the scrollview, I confirm that the NSURLConnection is created, but as long as I do not release the scrollview, the connectionDidFinishLoading is never called. Once I release my finger, the delegate of connectionDidFinishLoading is called.
Anyone can explain this to me please?
Thanks
That's because during scrolling the UI enters a modal loop processing the user scroll inputs, and until scrolling finishes your thread won't get back to the message queue to pick up any delegate messages from the NSURLConnection.
You could run the NSURLConnection in a separate thread, which should keep pumping messages when the user is scrolling, but you'd still not be able to call across to the UI thread. The way round that might be to have the connection thread write the data into a lock-free data structure that the UI thread can then read - but this is likely to be complicated!