Scroll view is not smooth - ios

I'm working in a project (iOS7 & ARC) in which, I want to display N number of images in the scroll view.These Images already stored into sandbox directory. My App has only landscape orientation I'm facing a problem that ScrollView is not smooth, it stuck after 2-3 times scroll
This is how I configure ScrollView
[self.containerScroll setAutoresizesSubviews:NO];
self.containerScroll.pagingEnabled = YES;
self.containerScroll.showsHorizontalScrollIndicator = NO;
self.containerScroll.showsVerticalScrollIndicator = NO;
self.containerScroll.scrollsToTop = NO;
self.containerScroll.maximumZoomScale = 5.0;
self.containerScroll.minimumZoomScale = 1.0;
self.containerScroll.delegate = self;
I'm maintaining only three Images in the scrollView at a time.
I'm loading Images in ScrollView in below method
-(void) loadScrollViewWithPage:(int) page{
if (page >= self.numberOfSlides)
return;
float image_width;
float image_height;
if(self.isFromListView){
if(IS_IPHONE5){
image_width = 568.0f;
image_height = 320.0f;
} else{
// iPhone retina-3.5 inch
image_width = 480.0f;
image_height = 320.0f;
}
}
else{
image_width = IMAGE_WIDTH;
image_height = IMAGE_HEIGHT;
}
CGFloat xPos = page * image_width;
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(xPos, 0.0f, image_width, image_height)];
imgView.tag = page;
NSString *imgPath = [self.storageDirPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%d%#", page, Image_Extension_JPG]];
NSFileManager *fileManager = [NSFileManager defaultManager];
__block UIImage *img = nil;
if(![fileManager fileExistsAtPath:imgPath]){
[imgView setContentMode:UIViewContentModeCenter];
img = [UIImage imageNamed:#"icon-loader.png"];
[imgView setImage:img];
}
else{
[imgView setContentMode:UIViewContentModeScaleAspectFit];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
img = [[UIImage alloc] initWithCGImage:[[UIImage imageWithData:[NSData dataWithContentsOfFile:imgPath]] CGImage] scale:1.0 orientation:UIImageOrientationUp];
dispatch_async(dispatch_get_main_queue(), ^{
[imgView setImage:img];
});
});
}
[self.containerScroll addSubview:imgView];
img = nil;
fileManager = nil;
imgView = nil;
}
and this how my ScrollView Delegate methods goes...
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
self.containerScroll.scrollEnabled = YES;
float page = self.containerScroll.contentOffset.x/self.view.frame.size.width;
showingSlide = (UInt16) roundf(page);
if(scrollView == self.containerScroll){
// switch the indicator when more than 50% of the previous/next page is visible
CGFloat pageWidth = CGRectGetWidth(self.containerScroll.frame);
NSUInteger pageNo = floor((self.containerScroll.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
// load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
[self loadScrollViewWithPage:pageNo - 1];
[self loadScrollViewWithPage:pageNo];
[self loadScrollViewWithPage:pageNo + 1];
// a possible optimization would be to unload the views+controllers which are no longer visible
if(scrollView == self.containerScroll)
{
[self.previewTableView reloadData];
[self.previewTableView setContentOffset:CGPointMake(0, (page*220)+64) animated:NO];
[self.previewTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:page inSection:0] atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
[self updateSlideNumber];
[self flashSlideNumber];
}
//unload unnecessary imageviews from scroll view
for (UIView* view in self.containerScroll.subviews) {
if ([view isKindOfClass:[UIImageView class]] && view.tag != page && view.tag != page-1 && view.tag != page+1) {
[view removeFromSuperview];
}
}
}
}
Now the problem is smoothness of scrollView. When I start scrolling it scrolls fine but after 2 or 3 (or after any random number) pages scroll, it stuck and after trying 2-3 times only it moves again and I have to swipe hard to scroll. Thanks in advance.

I think it's a problem of memory somewhere Try #autorelease pool in your code.

using scrollview is not a good approach for showing images, I will recommend you to either use tableview or collevtionview for the same.
Your app's memory will keep on increasing with every scroll because scollview doesn't reuse the memory, on the other hand tableview and collectionview reuses the memory.

As the most effective way to become better, scroll really slowly (one at a time) while you monitor the memory usage in your App. You'll be able to watch it go up as each new image is added to the view, especially if you haven't done any optimisation on the images.
The other thing is that while your code does look like is deallocc-ing the images, you still need to remember that it still has to try to reload the images as you scroll. You're creating your images on the main thread so you're never going to get the smoothness of a UITableView. While I realise that you're creating your image views on async threads, the act of adding and scrolling them is still being taken care of by the mainthread.
I would suggest a UITableView to solve your problem, or a UICollectionView. If you're set on using the scrollview, I would suggest using a crusher of some type to get the image size to as small as possible, while still keeping quality decent.
If you need help on the TableView implementation you should find plenty of information around SO. Probably a good option if you still want it to look like a scroll view is just to make all seperators, headers etc to nil, and then just use lazy loading for the images.

You make two the mistake. At first: never use imageNamed for non graphics content (example, use imageNamed for button background). And second: you try load a big images in real time. So you scroll view have lags therefore. If you load all images before you show the scroll view the amination end lagging. But you can get memory warnings. So, you need optimise it. P.S. Sorry for my english

Related

iOS Image Viewer: Set Zoomed Image Back when Scrolling

I have a gallery view that I use to, surprise, view images. :) I wanted it to be very similar to the standard iOS photo app with the same basic functionality: scrolling through images that are zoomable. To do this, I use a regular scroll view which I then populate with other scroll views that contain the images. The basic idea is something like this:
And, I do the populating the following way:
for (int i = 0; i < [imageGalleryArray count]; i++)
{
UIScrollView *imageContainer = [[UIScrollView alloc] initWithFrame:CGRectMake((windowSize.width * i), 0.0f, windowSize.width, windowSize.height)];
imageContainer.contentSize = CGSizeMake(windowSize.width, windowSize.height);
imageContainer.showsHorizontalScrollIndicator = NO;
imageContainer.showsVerticalScrollIndicator = NO;
imageContainer.bouncesZoom = YES;
imageContainer.delegate = self;
imageContainer.minimumZoomScale = 1.0f;
imageContainer.maximumZoomScale = 3.0f;
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, windowSize.width, windowSize.height)];
imageView.image = [[imageGalleryArray objectAtIndex:i] objectForKey:#"image"];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.tag = IMAGEZOOMVIEW;
imageView.opaque = YES;
[imageContainer addSubview:imageView];
[galleryScrollView addSubview:imageContainer];
// Add it to array for proper removal later
[contentScrollViewSubviewList addObject:imageContainer];
}
Quite simple and it works fine. The only problem arises when I am zoomed into an image and want to scroll to another. The expected behavior in this case would be to set the zoom level of all images back to their original size. I do this in the scrollViewDidEndDecelerating delegate method like so:
if (scrollView.tag == GALLERYSCROLLVIEW)
{
for (int i = 0; i < [contentScrollViewSubviewList count]; i++)
{
[[contentScrollViewSubviewList objectAtIndex:i] setZoomScale:1.0f animated:YES];
}
}
Unfortunately, it does not work (or rather not as intended). I did some poking around and found out that the image does indeed animate back to its original size when it is the active slide. To elaborate: if I zoom into an image and scroll to another, the image stays zoomed in. When I scroll back to it, that is when it zooms back to its original size. I, however, need the image to scale back as soon as I move away from it ().
just like in the iOS image viewer).
Frankly, I do not understand the whole zoom mechanic enough to find out how to do this properly, and the UIScrollView documentation is of no help either. Could someone show me how to solve my problem?

