Get image Asynchronously and setting setNeedsDisplay - objective-c

This code is in in a cell initialization routine that sets up the elements of a custom cell. It gets the image from the web asynchronously. But I need it to redraw once it's done.
This is my snippet of code:
dispatch_async(myCustomQueue, ^{
//Look for the image in a repository, if it's not there
//load the image from the web (a slow process) and return it
mCover.image = [helperMethods imageManagerRequest:URL];
//Set the image to be redrawn in the next draw cycle
dispatch_async(dispatch_get_main_queue(), ^{
[mCover setNeedsDisplay];
});
});
But it doesn't redraw the UIImageView. I've tried to also redraw the entire cell, and that doesn't work either. Your help is much appreciated. I've been trying to fix this for some time!

Instead of setNeedsDisplay, you should set the image on main thread as Apple have mentioned in their documentation.
Note: For the most part, UIKit classes should be used only from an
application’s main thread. This is particularly true for classes
derived from UIResponder or that involve manipulating your
application’s user interface in any way.
This should fix your problem:
dispatch_async(myCustomQueue, ^{
//Look for the image in a repository, if it's not there
//load the image from the web (a slow process) and return it
UIImage *image = [helperMethods imageManagerRequest:URL];
//Set the image to be redrawn in the next draw cycle
dispatch_async(dispatch_get_main_queue(), ^{
mCover.image = image;
});
});

Related

Multiple GCD request in a scrollView

I have a scrollView which i load images into while scrolling,and every time it has only 3 pages with images(current previous next ) .
It works great ,and the memory consumption is low (30-40M) for the iPad .
Problem is, that when i scroll fast(no paging enable), i get a memory warning, even that the memory is low.
I discovered that this is because of multiple GCD requests at once .
What can be done, in order to eliminate this from happen ?
One solution i could think of, is to start loading only when scroll view is decelerating , but i am afraid that the UX will be bad with this(now it looks great) ,
Another one, is using NSOperation for this- which is a higher level and might be better ?
Another one, is to use AutoreleasePool for the thread request-which i saw in a few articles.
What is the best approach for this ?
Here is the loader, that happens while scrolling (to relevant pages):
[self downloadImageWithURL:userUrl completionBlock:^(BOOL succeeded, NSData *tdata)
{
if (succeeded)
{
//load back
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^
{
UIImage *image=[UIImage imageWithData:tdata scale:1];
if (image)
{
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
[image drawAtPoint:CGPointZero];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
dispatch_async(dispatch_get_main_queue(), ^
{
//LOAD EASY
[UIView transitionWithView:view
duration:0.1f
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^
{
view.image=nil;
view.image=image;
}
completion:nil];
});
image=nil;
});
}
}];
}
I found a great solution that works great for me, i have an array of all what is being loading right now, and i have checked when this array size causing troubles . When user scrolls i check its size, and when the size exceeded a certain size, i just stop the scroller from scrolling fast. This happens usually when the user is scrolling too fast.
CGRect frame=scroller.frame;
frame.origin.y=scroller.contentOffset.y-1.0;
if ([imagesAreLoading count]>10 )//too many requests has being made.
[scroller scrollRectToVisible:frame animated:YES];
Hope it helps other people.

Progressview not showing

