web-dev-qa-db-ja.com

UIScrollView無限スクロール

無限(水平)スクロールでスクロールビューを設定しようとしています。

前方へのスクロールは簡単です-scrollViewDidScrollを実装し、contentOffsetが終わりに近づいたら、scrollview contentsizeを大きくして、スペースにデータを追加します(これは後で壊滅的な影響に対処する必要があります!)

私の問題はスクロールバックです-計画は、スクロールビューの先頭に近づいたときに確認し、コンテンツサイズを大きくしたときに、既存のコンテンツを移動し、新しいデータを先頭に追加してから、重要な調整を行うことです。 contentOffsetにより、ビューポートの下のデータは同じままになります。

これは、ゆっくりスクロールする(またはページングを有効にする)と完全に機能しますが、速くすると(それほど速くはありません!)、気が狂います!コードは次のとおりです。

- (void) scrollViewDidScroll:(UIScrollView *)scrollView {

    float pageNumber = scrollView.contentOffset.x / 320;
    float pageCount = scrollView.contentSize.width / 320;

    if (pageNumber > pageCount-4) {
        //Add 10 new pages to end
        mainScrollView.contentSize = CGSizeMake(mainScrollView.contentSize.width + 3200, mainScrollView.contentSize.height);
        //add new data here at (320*pageCount, 0);
    }

    //*** the problem is here - I use updatingScrollingContent to make sure its only called once (for accurate testing!)
    if (pageNumber < 4 && !updatingScrollingContent) {

        updatingScrollingContent = YES;

        mainScrollView.contentSize = CGSizeMake(mainScrollView.contentSize.width + 3200, mainScrollView.contentSize.height);
        mainScrollView.contentOffset = CGPointMake(mainScrollView.contentOffset.x + 3200, 0);
        for (UIView *view in [mainContainerView subviews]) {
            view.frame = CGRectMake(view.frame.Origin.x+3200, view.frame.Origin.y, view.frame.size.width, view.frame.size.height);
        }
        //add new data here at (0, 0);      
    }

    //** MY CHECK!
    NSLog(@"%f", mainScrollView.contentOffset.x);
}

スクロールが発生すると、ログは次のようになります。1286.5000001285.500000 1284.500000 1283.500000 1282.500000 1281.500000 1280.500000

次に、pageNumber <4の場合(最初に近づいています):4479.500000 4479.500000

すごい! -しかし、数は4,000秒で減少し続けるはずですが、次のログエントリは次のようになります:1278.00001277.000000 1276.5000001275.500000など。

中断したところから続けます!

記録のために、ゆっくりスクロールすると、ログは次のようになります。1294.5000001290.000000 1284.500000 1280.500000 4476.000000 4476.000000 4473.000000 4470.000000 4467.500000 4464.000000 4460.5000004457.500000など。

何か案は????

ありがとう

ベン。

16
Ben Robinson

そこにそれらの数値を設定しているものは何でも、contentOffsetをその手の下に設定することにあまり感銘を受けない可能性があります。そのため、contentOffsetが変更されたかどうかを確認せずに、次の瞬間にcontentOffsetと見なすものを設定し続けます。

UIScrollViewをサブクラス化し、魔法をsetContentOffsetメソッドに入れます。私の経験では、すべてのコンテンツオフセットの変更はそのメソッドを通過します。内部スクロールによって引き起こされるコンテンツオフセットの変更も同様です。 [super setContentOffset:..]ある時点で、メッセージを実際のUIScrollViewに渡します。

たぶん、そこにシフトアクションを入れると、うまくいくでしょう。少なくともcontentOffsetの3000オフ設定を検出し、メッセージを渡す前に修正することができます。 contentOffsetメソッドもオーバーライドする場合は、仮想の無限コンテンツサイズを作成できるかどうかを確認し、それを「内部」で実際の比率に縮小することができます。

7
mvds

私はApple OPで同様のアイデアを使用して無限スクロールを実装するための本当に素晴らしいサンプルアプリを見つけました。非常にシンプルで最も重要なのはティアリングなし