smooth scrolling of imageview

I have added the images to scrollview dynamically. Following is the code:
-(void)displayimages {
for (int i=0; i<[featuredarray count]; i++) {
NSString *image=[[featuredarray objectAtIndex:i]objectForKey:#"img"];
if (i==0) {
webview=[[UIImageView alloc]initWithFrame:CGRectMake(i*290+15, 0, 270, 380)];
}
else {
webview=[[UIImageView alloc]initWithFrame:CGRectMake(i*290+15, 0, 270, 380)];
}
webview.backgroundColor=[UIColor clearColor];
webview.layer.borderWidth=4;
webview.layer.cornerRadius=4;
webview.layer.borderColor=[[UIColor whiteColor]CGColor];
[webview setTag:i];
webview.userInteractionEnabled=YES;
NSURL *url = [NSURL URLWithString:image];
[webview setImageWithURL:url placeholderImage:[UIImage
imageNamed:#"placeholder.png"]];
[scroll addSubview:webview];
UITapGestureRecognizer *tap1 =
[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handlefirstTap:)];
tap1.delegate=self;
tap1.numberOfTapsRequired=1;
[webview addGestureRecognizer:tap1];
[tap1 release];
[webview release];
}
pages.defersCurrentPageDisplay=YES;
scroll.delegate =self;
scroll.contentSize = CGSizeMake(290*[featuredarray count],300);
scroll.pagingEnabled = YES;
pages.numberOfPages = [featuredarray count]-1;
pages.currentPage =0;
}
-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
int gallerypage = scrollView.contentOffset.x /scrollView.frame.size.width;
CGRect frame = scroll.frame;
frame.origin.x = frame.size.width-15*gallerypage;
NSLog(#"frame.origin.x..%f",frame.origin.x);
frame.origin.y =0;
[scroll scrollRectToVisible:frame animated:YES];
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
int gallerypage = scrollView.contentOffset.x /scrollView.frame.size.width;
CGRect frame = scroll.frame;
frame.origin.x=gallerypage*290;
frame.origin.y =0;
[scroll scrollRectToVisible:frame animated:YES];
}
It is showing the images well when I use pagecontroller action
-(IBAction)pagecontrolaction:(id)sender {
int page = pages.currentPage;
CGRect frame = scroll.frame;
frame.origin.x=page*290;
frame.origin.y =0;
[scroll scrollRectToVisible:frame animated:YES];
}
but when I used touch event to imageview then it is not displaying the images smoothly. It gets stuck during scrolling.
These are the preview which shows the current image with the start of second image.
The two methods: scrollViewWillBeginDecelerating and scrollViewDidEndDecelerating contains two animation with different x position it is animated to:
frame.origin.x = frame.size.width-15*gallerypage;
and
frame.origin.x=gallerypage*290;
It is better if you could turn off either function: scrollViewWillBeginDecelerating or scrollViewDidEndDecelerating. Otherwise probably you should adjust the x position (must be in sync for both functions) it will be animated to.
if (i==0) {
webview=[[UIImageView alloc]initWithFrame:CGRectMake(i*320, 0, 270, 440)];
}
else {
webview=[[UIImageView alloc]initWithFrame:CGRectMake(i*320, 0, 270, 380)];
}
You need to change your implementation for loading the images from a remote resource, thats where the biggest bottle neck lies.
I don't want to go into details but try to replace this line of code
[webview setImageWithURL:url placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
With something more c.p.u friendly. Use a caching mechanism that employs multi-threading, what you want to do is move the heavy lifting (Downloading remote images) to another thread.
SDWebImage does a perfect job at this https://github.com/rs/SDWebImage
If you are a bit hard core, you can roll your own multi-threaded image downloader using
[NSURLConnection sendAsynchronousRequest:queue: completionHandler:]
And do the image loading in the block, Or even better dispatch_async(queu, block)
You should checkout SYPaginator (SYPaginator is a horizontal TableView with lazy loading and view recycling.)
Simple paging scroll view to make complicated tasks easier. We use
this in several of the Synthetic apps including Hipstamatic, D-Series,
and IncrediBooth.
If you need more perfomance you should give MSCachedAsyncViewDrawing a try:
Helper class that allows you to draw views (a)synchronously to a
UIImage with caching for great performance.
I think that happens because your images are larger in size (MB) , and you fit them into that CGRect without resizing them that specific size, also it matters how many pictures you have loaded inside the scrollview , if you load many pictures it will take a lot of memory and it will slide slowly.
I am not sure why are you setting the frame when you start scrolling, but if I were in your place I would do the logic as per below:
-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
// do nothing
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
int gallerypage = scrollView.contentOffset.x /scrollView.frame.size.width;
CGFloat xoffset = 290 * gallerypage;
[UIView animateWithDuration:0.5
animations:^{
scrollView.contentOffset = CGPointMake(xoffset, 0);
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}
Hope it helps.
It happens when you are trying to show an image of larger size in that case you have to use cache to get the image there and try to load image from there.For better know how to use it..use SDwebimage thats the nice way to do it and it is fast.
You can read
Lazy Loading
2.paging enabled.
https://github.com/rs/SDWebImage
Need any further help.Happy to help you.
You should definitely resize your image in background prior to loading it into UIImageView. Also you should set UIImageView content mode to the none-resizing one. It gave me significantly scrolling improvement after other ways were already implemented (async cache and others).

UITableView with images crashes app when I scroll down a lot

I have this simple UITableView and each cell has an image corresponding to it. All I'm doing is displaying a title for the image and the image itself in each cell. Here is my cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// this is where the data for each cell is
NSDictionary *dataForThisCell = cachedData.posts[indexPath.row][#"data"];
// this creates a new cell or grabs a recycled one, I put NSLogs in the if statement to make sure they are being recycled, they are.
post *cell = (post *) [self.tableView dequeueReusableCellWithIdentifier:#"postWithImage"];
if (cell == nil) {
cell = [[[NSBundle mainBundle]loadNibNamed:#"postWithImage" owner:self options:nil]objectAtIndex:0];
[cell styleCell];
}
// if this cell has an image we need to stick it in the cell
NSString *lowerCaseURL = [dataForThisCell[#"url"] lowercaseString];
if([lowerCaseURL hasSuffix: #"gif"] || [lowerCaseURL hasSuffix: #"bmp"] || [lowerCaseURL hasSuffix: #"jpg"] || [lowerCaseURL hasSuffix: #"png"] || [lowerCaseURL hasSuffix: #"jpeg"]) {
// if this cell doesnt have an UIImageView, add one to it. Cells are recycled so this only runs several times
if(cell.preview == nil) {
cell.preview = [[UIImageView alloc] init];
[cell.contentView addSubview: cell.preview];
}
// self.images is an NSMutableDictionary that stores the width and height of images corresponding to cells.
// if we dont know the width and height for this cell's image yet then we need to know now to store it
// once the image downloads, and then cause our table to reload so that heightForRowAtIndexPath
// resizes this cell correctly
Boolean shouldReloadData = self.images[dataForThisCell[#"name"]] == nil ? YES : NO;
// download image
[cell.preview cancelImageRequestOperation];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString: dataForThisCell[#"url"]]];
[request addValue:#"image/*" forHTTPHeaderField:#"Accept"];
[cell.preview setImageWithURLRequest: request
placeholderImage: [UIImage imageNamed:#"thumbnailLoading.png"]
success: ^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
// if we indicated earlier that we didnt know the dimensions of this image until
// just now after its been downloaded, then store the image dimensions in self.images
// and tell the table to reload so that heightForRowAtIndexPath
// resizes this cell correctly
if(shouldReloadData) {
NSInteger imageWidth = image.size.width;
NSInteger imageHeight = image.size.height;
if(imageWidth > [ColumnController columnWidth]) {
float ratio = [ColumnController columnWidth] / imageWidth;
imageWidth = ratio * imageWidth;
imageHeight = ratio* imageHeight;
}
if(imageHeight > 1024) {
float ratio = 1024 / imageHeight;
imageHeight = ratio * imageHeight;
imageWidth = ratio* imageWidth;
}
self.images[dataForThisCell[#"name"]] = #{ #"width": #(imageWidth), #"height": #(imageHeight), #"titleHeight": #([post heightOfGivenText: dataForThisCell[#"title"]]) };
[self.tableView reloadData];
// otherwise we alreaady knew the dimensions of this image so we can assume
// that heightForRowAtIndexPath has already calculated the correct height
// for this cell
}else{
// assign the image we downloaded to the UIImageView within the cell
cell.preview.image = image;
// position the image
NSInteger width = [self.images[dataForThisCell[#"name"]][#"width"] integerValue];
NSInteger height = [self.images[dataForThisCell[#"name"]][#"height"] integerValue];
cell.preview.frame = CGRectMake( ([ColumnController columnWidth] - width)/2 , [self.images[dataForThisCell[#"name"]][#"titleHeight"] integerValue] + 10, width, height);
}
}
failure: ^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {}];
}
// set title of the cell
cell.title.text = [NSString stringWithFormat: #"%#\n\n\n\n\n", dataForThisCell[#"title"]];
`enter code here`// ask for a restyle
[cell setNeedsLayout];
// returns my customized cell
return cell;
}
What happens is that everything works exactly how I want it to, however once I scroll down past around 100 cells or so the background of my app goes black for a few seconds and then I see my homescreen (I've seen some people call this the HSOD - home screen of death). Sometimes in the console in xcode I see memory warnings before a crash and sometimes I do not.
I know for a fact that whatever the problem is, it has to do with putting images into the cells. If I comment out just this line:
cell.preview.image = image;
Then everything works fine and it doesn't crash any more (but then of course the images are not being displayed in the cells).
The cells are being reused and I know that's working, for good measure I set the UIImageView's image property to nil:
- (void) prepareForReuse {
[super prepareForReuse];
if(self.preview != nil)
self.preview.image = nil;
}
and in my appDelegate I also define this:
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
[UIImageView clearAFImageCache];
}
Which deletes the image cache but that doesn't fix the problem either (and anyway iOS should clear the image caches upon memory warnings automatically anyway).
I ran analyze on my project and it reports no memory leaks, and here is the profiler showing that, as well as showing the allocations at the time of the crash:
Other than the occasional memory warning in the console which appears about 2/3rds of the time the app crashes, there are no other errors that appear in the console, and I do not hit any breakpoints or exceptions.
All of those allocations are you creating new table view cells each time they're requested, rather than reusing existing ones. Without setting a reuseIdentifier for cells created from UINib, dequeueReusableCellWithIdentifier: will always return `nil.
To fix this, add the following code (as referenced in this question):
[self.tableView registerNib:[UINib nibWithNibName:#"nibname"
bundle:nil]
forCellReuseIdentifier:#"cellIdentifier"];
I've found a solution, albeit not a perfect one:
The AFNetworking library is brilliant and I assume the cause of my problem lies within my own code or my lack of understanding as to how NSCache works.
AFNetworking caches images using Apple's NSCache. NSCache is similar to NSMutableDictionary, but releases objects when memory is spread thin (see more here).
Within UIImageView+AFNetworking.m I located the definition of
+ (AFImageCache *)af_sharedImageCache
And altered it to resemble this:
+ (AFImageCache *)af_sharedImageCache {
static AFImageCache *_af_imageCache = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_af_imageCache = [[AFImageCache alloc] init];
_af_imageCache.countLimit = 35;
});
return _af_imageCache;
}
The important line here is
_af_imageCache.countLimit = 35;
This tells the NSCache object being used in AFNetworking to cache images that it must only cache up to a maximum of 35 things.
For reasons unknown to me, iOS was not automatically purging objects from the cache as it should, and calling removeAllObjects on the cache was not working either. This solution is hardly ideal because it may not utilize the cache to its full potential or may over use the cache, but for the meantime it atleast stops the cache from attempting to store an infinite number of objects.

Demand loading a UIScrollView

I would like to implement an app using a UIScrollView with paging, similar to the apple weather app.
But I am a little concerned about performance. The example implementation I have been using loads all of the views then the application launches. After a certain point, once this prove slow?
I wonder how Apple's camera roll is dealing with this, where a user may have 100+ photos that can be scrolled through. Should I try to figure out a way to build the view only when it is needed? Or maybe there is a way to replicate the dequeue reusable cell technique from a UITableView, only for horizontal view loading, since each view will have the same layout.
By far the most efficient solution (and this is used in many photo-browsing apps such as Facebook, and probably the native Photos app too) is going to be to load the content on-demand, just as UITableView does. Apple's StreetScroller sample project should get you on the right track.
A very efficient solution, is to make sure to reuse any views whenever possible. If you are going to be simply displaying images, you could use a subclass of UIScrollView, and layout these reusable views within layoutSubviews. Here you could detect what views are visible and not visible and create the subviews as needed.
An example dequeuing function may look like:
- (UIImageView *)dequeueReusableTileWithFrame:(CGRect) frame andImage:(UIImage *) image
{
UIImageView *tile = [reusableTiles anyObject];
if (tile) {
[reusableTiles removeObject:tile];
tile.frame = frame;
}
else {
tile = [[UIImageView alloc] initWithFrame:frame];
}
tile.image = image;
return tile;
}
Where reusableTiles is just an iVar of NSMutableSet type. You could then use this to load fetch any currently offscreen image views and quickly and easily bring them back into view.
Your layoutSubviews may look something like:
- (void)layoutSubviews {
[super layoutSubviews];
CGRect visibleBounds = [self bounds];
CGPoint contentArea = [self contentOffset];
//recycle all tiles that are not visible
for (GSVLineTileView *tile in [self subviews]) {
if (! CGRectIntersectsRect([tile frame], visibleBounds)) {
[reusableTiles addObject:tile];
[tile removeFromSuperview];
}
}
int col = firstVisibleColumn = floorf(CGRectGetMinX(visibleBounds)/tileSize.width);
lastVisibleColumn = floorf(CGRectGetMaxX(visibleBounds)/tileSize.width) ;
int row = firstVisibleRow = floorf(CGRectGetMinY(visibleBounds)/tileSize.height);
lastVisibleRow = floorf(CGRectGetMaxY(visibleBounds)/tileSize.height);
while(row <= lastVisibleRow)
{
col = firstVisibleColumn;
while (col <= lastVisibleColumn)
{
if(row < firstDisplayedRow || row > lastDisplayedRow || col < firstDisplayedColumn || col >lastDisplayedColumn)
{
UImageView* tile = [self dequeueReusableTileWithFrame:CGRectMake(tileSize.width*col, tileSize.height*row, tileSize.width, tileSize.height) andImage:YourImage];
[self addSubview:tile];
}
++col;
}
++row;
}
firstDisplayedColumn = firstVisibleColumn;
lastDisplayedColumn = lastVisibleColumn;
firstDisplayedRow = firstVisibleRow;
lastDisplayedRow = lastVisibleRow;
}
I used something similar to this to tile in areas of a line when I was working with an exceptionally large area of a scroll view and it seemed to work quite well. Sorry for any typos that I may have created when updating this for an image view instead of my custom tileView class.

TiledScrollView doesn't display some tiles when scrolled to that area

I'm having some problems in my usage of the TiledScrollView from Apple (3_Tiling/​Classes/​TiledScrollView.m) where sometimes the tiles don't show as I scroll and other times they do show. To clarify, the problem I'm seeing is analogous to having a tableview list of 100 rows and only 10 rows are being displayed at a time. As you scroll through the list, sometimes one or more rows are blank and stay blank because once they're displayed on the screen there is no reloading to make sure the content is there. However once these blank rows go off screen and say you scroll back to them, they show up with the content.
It seems to be completely random with no discernible patterns to it's behaviour. I know the delegate method ((UIView *)tiledScrollView:(TiledScrollView *)tiledScrollView tileForRow:(int)row column:(int)column resolution:(int)resolution) is executing thru NSLog's.
My Question is:
1. Have you encountered this phenomenon and how did you solve it? or
2. My debugging skill are very rudimentary. If I wanted to isolate the problem by seeing whether the tile or subviews exists, or the imageView was not able to fetch the image or if its a rendering problem... how would I go about debugging this?
Note- the delegate method shown below is a stripped down version of the above tiledScrollView delegate method where I removed the row and resolution portions of the code since there is no need for it if I'm just scrolling horizontally.
- (UIView *)tiledScrollView:(HorizontalTiledScrollView *)tiledScrollView column:(int)column {
NSLog(#"+++ %s +++", __PRETTY_FUNCTION__);
// re-use a tile rather than creating a new one, if possible
UIView *tile = [tiledScrollView dequeueReusableTile];
if (!tile) {
// the scroll view will handle setting the tile's frame, so we don't have to worry about it
if (tiledScrollView == self.timeHour) {
tile = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, TIMEHOUR_COLUMN_WIDTH, TIMEHOUR_COLUMN_HEIGHT)] autorelease];
} else if (tiledScrollView == self.timeMinute) {
tile = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, TIMEMINUTE_COLUMN_WIDTH, TIMEMINUTE_COLUMN_HEIGHT)] autorelease];
}
// Some of the tiles won't be completely filled, because they're on the right or bottom edge.
// By default, the image would be stretched to fill the frame of the image view, but we don't
// want this. Setting the content mode to "top left" ensures that the images around the edge are
// positioned properly in their tiles.
[tile setContentMode:UIViewContentModeTopLeft];
}
for(UIView *subview in [tile subviews]) {
if (subview.tag != 3) {
[subview removeFromSuperview]; //remove all previous subviews in tile except tile annotation if present
}
}
UIImageView *imgView = [[UIImageView alloc] init];
UILabel *digitLabel;
// Add blank UIImageView as filler or UIImageView with PNG or UILabel if no PNG sized correctly and offsetted from tile's origin as subviews in the tile
if (tiledScrollView == self.timeHour) {
if (column < 1) {
imgView.frame = CGRectZero;
[tile addSubview:imgView];
[tile bringSubviewToFront:imgView];
} else {
int digitH = ((column - 1) % 12 + 1);
imgView.frame = CGRectMake(9, 0, 17, 21);
[imgView setContentMode:UIViewContentModeScaleToFill];
if ((imgView.image = [UIImage imageNamed:[NSString stringWithFormat:#"TimeHour_%02d.png", digitH]])) {
[tile addSubview:imgView];
[tile bringSubviewToFront:imgView];
} else {
// NSLog(#"No TimeHour_%02d.png", digitH);
digitLabel = [self makeDigitLabel:digitH frame:imgView.frame fontSize:14.0];
[tile addSubview:digitLabel];
[tile bringSubviewToFront:digitLabel];
}
}
} else if (tiledScrollView == self.timeMinute) {
// if (column % 2) {
// tile.backgroundColor = [UIColor redColor];
// } else {
// tile.backgroundColor = [UIColor blueColor];
// }
if (column < 1) {
imgView.frame = CGRectZero;
[tile addSubview:imgView];
[tile bringSubviewToFront:imgView];
} else {
int digitM = ((column - 1) % 60);
imgView.frame = CGRectMake(9, 0, 16, 15);
[imgView setContentMode:UIViewContentModeScaleToFill];
if ((imgView.image = [UIImage imageNamed:[NSString stringWithFormat:#"TimeMinute_%02d.png", digitM]])) {
[tile addSubview:imgView];
[tile bringSubviewToFront:imgView];
} else {
NSLog(#"No TimeMinute_%02d.png", digitM);
digitLabel = [self makeDigitLabel:digitM frame:imgView.frame fontSize:12.0];
[tile addSubview:digitLabel];
[tile bringSubviewToFront:digitLabel];
}
}
}
}
[imgView release];
NSLog(#"Tile: %d",[tile.subviews count]);
return tile;
}
Hope it's clear.
Thanks for helping,
Hiren
I finally solved this!
The issue wasn't in the code posted above. It was in the layoutSubviews method of Apple's TiledScrollView. There is a line of code that calculates the maximum column shown below (max row has a similar calculation)
int lastNeededCol = MIN(maxCol, floorf(CGRectGetMaxX(visibleBounds) / scaledTileWidth));
In my case, this formula calculates one extra column than is needed and this column tile ends up sitting off screen. This is fine when you're not scrolling around and setting animated to NO. But when you do scroll around and/or set animated to YES in scrollview setContentOffset method call, you will sometimes end up with a missing tile because of the delay due to animation or if you scrolled really slowly. The animation or moving really slowly causes the scrollview to detect there is a movement and thus calls the layoutSubviews method where a line of code checks to see which tiles are visible and drops non-visible tiles. Now if you did this just right, then the extra tile created earlier gets dropped because its still off-screen and is never created again until the tile has moved far enough off-screen triggering a reload of that tile.
The fix I did was to change the above line to:
int lastNeededCol = MIN(maxCol, floorf((CGRectGetMaxX(visibleBounds)-0.1) / scaledTileWidth));
This new formula calculates the right number of columns of tiles needed to be displayed on screen thereby eliminating the whole thing of dropping extra tiles sitting off-screen.
I create an example code on github that helps demonstrates this.
https://github.com/hmistry/TiledScrollViewDebug

Resources