UIScrollView retrieve data from the server one page at a time - iphone

I have a UIScrollView which scrolls horizontally, I have lot of data to be displayed on that UIScrollView, when i create the uiscrollview I know exactly the size of the content that I would like to display so I create the frame accordingly.
I get the data from the server that i have to populate in the uiscrollview. I would like to retrieve one page of data at a time from the server, also I would like to retrieve the second page only when user has scrolled through the end of the first page, that way I will avoid pulling unnecessary data from the server.
Any suggestions on how to do this?

You should use the protocoled method - (void)scrollViewDidScroll:(UIScrollView *)scrollView;
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.y == _currentOffsetHeight)
{
// Do what you want
}
}

See: iOS Developer Library: PageControl Sample Code
When you detect scrolls via the delegate methods, you can start loading data for the next views into memory. Also, be sure to keep the previously viewed view in memory. So for example (if the 5th view is on the screen):
0: not in memory
1: not in memory
2: not in memory
3: not in memory
4: in memory
5: in memory
6: in memory
7: not in memory
8: not in memory
Once you detect the user has arrived at page 5, for example, you start loading the next page. You might even want to consider loading +2 pages ahead, in case the scrolling occurs faster, or the user scrolls past a view. The scrollview also bounces, so if you're missing content on one side it might look bad.
Good luck!

Related

How do you know when the UITableView is done updating it's view?