http://developer.Apple.com/library/ios/#samplecode/StreetScroller/Introduction/Intro.html

彼らは、UIScrollViewでlayoutSubviewsが呼び出されるたびに「コンテンツの再センタリング」を実装しました。

私が行った唯一の調整は、古いタイルを捨てて新しいタイルを割り当てるのではなく、「タイル効果」をリサイクルすることでした。

18
rwyland

この問題に直面したとき、私は3つの画像を持っていました。これらは、どちらの方向にも無限にスクロールできる必要がありました。最適な解決策は、おそらく3つの画像をロードし、ユーザーが右端/左端の画像に移動したときにコンテンツパネルを変更することですが、もっと簡単な解決策を見つけました。 (コードを編集しましたが、タイプミスが含まれている可能性があります)

3つの画像の代わりに、次のように5つの画像でスクロールビューを設定しました。

3 | 1 | 2 | 3 | 1

それに応じてコンテンツビューを計算しましたが、コンテンツサイズは固定されています。コードは次のとおりです。

for ( NSUInteger i = 1; i <= NO_OF_IMAGES_IN_SCROLLVIEW + 2 ; i ++) {

    UIImage *image;
    if ( i  % 3 == 1){

        image = [UIImage imageNamed:[NSString stringWithFormat:@"img1.png"]];
    }

    else if (i % 3 == 2 ) {

        image = [UIImage imageNamed:[NSString stringWithFormat:@"img2.png"]];

    }

    else {

        image = [UIImage imageNamed:[NSString stringWithFormat:@"img3.png"]];
    }

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake((i-1)*_scrollView.frame.size.width, 0, _scrollView.frame.size.width, _scrollView.frame.size.height)];
    imageView.contentMode=UIViewContentModeScaleToFill;
    [imageView setImage:image];
    imageView.tag=i+1;
    [self.scrollView addSubview:imageView];
}

[self.scrollView setContentOffset:CGPointMake(self.scrollView.frame.size.width, 0)];
[scrMain setContentSize:CGSizeMake(self.scrollView.frame.size.width * ( NO_OF_IMAGES_IN_SCROLLVIEW + 2 ), self.scrollView.frame.size.height)];

画像が追加されたので、唯一のことは無限スクロールの錯覚を作成することです。そのために、ユーザーが外側の2つの画像のいずれかにスクロールしようとするたびに、ユーザーを3つのメイン画像に「テレポート」しました。ユーザーがそれを感じることができないように、アニメーション化されていないことを行うことが重要です。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {

    if (scrollView.contentOffset.x < scrollView.frame.size.width ){

        [scrollView scrollRectToVisible:CGRectMake(scrollView.contentOffset.x + 3 * scrollView.frame.size.width, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:NO];
    }
}

    else if ( scrollView.contentOffset.x > 4 * scrollView.frame.size.width  ){

        [scrollView scrollRectToVisible:CGRectMake(scrollView.contentOffset.x - 3 * scrollView.frame.size.width, 0, scrollView.frame.size.width, scrollView.frame.size.height) animated:NO];
    }
}
6

(私がこれを正しく投稿していることを願っています-私はここで新しいです!?)

mvdsはスポットでした-ありがとう-サブクラス化UIScrollViewは完全に機能します-新しいデータのシフトとロードはまだ実装していませんが、無限ループでスクロールするUIScrollViewがあります!

コードは次のとおりです。

#import "BRScrollView.h"

@implementation BRScrollView

- (id)awakeFromNib:(NSCoder *)decoder {
    offsetAdjustment = 0;
    [super initWithCoder:decoder];
    return self;
}

- (void)setContentOffset:(CGPoint)contentOffset {

    float realOffset = contentOffset.x + offsetAdjustment;

    //This happens when contentOffset has updated correctly - there is no need for the adjustment any more
    if (realOffset < expectedContentOffset-2000 || realOffset > expectedContentOffset+2000) {
        offsetAdjustment = 0;
        realOffset = contentOffset.x;
    }

    float pageNumber = realOffset / 320;
    float pageCount = self.contentSize.width / 320;

    if (pageNumber > pageCount-4) {
        offsetAdjustment -= 3200;
        realOffset -= 3200;
    }

    if (pageNumber < 4) {
        offsetAdjustment += 3200;
        realOffset += 3200; 
    }

    //Save expected offset for next call, and pass the real offset on
    expectedContentOffset = realOffset;     
    [super setContentOffset:CGPointMake(realOffset, 0)];


}

