Infinite UIScrollView gets strange behavior in iOS

2019-04-09 22:51发布

I've implemented an infinite UIScrollView that contains UIViews. When scrolling in iOS5 (simulator and iphone) it works beautifully. But in iOS 4.3 (sim and phone) it kinda goes a bit crazy and scrolls passed more views than it should (about 10-15x more views than in iOS5). And to make it more strange it works as expected if I drag slowly so it doesn't continue scroll after I release.

The setup is kinda simple, I extend the UIScrollView class. And add UIView with a width of about 1500pt, in this I add my UIViews I want to scroll over. When scrolling the views are added and removed if they are off screen.

I think my problem lies with in this code:

 - (void)recenterIfNecessary {
    CGPoint currentOffset = [self contentOffset];
    CGFloat contentWidth = [self contentSize].width;
    CGFloat centerOffsetX = (contentWidth - [self bounds].size.width) / 2.0;
    CGFloat distanceFromCenter = fabs(currentOffset.x - centerOffsetX);
    UIView *image;

    if (distanceFromCenter > (contentWidth / 4.0)) {
        // Recenter the ScrollView so we never reach the edge
        self.contentOffset = CGPointMake(centerOffsetX, currentOffset.y);
        // Move visiblePhotos to new position to adjust to new contentOffset
        for (image in visiblePhotos) {
            CGPoint center = image.center;
            center.x += (centerOffsetX - currentOffset.x);
            image.center = center;
        }
    }
}

recenterIfNecessary is invoked here:

- (void)layoutSubviews {
    [super layoutSubviews];    
    [self recenterIfNecessary];
    [self tileImagesFromMinX:minVisibleX toMaxX:maxVisibleX];
    //....
}

I've tried logging positions and offsets to try find a pattern with the difference between iOS4 & 5 with luck.

I got the concept and some code from http://developer.apple.com/videos/wwdc/2011/#advanced-scrollview-techniques (they use iOS5).

EDIT

Photos (that are put in the visiblePhotos array) are created with this methods:

 - (CGFloat)placeNewImageOnRight:(CGFloat)rightEdge {
    imageIndex = imageIndex < 0 ? ([reportItems count] - 1) : (imageIndex >= [reportItems count]  ? 0 : imageIndex);

    UIView *image = [self createItemContainer:[reportItems objectAtIndex:imageIndex]];
    [visiblePhotos addObject:image];

    CGRect frame = [image frame];
    frame.origin.x = rightEdge;
    frame.origin.y = [imageContainerView bounds].size.height - frame.size.height;

    [image setFrame:frame];

    imageIndex += 1;

    return CGRectGetMaxX(frame);
}

- (CGFloat)placeNewImageOnLeft:(CGFloat)leftEdge {
    imageIndex = imageIndex < 0 ? ([reportItems count] - 1) : (imageIndex >= [reportItems count] ? 0 : imageIndex);

    UIView *image = [self createItemContainer:[reportItems objectAtIndex:imageIndex]];
    [visiblePhotos insertObject:image atIndex:0];

    CGRect frame = [image frame];
    frame.origin.x = leftEdge - frame.size.width;
    frame.origin.y = [imageContainerView bounds].size.height - frame.size.height;
    [image setFrame:frame];

    imageIndex -= 1;

    return CGRectGetMinX(frame);
}

[self createItemContainer:[reportItems objectAtIndex:imageIndex]] creates a UIVeiw that contains a photo`.

The left and right methods are basically the same and are invoked by the method below. And tileImagesFromMinX is invoked on every frame by - (void)layoutSubviews.

- (void)tileImagesFromMinX:(CGFloat)minVisibleX toMaxX:(CGFloat)maxVisibleX {
    if ([visiblePhotos count] == 0) {
        [self placeNewImageOnRight:minVisibleX];
    }

    UIView *lastImage = [visiblePhotos lastObject];
    CGFloat rightEdge = CGRectGetMaxX([lastImage frame]);
    while (rightEdge < maxVisibleX) {
        rightEdge = [self placeNewImageOnRight:rightEdge];
    }

    UIView *firstImage = [visiblePhotos objectAtIndex:0];
    CGFloat leftEdge = CGRectGetMinX([firstImage frame]);
    while (leftEdge > minVisibleX) {
        leftEdge = [self placeNewImageOnLeft:leftEdge];
    } 

    lastImage = [visiblePhotos lastObject];
    while ([lastImage frame].origin.x > maxVisibleX) {
        [lastImage removeFromSuperview];
        [visiblePhotos removeLastObject];
        lastImage = [visiblePhotos lastObject];
    }    

    firstImage = [visiblePhotos objectAtIndex:0];
    while (CGRectGetMaxX([firstImage frame]) < minVisibleX) {
        [firstImage removeFromSuperview];
        [visiblePhotos removeObjectAtIndex:0];
        firstImage = [visiblePhotos objectAtIndex:0];
    }
}

1条回答
看我几分像从前
2楼-- · 2019-04-09 23:43

I've done a bunch of investigating and determined that you can't just set self.contentOffset = ...; and expect the scroll view to scroll/decelerate properly in iOS 4.3. So, even without the image tiling (and in fact you will notice this more clearly if you remove your images and just put a background on your image container view), your implementation of recenterIfNecessary will not work under 4.3 (this also affects the sample code on the developer site!).

Unfortunately, it seems the only way to work around this is to override -setContentOffset: and modify the offset there before calling super. Have a look at this question for some guidance on how to get that working correctly. I hope for your sake that this method will also work in iOS 5...

查看更多
登录 后发表回答