So I implement UITableViewDataSource protocol using an NSFetchedResultsController.
I then modify the contents of the Core Data base and the NSFetchedResultsController then update the tableView..
Is there anyway to know when the tableView has done reloading the data?
We have a complex data model that is caching against a REST implementation, and it's difficult to determine if we need to get more data to fill up the screen (because the screen might be using complex filters against the raw data loaded). Also, the UITableViewCell objects are NOT guaranteed to be the same height.
The easy answer was to simply dump the data to core data, and use an NSFetchedResultsController to serve the data to the UITableView.
Here is how it works:
We added a tableview.tableFooterView that displays our "data loading" message.
Loading up a batch of data from the REST API and then using it update our Core Data objects.
This triggers the controllerDidChangeContent method to trigger, which then triggers a tableView reloadData (eg:)
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView reloadData];
}
We then basically then "Check" to see if the Footer is still visible 250ms later by calling a method like:
[self performSelector:#selector(checkIfFooterViewIsVisible) withObject:nil afterDelay:0.25];
- (void)checkIfFooterViewIsVisible
{
BOOL viewVisible = CGRectIntersectsRect(self.tableView.bounds,self.tableView.tableFooterView.frame);
if (viewVisible)
{
[self getMoreData];
}
We also check to see if the Footer is in view everytime we scroll.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self checkIfFooterViewIsVisible];
}
If we load the "last batch" of data, we actually just remove the tableViewFooter view object from the table (so it can't ever be visible).
So the answer is cool, because we can figure out if we need to pull more data, only if the user "needs" it. Either because there wasn't much visible data in the first batch, or because they have scrolled down and want more data. Checking the current placement of the tableFooterView lets us know if we have "painted" enough data on the screen.
The PROBLEM is - can I get rid of the:
[self performSelector:#selector(checkIfFooterViewIsVisible) withObject:nil afterDelay:0.25];
If we set the delay too fast, then UITableView doesn't have time to update the screen (and change the footer's position). If we update too slowly, then it feels like the app "stutters" as it loads data, and can load data slower than it could. But different iOS devices (and different network coverage) are going to mean slightly different timings, so the "let the UITableView update and check a bit later" works most of the time, but I feel like this could work smoother.
Is there anyway (maybe by overloading UITableView) that we can determine that the UITableView is "done" loading UITableViewCell objects? (at least until the next time the scroll moves?). Adding in this weird delay works well, but it would work cleaner if we knew definitively that the [tableview reloadDate] operation has completed.

Preloading pages of UIPageViewController

I can't find a good solution to having the UIPageViewController preload the surrounding ViewControllers.
Right now what happens is when the user starts to turn the page, nothing happens, the code calls
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
and once its loaded thennnn it shows the animation. This takes too long and is not smooth.
Anyone have a solution?
Create a mutable container and when you show the first view, create the two viewController that would be needed if the user pages, once you have the viewController object ask it for its view (to get it to load the nib and call the controller's "viewDidLoad:" method. You need to figure out a system on identifying the viewControlers so you can retrieve the viewController you need). You might be able to do the heavy lifting here in a block on a dispatch_queue.
From then on, when the user pages, you look first in the container for the viewController, and if not found, you have to do it in real time.
You probably want to use a dispatch_group so that you can wait on it for pending blocks to finish before paging.
Everytime the user pages, you will look and see if the pages surrounding that page are in the container or not. You could also pre-fetch more viewControllers - like two forward two reverse at each page shown.

"Infinite UIScrollView" containing UIWebViews

The background:
First of all, the background. I've created an "infinite scrollview" by using an UIScrollView with the width of three pages (0, 1 and 2) and then made sure that the UIScrollView always is centered on page 1 when not being scrolled upon. When you scroll to either page 0 or page 2 and the UIScrollViewDelegate senses that the scrolling ended, the content of all the three pages first switches in the direction that you've scrolled and then the UIScrollView instantly moves back to page 1, simulating the effect that you can continue to scroll through an endless stream of pages.
At first, I used a UITextView to display the text content of each page, but pretty soon wanted something more flexible when it comes to styling the text. I therefore ended up using a UIWebView in which I load an NSString containing the styled HTML content I wish to display. This worked perfectly, just the way I wanted it to look.
The problem:
Naturally, it takes longer time to load, parse and show a simple HTML page in a UIWebView than it takes to just load a static text in a UITextView. Because of this, when you've scrolled in whatever direction, and the scroll view automatically moves back and switches the content of the pages, you can hint the old content for a tenth of a second before the new one shows.
My solution (that did not work):
I searched for the UIWebViewDelegate in the documentation and found that there's an event when the page is done loading. I hooked this up to do this:
User scrolls from page 1 to page 2.
UIScrollViewDelegate calls scrollViewDidEndDecelerating, telling page 1 to switch it's content to the same content currently being displayed at page 2.
UIWebViewDelegate of page 1 calls webViewDidFinishLoad, telling the UIScrollView to move back to page 1 and then switch the content of page 0 and 2.
In my head this would fix this problem, but apparently it's still not enough. Therefore, I ask you for help. Any ideas? Might be worth mentioning that I've had NSLog in all the methods and made sure that they're being called. If there's anything else, I'd be glad to answer any questions you have.
Thanks in advance!
View Controller containing UIScrollView:
// Sense when the UIScrollView stops accelerating
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender {
if(pScrollView.contentOffset.x > pScrollView.frame.size.width) {
[self switchToNextPost];
} else if (pScrollView.contentOffset.x < pScrollView.frame.size.width) {
[self switchToPreviousPost];
}
}
// Update the currentPost variable and switch page 1
- (void)switchToNextPost {
[prefs setInteger:[[self getNextKey:nil] intValue] forKey:#"currentPost"];
[self loadPostWithKey:[self getCurrentKey] atPage:[NSNumber numberWithInt:1] withViewController:currentPage];
}
// Update the currentPost variable and switch page 1
- (void)switchToPreviousPost {
[prefs setInteger:[[self getPreviousKey:nil] intValue] forKey:#"currentPost"];
[self loadPostWithKey:[self getCurrentKey] atPage:[NSNumber numberWithInt:1] withViewController:currentPage];
}
// Custom delegate function. Scrolls UIScrollView to page 1 and then loads new content on page 1 and 3
- (void)webViewFinishedLoading {
[pScrollView scrollRectToVisible:CGRectMake(pScrollView.frame.size.width, 0, pScrollView.frame.size.width, pScrollView.frame.size.height) animated:NO];
[self loadPostWithKey:[self getPreviousKey:nil] atPage:[NSNumber numberWithInt:0] withViewController:previousPage];
[self loadPostWithKey:[self getNextKey:nil] atPage:[NSNumber numberWithInt:2] withViewController:nextPage];
}
View Controller for the subviews being displayed as separate pages in the UIScrollView
// The delegate shown in previous code
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[[self delegate] webViewFinishedLoading];
}
what you can do , i think, is
populate all three pages with relevant content from the beginning.. (you are probably doing that already)...
when scrolling starts, the next (or previous) page shows up as you scroll..
when scrolling finishes, move the scroll content view to center as you already do, and instead of changing text in each web view, change the frames of the views so that relevant web view (i.e. the one user has scrolled to) remains in the center, and other two are adjusted on both sides
change the content of the both web views (which are not visible) so that they are ready for next scroll movement..
this would still fail in case of fast scrolling.. but because of the lag in loading content i any approach would file there (i think)
so you can probably make all web views blank (load nothing) and show an activity indicator in case of fast scrolling..

iphone tableview with a lot of images

I have a UITableView that displays a lot of images in the cells, and i am not quit happy about the scroll performance. My UITableView is something like the photo app on the iphone. Does anybody knows why the iphone foto app scrolls so damn fast as if their's nothing on the screen.
And does anybody has some tips/tricks to increase my performance/scrolling speed?
You should precache your images and not do it lazily. As you scroll your table the UITableViewDataSource:cellForRowAtIndexPath: method is called, and if you're loading your images in there then you'll see it requesting your cell contents as your scroll, creating latency in your application. Try putting making your cellForRowAtIndexPath: something like this:
NSDate *date = [NSDate date];
... your cell loading code ...
NSLog( #"Elapsed time to generate cell %.2d", [date timeIntervalSinceNow] );
You'll see how long you're spending for getting each cell.
To get around this, you can be as complex as you need to - if you have a lot of images, you're going to have to be more and more clever. You can do paged loading, where you keep track of the last cell requested's NSIndexPath, and determine if the scroll is going up or down and use +NSImage:imageNamed: to fetch at once some page worth of images forward (ie, 5 images forward of your current position), or whatever works out for you (taking advantage of the fact that people have to return their finger down to the bottom of the table to swipe again, and so the consumption of table elements has pauses - you can make your page size large enough to fill a swipe). This is probably still not great, though, because you'll just be suffering all your impact at once instead of a jittery load for every cell.
You can return control to the UI rapidly and allow the system to schedule your prefetched page of images using NSRunLoop:performSelector:target:argument:order:mode: off the main runloop using NSImage:imageNamed:, and then when the cell is requested if you're fetching far enough ahead it'll be available to display.
You need to be painfully aware of memory concerns though. If you're finding this to be an issue, use NSImage:initWithContentsOfFile:, which will clean up image caches in low memory situations. Depending on the strategy used by the cache invalidation algorithm these situations may cause a "stutter" as you purge caches and have to reload your invalidated prefetches.
Great scrolling performance results have been reported by subclassing UITableViewCell and drawing the contents of each cell directly. See the accepted answer for this question for more details and links to code samples.
The problem here is memory, you are loading too many high resolution pictures at once, the foto app does not use a tableview it uses a scroll view and it only loads a max of 3 pictures at a time , so memory is not a concern, if u are trying to do something similar to the foto app use a scroll view

TTTableImageItem doesn't load the image until scroll

I'm using the three20 project for my iPhone app. I've narrowed my problem down and I'm now just trying to re-create the 'Web Images in Table' example that comes with the project. I've copied the code exactly as in the project, with the exception that I do not use the TTNavigator (which the example does) but I am adding my TTTableViewController manually to a tabBar.
The problem is as follows; the images in the table should load automatically from the web, like in the example. But they only load after I scroll the table up and down.
In the console it clearly says it is downloading the images, and you see the activity indicator spinning like forever.. And unless I scroll up and down once, the images will never appear.
Anyone? Thanks in advance.
P.S:
If I'm using this code in any random UIView, It also doesn't work (only shows a black square):
TTImageView* imageView = [[[TTImageView alloc] initWithFrame:CGRectMake(30, 30, 100, 100)] autorelease];
imageView.autoresizesToImage = YES;
imageView.URL = #"http://webpimp.nl/logo.png";
[self.view addSubview:imageView];
If I put this code in my AppDelegate (right onto the window), it does work .. strange?
POSSIBLE SOLUTION:
Although I stopped using TTImageView for this purpose, I do think I found out what the problem was; threading (hence accepting the answer of Deniz Mert Edincik). If I started the asynchronous download (because basically that is all the TTImageView is, an asynchronous download) from anywhere BUT the main thread, it would not start. If I started the download on the main thread, it would start immediately..
Sounds like a threading problem to me, are you creating TTImageView in the main runloop?
I find one interesting thing. When I use combination TTTableViewController, TTTableViewDataSource and TTModel I have same problem with loading TTImageView. My problem was, that my implementation of Model methods 'isLoading' and 'isLoaded' don't return proper values after initialization of model. That forces me to call reload on model manualy in 'viewDidAppear' method and that causes image loading problem. So I repair my 'isLoading' and 'isLoaded' methods to both return 'NO' after Model init, and everything is fine.
When an image finishes loading try sending a reloadData message to the table view. This forces the table to recalculate the size of the rows and redraw the table. Just be careful that you don't start downloading the image again in response to this message.
I've written something similar to this where an image view will load its own image from the web.
Im my experience, when the image had loaded successfully but was not shown in its view, it was a case that the cell needed to be told to redraw.
When you scroll the table view, the cells are set to redraw when the come onscreen, which is why they appear when you scroll.
When the image loads, tell the cell that it is sitting in to redraw by sending it the message setNeedsDisplay.
That way, when the image finishes downloading, the cell its sitting in (and only that cell) will redraw itself to show the new image.
It's possible that you might not need to redraw the entire cell and might be able to get away with simply redrawing the image view using the same method call. In my experience, my table cells view hierarchy was flattened, so I had to redraw the whole cell.
I don't have an answer for what you want to do, but I will say that this is considered a feature, and the expected behavior. You use TTImageView in UITableView when you want to do lazy loading of images. TTImageView will only load the images whose frames are visible on the screen. That way, the device uses its network resources to download images that the user has in front of them, rather than a bunch of images that the user isn't even trying to look at.
Consider having a long list that may contain a couple hundred thumbnail images (like a list of friends). I know from experience that if you kick off 100+ image requests on older devices, the memory usage will go through the roof, and your app will likely crash. TTImageView solves this problem.
This is a thread problem. You can load the images by including the line:
[TTURLRequestQueue mainQueue].suspended = NO;
in - (void)didLoadModel:(BOOL)firstTime.