- (void)dealloc {
    [super dealloc];
}


@end

ノート:

実際に無限ループが必要な場合は、数値を調整する必要があります。このコードは、エッジに近づくと通知され、中央を少し超えたところに移動します。

私のcontentSizeは6720(21ページ)です

水平方向にスクロールすることにのみ関心があるので、x値を保存し、yを0にハードコーディングするだけです。

ベン

5
Ben Robinson

私はあなたの問題を解決するためにサンプルプロジェクトを作りました。

あなたはからコードをダウンロードすることができます

https://code.google.com/p/iphone-infinite-horizo​​ntal-scroller/

これは両方向に無限にスクロールし、外出先で画像をロードします。

5
Rajat Talwar

私はこの種のuiscrollviewを開発したので、私のプロジェクトを 両方向の無限UIScrollView で確認できます。

1
Quang Peter

私はあなたが望むことをするだけのUIScrollViewのサブクラスを作りました。両方向に同時にでも、どの方向にも永久にスクロールします。スムーズスクロールとページングスクロールをサポートします

https://github.com/vasvf/NPInfiniteScrollView

1
VasVF

これも見てください(ビデオはそれが何をするかについての簡単なアイデアをあなたに与えるでしょう): http://dev.doukasd.com/2011/04/infinite-scrolling-dial-control-for-ios /

水平ではありませんが、役に立つはずです。私はあなたが提案していることを試しましたが、スクロールビューがアニメーション化されている間、コンテンツオフセットを設定すると正しく見えず、アニメーションは常に壊れていました。

0
Dimitris

したがって、1つの問題は、scrollViewDidScroll:デリゲートメソッド内でcontentOffsetを設定すると、デリゲートメソッド内にいるときにデリゲートメソッドが再度起動することです。だから私がすることはデリゲートを削除する> setContentOffset>デリゲートを再度設定することです。

これが私のコードです:

#import "ViewController.h"

@interface ViewController () <UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (strong, nonatomic) NSMutableArray *labels;
@property (weak, nonatomic) UILabel *originLabel;

- (void)addScrollViewLabels;
- (void)adjustOrigins:(float)deltaX;

@end


@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // just some starting size
    CGSize size = self.scrollView.frame.size;
    CGSize contentSize = CGSizeMake(size.width * 4, size.height);
    [self.scrollView setContentSize:contentSize];

    // just some starting offset
    CGPoint contentOffset = CGPointMake((contentSize.width / 2), 0);
    [self.scrollView setContentOffset:contentOffset];

    [self addScrollViewLabels];
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    CGSize contentSize = scrollView.contentSize;
    CGSize size = scrollView.frame.size;
    CGPoint contentOffset = scrollView.contentOffset;

    const float kContentOffsetBuffer = 100;
    const float kContentSizeGrowth = (4 * size.width);

    BOOL shouldGrowContentSize = (contentOffset.x > (contentSize.width - size.width - kContentOffsetBuffer))
                                || (contentOffset.x < (kContentOffsetBuffer));
    if (shouldGrowContentSize) {
        // the contentOffset has reached a point where we need to grow the contentSize
        CGSize adjustedContentSize = CGSizeMake(contentSize.width + kContentSizeGrowth, contentSize.height);
        [self.scrollView setContentSize:adjustedContentSize];

        if(contentOffset.x < (kContentOffsetBuffer)) {
            // the growth needs to happen on the left side which means that we need to adjust the contentOffset to compensate for the growth.
            // this is not necessary when growth happens to the right since the contentOffset is the same.
            CGPoint adjustedContentOffset = CGPointMake(contentOffset.x + kContentSizeGrowth, contentOffset.y);
            [self.scrollView setDelegate:nil];
            [self.scrollView setContentOffset:adjustedContentOffset];
            [self.scrollView setDelegate:self];
            [self adjustOrigins:kContentSizeGrowth];
        }

        [self addScrollViewLabels];
    }
}


