問題描述
我正在嘗試提高 UIScrollView
的滾動性能.我上面有很多 UIButtons
(可能有數百個):每個按鈕都有一個 png 圖像設置為背景.
I'm trying to increase the scrolling performance of my UIScrollView
. I have a lot of UIButtons
on it (they could be hundreds): every button has a png image set as background.
如果我在整個滾動出現時嘗試加載它,會花費太多時間.在網上搜索,我找到了一種優化它的方法(在滾動時加載和卸載頁面),但是每次我必須加載新頁面時滾動都會有一點停頓.
If I try to load the entire scroll when it appears, it takes too much time. Searching on the web, I've found a way to optimize it (loading and unloading pages while scrolling), but there's a little pause in scrolling everytime I have to load a new page.
你有什么建議讓它滾動順暢嗎?
Do you have any advice to make it scroll smoothly?
您可以在下面找到我的代碼.
Below you can find my code.
- (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;
}
推薦答案
這里有一些建議:
1) 首先,了解 scrollViewDidScroll:
將在用戶滾動時連續調用.不只是每個頁面一次.因此,我會確保您的邏輯能夠確保您的加載所涉及的實際工作僅在每頁觸發一次.
1) First, understand that scrollViewDidScroll:
will get called continuously, as the user scrolls. Not just once per page. So, I would make sure that you have logic that ensures that the real work involved in your loading is only triggered once per page.
通常,我會保留一個類 ivar,例如 int lastPage
.然后,當調用 scrollViewDidScroll:
時,我計算新的 當前頁面.只有當它與 ivar 不同時,我才會觸發加載.當然,那你需要將動態計算的索引(currentPage
in your code)保存在你的ivar中.
Typically, I will keep a class ivar like int lastPage
. Then, as scrollViewDidScroll:
is called, I calculate the new current page. Only if it differs from the ivar do I trigger loading. Of course, then you need to save the dynamically calculated index (currentPage
in your code) in your ivar.
2) 另一件事是我盡量不做 scrollViewDidScroll:
方法中的所有密集工作.我只在那里觸發它.
2) The other thing is that I try not to do all the intensive work in the scrollViewDidScroll:
method. I only trigger it there.
因此,例如,如果您將發布的大部分代碼放入名為 loadAndReleasePages
的方法中,那么您可以在 scrollViewDidScroll:
方法中執行此操作,將執行推遲到 scrollViewDidScroll:
完成后:
So, for example, if you take most of the code you posted and put it in a method called loadAndReleasePages
, then you could do this in the scrollViewDidScroll:
method, which defers the execution until after scrollViewDidScroll:
finishes:
- (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];
}
}
這是我從早期 iOS 版本開始使用的代碼,因此您當然也可以將 performSelector:
調用替換為異步 GCD 方法調用.關鍵是不要在內部滾動視圖委托回調.
This is code that I've used since early iOS versions, so you can certainly replace the performSelector:
call with an asynchronous GCD method call, too. The point is not to do it inside the scroll view delegate callback.
3) 最后,您可能希望嘗試使用略有不同的算法來計算滾動視圖何時實際滾動到您想要加載和釋放內容的程度.您目前使用:
3) Finally, you might want to experiment with slightly different algorithms for calculating when the scroll view has actually scrolled far enough that you want to load and release content. You currently use:
int currentPage=(int)(offset.y / 322.0f);
這將根據 /
運算符和 float
到 int
轉換的方式產生整數頁碼.那可能沒問題.但是,您可能會發現您需要一個稍微不同的算法,以在稍微不同的點觸發加載.例如,您可能希望在頁面從一個頁面滾動到下一個頁面時恰好滾動 50% 時觸發內容加載.或者,您可能只想在您幾乎完全離開第一頁(可能 90%)時才觸發它.
which will yield integer page numbers based on the way the /
operator, and the float
to int
cast works. That may be fine. However, you might find that you want a slightly different algorithm, to trigger the loading at a slightly different point. For example, you might want to trigger the content load as the page has scrolled exactly 50% from one page to the next. Or you might want to trigger it only when you're almost completely off the first page (maybe 90%).
我相信我編寫的一個滾動密集型應用程序確實需要我在加載大量資源時調整頁面滾動的精確時刻.因此,我使用了一個稍微不同的舍入函數來確定當前頁面何時發生了變化.
I believe that one scrolling intensive app I wrote actually did require me to tune the precise moment in the page scroll when I did the heavy resource loading. So, I used a slightly different rounding function to determine when the current page has changed.
你也可以玩弄它.
再查看您的代碼后,我還看到您正在做的工作是加載和縮放圖像.這實際上也是一個后臺線程的候選者.您可以從文件系統加載 UIImage
,并在后臺線程上進行縮放,然后使用 GCD 最終設置按鈕的背景圖像(加載的圖像)并將其框架更改回 UI線程.
after looking at your code a little more, I also see that the work you're doing is loading and scaling images. This is actually also a candidate for a background thread. You can load the UIImage
from the filesystem, and do your scaling, on the background thread, and use GCD to finally set the button's background image (to the loaded image) and change its frame back on the UI thread.
UIImage
自 iOS 4.0 起在后臺線程中使用是安全的.
UIImage
is safe to use in background threads since iOS 4.0.
這篇關于iOS UIScrollView 性能的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!