3

のスクロールパフォーマンスを向上させようとしていUIScrollViewます。私はそれをたくさん持っていUIButtonsます(それらは数百になる可能性があります):すべてのボタンには背景として設定されたpng画像があります。

スクロールが表示されたときに全体を読み込もうとすると、時間がかかりすぎます。Webで検索して、それを最適化する方法(スクロールのページのロードとアンロード)を見つけましたが、新しいページをロードする必要があるたびに、スクロールが少し一時停止します。

スムーズにスクロールするためのアドバイスはありますか?

以下に私のコードがあります。

- (void)scrollViewDidScroll:(UIScrollView *)tmpScrollView {
    CGPoint offset = tmpScrollView.contentOffset;
    //322 is the height of 2*2 buttons (a page for me)
    int currentPage=(int)(offset.y / 322.0f);
    if(lastContentOffset>offset.y){
        pageToRemove = currentPage+3;
        pageToAdd = currentPage-3;
    }
    else{
        pageToRemove = currentPage-3;
        pageToAdd = currentPage+3;
    }
    //remove the buttons outside the range of the visible pages
    if(pageToRemove>=0 && pageToRemove<=numberOfPages && currentPage<=numberOfPages){
        for (UIView *view in scrollView.subviews)
        {
            if ([view isKindOfClass:[UIButton class]]){
                if(lastContentOffset<offset.y && view.frame.origin.y<pageToRemove*322){
                    [view removeFromSuperview];
                }
                else if(lastContentOffset>offset.y && view.frame.origin.y>pageToRemove*322){
                    [view removeFromSuperview];
                }
            }
        }
    }
    if(((lastContentOffset<offset.y && lastPageToAdd+1==pageToAdd) || (lastContentOffset>offset.y && lastPageToAdd-1==pageToAdd)) && pageToAdd>=0 && pageToAdd<=numberOfPages){
        int tmpPage=0;
        if((lastContentOffset<offset.y && lastPageToAdd+1==pageToAdd)){
            tmpPage=pageToAdd-1;
        }
        else{
            tmpPage=pageToAdd;
        }
        //the images are inside the application folder
        NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
        for(int i=0;i<4;i++){
            UIButton* addButton=[[UIButton alloc] init];
            addButton.layer.cornerRadius=10.0;
            if(i + (tmpPage*4)<[imagesCatalogList count]){
                UIImage* image=[UIImage imageWithContentsOfFile:[NSString stringWithFormat: @"%@/%@",docDir,[imagesCatalogList objectAtIndex:i + (tmpPage*4)]]];
                if(image.size.width>image.size.height){
                    image=[image scaleToSize:CGSizeMake(image.size.width/(image.size.height/200), 200.0)];
                    CGImageRef ref = CGImageCreateWithImageInRect(image.CGImage, CGRectMake((image.size.width-159.5)/2,(image.size.height-159.5)/2, 159.5, 159.5));
                    image = [UIImage imageWithCGImage:ref];
                }
                else if(image.size.width<image.size.height){
                    image=[image scaleToSize:CGSizeMake(200.0, image.size.height/(image.size.width/200))];
                    CGImageRef ref = CGImageCreateWithImageInRect(image.CGImage, CGRectMake((image.size.width-159.5)/2, (image.size.height-159.5)/2, 159.5, 159.5));
                    image = [UIImage imageWithCGImage:ref];
                }
                else{
                    image=[image scaleToSize:CGSizeMake(159.5, 159.5)];
                }


                [addButton setBackgroundImage:image forState:UIControlStateNormal];
                image=nil;
                addButton.frame=CGRectMake(width, height, 159.5, 159.5);
                NSLog(@"width %i height %i", width, height);
                addButton.tag=i + (tmpPage*4);
                [addButton addTarget:self action:@selector(modifyImage:) forControlEvents:UIControlEventTouchUpInside];
                [tmpScrollView addSubview:addButton];
                addButton=nil;
                photos++;
            }
        }
    }
    lastPageToAdd=pageToAdd;
    lastContentOffset=offset.y;
}
4

4 に答える 4

4

いくつかの推奨事項を次に示します。

1)scrollViewDidScroll:まず、ユーザーがスクロールすると、 が継続的に呼び出されることを理解します。1ページに 1 回だけではありません。そのため、ロードに関連する実際の作業がページごとに 1回だけトリガーされるようにするロジックがあることを確認します。

通常、私は のようなクラス ivar を保持しますint lastPage。次に、呼ばれるように、新しい現在のページscrollViewDidScroll:を計算します。ivar と異なる場合にのみ、読み込みをトリガーします。もちろん、動的に計算されたインデックス (コード内) を ivar に保存する必要があります。currentPage

2)もう 1 つは、メソッド内のすべての集中的な作業を行わないようにしていることscrollViewDidScroll:です。私はそこでそれをトリガーするだけです。