Strange things are happening here. Im working on a project which has a tableviewcontroller. The headersview are from a custom class which add a progressview and a button. When the button is clicked the progressview shows the download status en removes itself from the screen when done. Here is the problem.
The progressview isn't showing nor is it showing its progress (blue color). When I set the backgroundcolor to black I do see a black bar with no progress. Even when I set the progress to a static 0.5f.
Im also working with revealapp which shows the progressviews frame but not the trackingbar.
So the progressview is physically there but just not visible. Does anybody know why?
Structure:
Header.m
init:
[[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]
layoutSubviews:
CGRectMake(x, x, x, x);
Then:
- (void)synchMedia
{
// add progressview to view
[self performSelectorInBackground:#selector(synchMediaThread) withObject:nil];
}
- (void)synchMediaThread
{
// Do all the heavy lifting
// set progressview progress
// loop this:
[self performSelectorOnMainThread:#selector(updateProgressView:) withObject:progress waitUntilDone:YES];
// finally
[self performSelectorOnMainThread:#selector(synchDone:) withObject:#(numFailed) waitUntilDone:YES];
}
When done:
- (void)synchDone {
// remove progressview
[tableview reloadData];
}
Have you downloading any large data? If so then in that case updation on UI get blocked as main thread get busy.
To check this just add NSLog statement in a function which is calculating the progress of downloading. in this case it will log the progress on console but not on UI. If you get the correct progress on console then it is sure that UI updation is blocking when you start downloading.
Try to use some delay to update the UI or
dispatch_async(dispatch_get_main_queue(), ^{
//do you code for UI updation
});
The problem was with the way the UIView was used in the tableview delegate.

How to auto scroll a WebView after DOM changes in Cocoa / WebKit

I'm building a small Mac Application that gets continuously supplied with data via a web socket. After processing each data segment, the data is displayed in a WebView. New data never replaces any data in the WebView instead new data is always appended to the WebView's content using DOM manipulations. The code sample below should give you an idea of what I'm doing.
DOMDocument *doc = self.webview.mainFrame.DOMDocument;
DOMHTMLElement *container = (DOMHTMLElement *)[doc createElement:#"div"];
NSString *html = [NSString stringWithFormat:#"... omitted for the sake of brevity ... "];
[container setInnerHTML:html];
[doc.body appendChild:container];
The rendering of the WebView apparently happens asynchronously. Is there a way to tell when the DOM manipulation finished and the content has been drawn? I want to use something like [webview scrollToEndOfDocument:self] to implement auto scrolling. Listening for DOM Events didn't help since they seem to be triggered when the DOM was modified but before these changes have been rendered. The code I'm using so far is similar to the following
[self.webview.mainFrame.DOMDocument addEventListener:#"DOMSubtreeModified" listener:self useCapture:NO];
in conjunction with
- (void)handleEvent:(DOMEvent *)event
{
[self.webview scrollToEndOfDocument:self];
}
The problem with this code is that the scrolling happens too early. I'm basically always one data segment behind. Can I register for a callback / notification of any kind that is triggered when the content was drawn?
Using timers
Auto scrolling can be implemented using an NSTimer. The challenge this solution bears is to figure out when to disable the timer to allow manual scrolling. I wasn't able to do the latter. Anyways, here is the code that enables autoscrolling using a timer:
self.WebViewAutoScrollTimer =
[NSTimer scheduledTimerWithTimeInterval:1.0/30.0
target:self
selector:#selector(scrollWebView:)
userInfo:nil
repeats:YES];
scrollWebView: simply being a method that calls scrollToEndOfDocument: on the web view.
Using notifications
Listing to NSViewFrameDidChangeNotification emitted by the web view allows to only scroll in the event of frame size changes. These changes occur when new content is added but also when the size of the encapsulating view changes, e.g. the window is resized. My implementation does not distinguish between those two scenarios.
NSClipView *contentView = self.webView.enclosingScrollView.contentView;
[contentView setPostsFrameChangedNotifications:YES];
[[NSNotificationCenter defaultCenter] addObserverForName:NSViewFrameDidChangeNotification
object:contentView
queue:nil
usingBlock:^(NSNotification *notification) {
[self.webView scrollToEndOfDocument:self];
}];
Note: It is important that you instruct the content view of the web view's scroll view – think about this a couple of times and it will start to make sense – to post notifications when its frame size changes because NSView instances do not do this by default. This is accomplished using the first two lines of code in the example above.

How to asynchronous reload the UIImage in one UITableView cell/row?

in the UITableView that have some cells, and each cell have tow UIImage, and i will update the UIImage asynch for performance. but there is a question, how can i only reload the UIImageView in cell when a image pulled from the Web.
i kown can reload a whole cell with methoed "reloadRowsAtIndexPaths", but i just want to kown is there have a method can only reload the UIView object which is necessary to be reload (i.e. a image or a label) in the cell.
BTW, i found the methoed "reloadRowsAtIndexPaths" will execute the method "heightForRowAtIndexPath" for all cells when i just reload one cell. is there anything wrong?
Maybe the sample code LazyTableImages from apple is what you want.
Get a reference to your custom cell calling tableView:cellForRowAtIndexPath:. Then update its image property running this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:someURL];
dispatch_async(dispatch_get_main_queue(), ^{
cell.someImage = [UIImage imageWithData:imageData];
});
});
You'll be loading at most 6 images concurrently because that's the limit of NSURLConnection.

How to fade picture loading process with an UIActivityIndicatorView?

I have created a custom UITableViewCell. My custom cell contents an ImageView for picture of current article. On creation of the cell image will be loaded from Internet and displayed in the UIImageView. How can I fade then downloading process with an ActivityIndicatorView?
I'm sorry for this bad English :-) I use an online translator.
If I'm understanding you correctly, it seems like you want your UIImageView's (that are lazily downloaded in realtime) to fade in once they have fully downloaded. And while they are downloading, display the spinning UIActivityIndicatorView wheel.
Here is what I suggest:
1) Instead of defining the custom view in your table cell as a UIImageView specifically, just use the more generic UIView. This is because both classes (UIImageView and UIActivityIndicatorView) are subclasses and can be set as such.
2) Initially, for any and all cells, set the UIView to the UIActivityIndicatorView (don't forget to use "startAnimating" to get it to spin) and then on the callback function for the download completion, go to the appropriate cell and set that custom UIView to the downloaded UIImageView.
3) To achieve the fade in effect, look at the following code:
// Sets the image completely transparent
imageView.alpha = 0.0f;
// Animates the image to fade in fully over 1.0 second
[UIView animationWithDuration:1.0f animations:^{
imageView.alpha = 1.0f;
}];
4) You might need to call "setNeedsDisplay" on the table cell to refresh it's subviews after setting the new image view, and before animating it.