Lazy load pages in UIScrollView - iphone

I have a UIScrollView that contains large images and am using paging to scroll between images. In order to save memory, I am loading only one image before and after the currently visible one and loading/releasing new images after a scroll has completed.
The problem occurs when one scrolls quickly and scrollViewDidEndDecelerating is not called.
How can I detect continuous scrolling?
I could check item location on every scrollViewDidScroll but this seems a bit heavy...

Perhaps a table view with custom cell content would work better for you, since it has a lot of logic built in for only loading cells that are visible as opposed to everything at once. There are lots of tutorials for how to manage table views in various ways.

My temporary solution is to cancel continuous scrolling by disabling scroll from when the user lifts the finger until scroll has completed.
-(void) scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
[scrollView setScrollEnabled:NO];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[scrollView setScrollEnabled:YES];
}

if you are still looking for a solution... this what I do, it works really good having a good performance, too.
- (void)scrollViewDidEndDragging:(UIScrollView *)sv willDecelerate:(BOOL)decelerate
{
if (!decelerate)
{
// load images and unload the ones that are not needed anymore
[self someMethod]
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sv
{
// load images and unload the ones that are not needed anymore
[self someMethod]
}

You have to check both scrollViewDidEndDecelerating and scrollViewDidScroll.
When to user swipes and releases it letting to have the "momentum" scroll the first will be called at the and. If the user decides to stop the scroll with his finger by tapping to the tableview (or scroll comes to the most bottom, but I am not sure about that) the second event is fired.

You can do something like this
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth / 1.5) / pageWidth) + 1;
}
this method is called anytime the contentoffset changed whether programmatically or not.
From this method you can check to see if 'page' is the same as your current page, if not, you know you need to load something. The trick here is making images load without holding up the scrolling of the scrollview. That is where I am stuck.
the page calculation will change to next/previous when you are half way to the next page

As per my experience, #jd291 is correct; I am using following callbacks successfully for most places
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
but, the only case when a callback is not called is that you may not had set up the delegate properly.

The WWDC 2010 has talked about this. They do a reuse of the sub scrollviews, alloc the missing image view in scrollViewDidScroll:. The video may help:
Session 104 - Designing Apps with Scroll Views

Related

ios load images on scroll from web server

For the iPhone 5.0 I am getting lot of image(image url's) and text data from server in a json form. My requirement is to load the first five images from the server and when the user scrolls down the next five images load and so on. I need to show it in a UITableViewCell. By doing this I can reduce the network calls and make the application faster on the device.
Currently, I am using a background thread to load images, but they continuously load in the background. I don't want to do it like this.
Check this sample code from Apple.
It should answer your question.
LazyTableImages
it uses UIScrollViewDelegate to implement the following function
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate)
{
[self loadImagesForOnscreenRows];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self loadImagesForOnscreenRows];
}
Basically it does what you want - just load image of the visible cell.
With [self.tableView indexPathsForVisibleRows] you can call the startIconDownload function to the specified tableCell

IPhone SDK: Problem with lazy loading scrollView pictures