したがって、たとえば、投稿したコードのほとんどを取得して というメソッドに配置した場合loadAndReleasePages、メソッドでこれを行うことができます。これにより、終了scrollViewDidScroll:後まで実行が延期されます。scrollViewDidScroll:

- (void)scrollViewDidScroll:(UIScrollView *)tmpScrollView {
    CGPoint offset = tmpScrollView.contentOffset;
    //322 is the height of 2*2 buttons (a page for me)
    int currentPage = (int)(offset.y / 322.0f);

    if (currentPage != lastPage) {
        lastPage = currentPage;
        // we've changed pages, so load and release new content ...
        // defer execution to keep scrolling responsive
        [self performSelector: @selector(loadAndReleasePages) withObject: nil afterDelay:0];
    }
}

performSelector:これは初期の iOS バージョンから使用してきたコードなので、呼び出しを非同期 GCD メソッド呼び出しに置き換えることもできます。要点は、スクロール ビュー デリゲート コールバックで実行しないことです。

3)最後に、スクロール ビューが実際にコンテンツを読み込んで解放するのに十分な距離までスクロールしたときを計算するために、少し異なるアルゴリズムを試してみることをお勧めします。現在使用している:

int currentPage=(int)(offset.y / 322.0f);

/これは、演算子の方法とfloattointキャストの動作に基づいて整数のページ番号を生成します。それでいいかもしれません。ただし、わずかに異なるポイントでロードをトリガーするために、わずかに異なるアルゴリズムが必要になる場合があります。たとえば、あるページから次のページにちょうど 50% スクロールしたときに、コンテンツの読み込みをトリガーしたい場合があります。または、最初のページからほぼ完全に離れたとき (おそらく 90%) にのみトリガーすることもできます。

私が作成したあるスクロール集中型アプリでは、大量のリソースをロードしたときに、ページ スクロールの正確な瞬間を調整する必要があったと思います。そのため、現在のページがいつ変更されたかを判断するために、少し異なる丸め関数を使用しました。

あなたもそれをいじるかもしれません。

編集:コードをもう少し見てみると、あなたが行っている作業は画像の読み込みとスケーリングであることがわかります。これは、実際にはバックグラウンド スレッドの候補でもあります。ファイルシステムからを読み込み、UIImageバックグラウンド スレッドでスケーリングを行い、GCD を使用して最終的にボタンの背景画像を (読み込まれた画像に) 設定し、そのフレームを UI スレッドに戻すことができます。

UIImageiOS 4.0 以降、バックグラウンド スレッドで安全に使用できます。

于 2012-12-12T22:18:49.720 に答える
3

プロファイリングが完了するまで、コードの行に触れないでください。Xcode には、まさにこの目的のための優れたツールが含まれています。

  1. まず、Xcode で、シミュレーターではなく実際のデバイスにビルドしていることを確認します。

  2. Xcode で、[製品] メニューから [プロファイル] を選択します。

  3. Instrumentsが開いたら、Core Animationインストゥルメントを選択します

  4. アプリで、プロファイリングするスクロール ビューをスクロールします。

上部にリアルタイム FPS が表示され、下部には、合計実行時間に基づくすべての関数とメソッド呼び出しの内訳が表示されます。独自のコードでメソッドに到達するまで、最大回数のドリルダウンを開始します。Command + E を押して右側のパネルを表示すると、クリックした各関数とメソッド呼び出しの完全なスタック トレースが表示されます。

あとは、最も「高価な」関数とメソッドの呼び出しを排除または最適化し、より高い FPS を検証するだけです。

こうすることで、盲目的に最適化したり、パフォーマンスに実質的な影響を与えない変更を加えたりして時間を無駄にすることがなくなります。


私の答えは、スクロール ビューとテーブル ビューのパフォーマンスを改善するためのより一般的なアプローチです。特定の懸念事項のいくつかに対処するために、高度なスクロール ビューの使用に関する次の WWDC ビデオをご覧になることを強くお勧めします。 -テクニック

于 2012-12-12T22:43:59.117 に答える
1

私の最も一般的な解決策は、ビューをラスタライズすることです:

_backgroundView.layer.shouldRasterize = YES;
_backgroundView.layer.rasterizationScale = [[UIScreen mainScreen] scale];

しかし、すべての状況で機能するとは限りません..試してみてください

于 2013-09-25T06:01:41.517 に答える
1

あなたのパフォーマンスを殺している可能性が高い行は次のとおりです。

addButton.layer.cornerRadius=10.0;

なんで?cornerRadius のパフォーマンスがひどいことがわかりました! それを取り出してください... 大幅なスピードアップが保証されています。

編集:この回答は、何をすべきかを非常に明確に要約しています。

https://stackoverflow.com/a/6254531/537213

于 2012-12-12T22:51:27.853 に答える