- (void)addScrollViewLabels {
    const float kOriginY = 100;

    if (!self.labels) {
        self.labels = [NSMutableArray array];
        float originX = [self.scrollView contentOffset].x;
        UILabel *label0 = [[UILabel alloc] initWithFrame:CGRectMake(originX, kOriginY, 100, 30)];
        label0.text = @"0";
        [self.scrollView addSubview:label0];
        [self.labels addObject:label0];
        self.originLabel = label0;
    }

    CGSize contentSize = [self.scrollView contentSize];
    const float kIncrementAmount = 75;
    NSInteger indexOfOriginLabel = [self.labels indexOfObject:self.originLabel];

    // add labels to the right
    UILabel *lastLabel = [self.labels lastObject];
    float lastOriginX = lastLabel.frame.Origin.x;
    for (float x = (lastOriginX + kIncrementAmount); (x < (contentSize.width)) ; x += kIncrementAmount) {
        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(x, kOriginY, 100, 30)];
        NSInteger indexFromOrigin = ([self.labels count] - indexOfOriginLabel);
        label.text = [@(indexFromOrigin) description];
        [self.scrollView addSubview:label];
        [self.labels addObject:label];
        [label setNeedsDisplay];
    }

    // add labels to the left
    UILabel *firstLabel = [self.labels firstObject];
    float firstOriginX = firstLabel.frame.Origin.x;
    for (float x = (firstOriginX - kIncrementAmount); (x >= 0) ; x -= kIncrementAmount) {
        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(x, kOriginY, 100, 30)];
        NSInteger indexFromOrigin = -(indexOfOriginLabel + 1);
        label.text = [@(indexFromOrigin) description];
        [self.scrollView addSubview:label];
        [self.labels insertObject:label atIndex:0];
        indexOfOriginLabel++;
        [label setNeedsDisplay];
    }
}

- (void)adjustOrigins:(float)deltaX {
    for (UILabel *label in self.labels) {
        CGRect frame = label.frame;
        frame.Origin.x += deltaX;
        [label setFrame:frame];
        [label setNeedsDisplay];
    }
}


@end
0
Spencer Hall

あなたはこの例を見ることができます

numbercolors=[[NSMutableArray alloc] init];

//total count of array is  49 

 numbercolors = [NSMutableArray arrayWithObjects:@"25",@"26",@"27",@"28",@"29",@"31",@"32",@"33",@"34",@"35","0",@"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9",@"10",@"11",@"12",@"13",@"14",@"15",@"16",@"17",@"18",@"19",@"20",@"21",@"22",@"23",@"24",@"25",@"26",@"27",@"28",@"29",@"30",@"31",@"32",@"33",@"34",@"35", @"0",@"1",@"2",@"3",nil];

  int x=2500;

 for (NSInteger index = 0; index < [numbercolors count]; index++)
 {
  UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];

  button.frame = CGRectMake(x ,0,29.0,77.0);

  button.tag = index;

  [button setTitle:[numbercolors objectAtIndex:index] forState:UIControlStateNormal];

  [button addTarget:self action:@selector(didTapButton:) 

  forControlEvents:UIControlEventTouchUpInside];

  [coloringScroll addSubview:button];

   x=x+70+29;
 } 
  [coloringScroll setContentSize:CGSizeMake(5000+ (29+70)*[numbercolors count], 1)];

  [coloringScroll setContentOffset:CGPointMake(2500+(29+70)*11, 0)];


- (void)scrollViewDidScroll:(UIScrollView *)scrollView

{

if (scrollView.contentOffset.x > 2500+(29+70)*4 + ((29+70)*36))
{

 [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x-((29+70)*36),  0)];

}

 if (scrollView.contentOffset.x < 2500+(29+70)*4)

{
 [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x+((29+70)*36), 

 0)];
}

}
0
virantporwal