My app involves a scrollView containing imageViews chosen by the user through a modified ELCImagePicker. The chosen pictures will typically be high quality photos at 5 MB+ and the user will usually choose at least a dozen pictures at a time. Currently, I am loading the photos as below:
-(void)loadViewWithPage: (int)page
{
if (page > 0 && page < [Album count]) {
[scrollView addSubview:[Album objectAtIndex:page]];
}
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth) / pageWidth) + 1;
[self loadViewWithPage:page + 1];
}
Where Album is where the photos are stored as imageViews.
This works great when the user is not trying to break the app and scroll through the photos one at a time, but fails miserably when he/she tries to scroll through the entire selection. The pages are blank unless the user stops after each photo. I tried using scrollViewDidScroll ala PageControl sample, but since the photos are all huge the lag is very visible.
Is there any way of loading the photos smoothly?
I had a similar situation and dealt with it by creating a custom subclass of NSOperation which would load the images in a separate thread and then display them by calling a method on the main thread.
While the image was loading I displayed a UIActivityView
Hope that helps.
if you use scrollViewDidEndDecelerating it will only fire when the scrollview stops. I would use scrollViewDidScroll for that. (it's also used in their examples)

Detecting hits in UIScrollView while still letting it do it's thing

Is there a simple way to detect if someone touched a UIScrollView without having to disable user interactions?.
I know this has been answered a few times before, but every answer I find is somebody wanting to detect hits in an image. I don't have an image. What I do have is a scroll view with a number of text fields embedded in it. They used to be inside a UIControl, from which I could detect a touchDown and call resignFirstResponder on all my textfields (for when the keyboard is up). But when they are in a UIScrollView, I can't seem to find a simple way to do this.
I don't really want to have to write code to do the scrolling myself, which is what I assume I have to do if I disable user interactions and grab the touchesBegan message. But I may be wrong. I'm still a little new at this, but this is the first time I haven't been able to figure out the answer by reading the code doc and googling ...
Thanks for any help,
J
Override touchesBegan, do your processing, and call the super implementation so that the scroll view still gets the message.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//call resignFirstResponder
[super touchesBegan:touches withEvent:event];
}
Ok, now I feel a little silly answering this myself. I'm sure the other suggested answer would have worked also, but I found something else while browsing in other topics which totally fixed this for me.
I added this to my viewDidLoad:
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(backgroundTap:)];
[scrollView addGestureRecognizer: singleTap];
Where backgroundTap is
- (IBAction)backgroundTap:(id)sender
is the function I was originally calling from touchDown in UIControl to resign all keyboard firstResponders.
This works perfectly and adds very little code. And I DON'T have to disable user interactions! Yay!
Thanks all for your comments and help.
J
I fiddled with this for a while, and found that simple touchesBegan and touchesEnd type stuff didn't really work.
They don't work because because of the gestures. For example touchesEneded never fires when you swipe to scroll (at least not in the UIScrollView). So while you can detect when a touch event starts, you'll never know when it ends.
I wasn't real keen on including a gesture recognizer for simple stuff and discovered that you can find out about the state of the scrolling better from interacting with the UIScrollView directly.
And it's not that painful. Just add the protocol, then add the functions you need for detection.
In the view (class) that contains the UIScrolView, add the protocol, then added any the functions from here to your view (class).
Example:
// --------------------------------
// In the "h" file:
// --------------------------------
#interface myViewClass : UIViewController <UIScrollViewDelegate> // <-- Adding the protocol here
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView;
// --------------------------------
// In the "m" file:
// --------------------------------
#implementation BlockerViewController
UIScrollView *scrollView;
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSLog(#"end decel");
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
NSLog(#"end dragging");
}
// All of the available functions are here:
// https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIScrollViewDelegate_Protocol/Reference/UIScrollViewDelegate.html

Zoom multiples UIImageViews inside an UIScrollView

I am trying to create a small game for the iPhone with images and I want to them to zoom in when the player is pinching on the screen. I have seen the tutorials with one UIImageView. However, now that I am using multiple UIImageViews, things do not work OK.
I put them in an UIView and I am trying to zoom the UIView. However things are not scaling inside the screen when I run the simulator.
I use the following code
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return containerView; //containerView is the UIView containing all the UIIMageviews
}
-(void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
[containerView setTransform:CGAffineTransformIdentity];
containerView.contentScaleFactor = scale;
}
Any suggestions?
Thanx!
Have a look at the ScrollViewSuite sample on the apple developer site, that has a working example of what I think you're trying to do.
(I think I got the name right, the developer site is down for updates just now so I can't give you a link.)

ScrollOffset in UIWebView?

I'm having a really hard time understanding delegates and object inheritance (if I may use this word) and I think I need a simple (or so I think) thing: catch scrollViewDidScroll event in UIWebView and get offset (basically, just to know if scroll is not on top/bottom, so I could hide navigation and tab bars).
Is there any way I could do it? I already using UIWebviewDelegate in my view controller to "shouldStartLoadWithRequest". Maybe I could some how use UIScrollViewDelegate too for scrollViewDidScroll? If yes, then how?
I really have trouble understanding delegates. I've red some articles, but still, in practice, I can't manage to use them.
Any help or info would be lovely.
Thank you in advance!
To retrieve scroll events on UIWebView I personnaly use this code to get the scrollview that is inside the UIWebView :
- (void) addScrollViewListener
{
UIScrollView* currentScrollView;
for (UIView* subView in self.myWebView.subviews) {
if ([subView isKindOfClass:[UIScrollView class]]) {
currentScrollView = (UIScrollView*)subView;
currentScrollView.delegate = self;
}
}
}
It's working. You can also use it to call [currentScrollView setContentOffset:offSet animated:YES]; The only problem may be not to pass Apple code checking. I don't know yet since I'm still in coding phase.
[UPDATE] The app with this code is in the app store for 4 months now and used by 40 000 users. I didn't have any trouble [UPDATE]
You can use the following methods to solve your problem.
For getting the pageOffset:
int pageYOffset = [[webViewObj stringByEvaluatingJavaScriptFromString:#"window.pageYOffset"] intValue];
For getting the total scroll height of a webpage:
int scrollHeight = [[webViewObj stringByEvaluatingJavaScriptFromString:#"document.documentElement.scrollHeight"] intValue];
For scrolling the webpage to a particular offset:
[webViewObj stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:#"document.body.scrollTop = %d",scrollHeight ]];
I made a modification to detect the class with isKindOfClass. Works - but may have issues with Apple code checking as stated above.
UIScrollView* currentScrollView;
for (UIView* subView in terms.subviews) {
if ([subView isKindOfClass:[UIScrollView class]]) {
NSLog(#"found scroll view");
currentScrollView = (UIScrollView *)subView;
currentScrollView.delegate = self;
}
}
Old thread, I know -
As of iOS 5.0 you can use
myAccountWebView.scrollview
to access content size and offset.
There is a scrolling view in the UIWebView, but it a) isn't a UIScrollView, and b) is something Apple considers a private implementation detail (and you should too). I only really have two suggestions:
File a bug with Apple asking them to expose more of the infrastructure of the web view, or at least add some more delegate methods by which we can be notified of these sorts of events.
Add some JavaScript code to your page that listens from scroll events, and notifies your app of them.
The basic foundation of #2 is to load a fake URL, and have your web view delegate process (and abort!) that load. (This question has come up a few times here on Stack Overflow.)
UPDATE:
As of iOS 5, there is now a public scrollView property on UIWebView that you can use to customize scrolling behavior. The exact view hierarchy of the web view remains an undocumented implementation detail, but this gives you a sanctioned way to access this piece of it.
It's a good question. UIWebView is not a subclass of UIScrollView, although I can see why one might think it is. That means using the UIScrollViewDelegate methods is not an option to do what you want, and the UIWebViewDelegate protocol does not respond to those scrolling event type of messages. I don't think there's an easy way to detect scrolling events in a web view.
I tired the delegate method and found it prevented the view from scrolling when the keyboard was shown. I found that by adding an observer you do not override the current delegate and will prevent you from effecting the webview performance.
for (UIView* subView in myAccountWebView.subviews) {
if ([subView isKindOfClass:[UIScrollView class]])
{
NSLog(#"found scroll view");
[((UIScrollView*)subView) addObserver:self forKeyPath:#"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
}
}