簡體   English   中英

如何使用1000多個子視圖實現UIScrollView?

[英]How to implement UIScrollView with 1000+ subviews?

我正在努力編寫應用程序的部分應該像本機iphone照片應用程序。 看看Orielly的iphone sdk app開發書,它提供了一個實現這個所謂的頁面輕彈的示例代碼。 那里的代碼首先創建所有子視圖,然后隱藏/取消隱藏它們。 在給定時間,只有3個子視圖可見隱藏。 經過努力,我得到了應用程序,當時只有大約15頁。

一旦我添加了300頁,就會發現預先分配這么多子視圖的方法存在性能/內存問題。 然后我想可能是我的情況我應該只分配3個子視圖而不是隱藏/取消隱藏它們。 可能是我應該在運行時刪除/添加子視圖。 但無法弄清楚UIScrollView是否可以動態更新內容。 例如,在開始時,如UIScrollView所理解的,在屏幕上的不同x偏移(0,320,640)處有3個幀。 一旦用戶移動到第3頁,我如何確保我能夠添加第4頁並刪除第1頁但是UIScrollView不會混淆?

希望有這種問題的標准解決方案......有人可以指導嗎?

按照所說的,你可以只使用有限數量的資源來展示數千個元素(是的,它確實是一個Flyweight模式)。 這里有一些代碼可以幫助你做你想做的事情。

UntitledViewController類只包含一個UIScroll並將自己設置為其委托。 我們有一個NSArray,里面有NSString實例作為數據模型(其中可能有數千個NSStrings),我們希望使用水平滾動在UILabel中顯示每個。 當用戶滾動時,我們將UILabels移動到左側,另一個在右側,這樣一切都准備好進行下一個滾動事件。

這是界面,相當簡單:

@interface UntitledViewController : UIViewController <UIScrollViewDelegate>
{
@private
    UIScrollView *_scrollView;

    NSArray *_objects;

    UILabel *_detailLabel1;
    UILabel *_detailLabel2;
    UILabel *_detailLabel3;
}

@end

這是該類的實現:

@interface UntitledViewController ()
- (void)replaceHiddenLabels;
- (void)displayLabelsAroundIndex:(NSInteger)index;
@end

@implementation UntitledViewController

- (void)dealloc 
{
    [_objects release];
    [_scrollView release];
    [_detailLabel1 release];
    [_detailLabel2 release];
    [_detailLabel3 release];
    [super dealloc];
}

- (void)viewDidLoad 
{
    [super viewDidLoad];

    _objects = [[NSArray alloc] initWithObjects:@"first", @"second", @"third", 
                @"fourth", @"fifth", @"sixth", @"seventh", @"eight", @"ninth", @"tenth", nil];

    _scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 460.0)];
    _scrollView.contentSize = CGSizeMake(320.0 * [_objects count], 460.0);
    _scrollView.showsVerticalScrollIndicator = NO;
    _scrollView.showsHorizontalScrollIndicator = YES;
    _scrollView.alwaysBounceHorizontal = YES;
    _scrollView.alwaysBounceVertical = NO;
    _scrollView.pagingEnabled = YES;
    _scrollView.delegate = self;

    _detailLabel1 = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 460.0)];
    _detailLabel1.textAlignment = UITextAlignmentCenter;
    _detailLabel1.font = [UIFont boldSystemFontOfSize:30.0];
    _detailLabel2 = [[UILabel alloc] initWithFrame:CGRectMake(320.0, 0.0, 320.0, 460.0)];
    _detailLabel2.textAlignment = UITextAlignmentCenter;
    _detailLabel2.font = [UIFont boldSystemFontOfSize:30.0];
    _detailLabel3 = [[UILabel alloc] initWithFrame:CGRectMake(640.0, 0.0, 320.0, 460.0)];
    _detailLabel3.textAlignment = UITextAlignmentCenter;
    _detailLabel3.font = [UIFont boldSystemFontOfSize:30.0];

    // We are going to show all the contents of the _objects array
    // using only these three UILabel instances, making them jump 
    // right and left, replacing them as required:
    [_scrollView addSubview:_detailLabel1];
    [_scrollView addSubview:_detailLabel2];
    [_scrollView addSubview:_detailLabel3];

    [self.view addSubview:_scrollView];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [_scrollView flashScrollIndicators];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self displayLabelsAroundIndex:0];
}

- (void)didReceiveMemoryWarning 
{
    // Here you could release the data source, but make sure
    // you rebuild it in a lazy-loading way as soon as you need it again...
    [super didReceiveMemoryWarning];
}

#pragma mark -
#pragma mark UIScrollViewDelegate methods

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    // Do some initialization here, before the scroll view starts moving!
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    [self replaceHiddenLabels];
}

- (void)displayLabelsAroundIndex:(NSInteger)index
{
    NSInteger count = [_objects count];
    if (index >= 0 && index < count)
    {
        NSString *text = [_objects objectAtIndex:index];
        _detailLabel1.frame = CGRectMake(320.0 * index, 0.0, 320.0, 460.0);
        _detailLabel1.text = text;
        [_scrollView scrollRectToVisible:CGRectMake(320.0 * index, 0.0, 320.0, 460.0) animated:NO];

        if (index < (count - 1))
        {
            text = [_objects objectAtIndex:(index + 1)];
            _detailLabel2.frame = CGRectMake(320.0 * (index + 1), 0.0, 320.0, 460.0);
            _detailLabel2.text = text;
        }

        if (index > 0)
        {
            text = [_objects objectAtIndex:(index - 1)];
            _detailLabel3.frame = CGRectMake(320.0 * (index - 1), 0.0, 320.0, 460.0);
            _detailLabel3.text = text;
        }
    }
}

- (void)replaceHiddenLabels
{
    static const double pageWidth = 320.0;
    NSInteger currentIndex = ((_scrollView.contentOffset.x - pageWidth) / pageWidth) + 1;

    UILabel *currentLabel = nil;
    UILabel *previousLabel = nil;
    UILabel *nextLabel = nil;

    if (CGRectContainsPoint(_detailLabel1.frame, _scrollView.contentOffset))
    {
        currentLabel = _detailLabel1;
        previousLabel = _detailLabel2;
        nextLabel = _detailLabel3;
    }
    else if (CGRectContainsPoint(_detailLabel2.frame, _scrollView.contentOffset))
    {
        currentLabel = _detailLabel2;
        previousLabel = _detailLabel1;
        nextLabel = _detailLabel3;
    }
    else
    {
        currentLabel = _detailLabel3;
        previousLabel = _detailLabel1;
        nextLabel = _detailLabel2;
    }

    currentLabel.frame = CGRectMake(320.0 * currentIndex, 0.0, 320.0, 460.0);
    currentLabel.text = [_objects objectAtIndex:currentIndex];

    // Now move the other ones around
    // and set them ready for the next scroll
    if (currentIndex < [_objects count] - 1)
    {
        nextLabel.frame = CGRectMake(320.0 * (currentIndex + 1), 0.0, 320.0, 460.0);
        nextLabel.text = [_objects objectAtIndex:(currentIndex + 1)];
    }

    if (currentIndex >= 1)
    {
        previousLabel.frame = CGRectMake(320.0 * (currentIndex - 1), 0.0, 320.0, 460.0);
        previousLabel.text = [_objects objectAtIndex:(currentIndex - 1)];
    }
}

@end

希望這可以幫助!

UIScrollView只是UIView的子類,因此可以在運行時添加和刪除子視圖。 假設您有固定寬度的照片(320px)並且有300張,那么您的主視圖將是300 * 320像素寬。 創建滾動視圖時,將框架初始化為該寬度。

因此滾動視圖的框架將具有尺寸(0,0)到(96000,480)。 無論何時添加子視圖,都必須更改其框架,使其適合父視圖中的正確位置。

所以我們假設我們將第4張照片添加到滾動視圖中。 它的幀是從(960,480)到(1280,480)。 如果你能以某種方式將索引與每張圖片相關聯,這很容易計算。 然后使用它來計算索引從0開始的圖片幀:

Top-Left -- (320 * (index - 1), 0)

Bottom-Right -- (320 * index, 480)

刪除第一張圖片/子視圖應該很容易。 保持當前屏幕上的3個子視圖的數組。 每當您向屏幕添加新的子視圖時,也將其添加到此數組的末尾,然后從屏幕中刪除此數組中的第一個子視圖。

非常感謝Adrian提供的非常簡單和強大的代碼示例。 這段代碼只有一個問題:當用戶進行“雙滾動”時(IE當他沒有等待動畫停止並一次又一次地重新滾動滾動視圖時)。

在這種情況下,只有在調用“scrollViewDidEndDecelerating”方法時,3個子視圖位置的刷新才有效,結果是在屏幕上顯示子視圖之前的延遲。

通過添加幾行代碼可以輕松避免這種情況:

在界面中,只需添加:

int refPage, currentPage;

在實現中,在“viewDidLoad”方法中初始化refPage和currentPage,如下所示:

refpage = 0; 
curentPage = 0;

在實現中,只需添加“scrollViewDidScroll”方法,如下所示:

- (void)scrollViewDidScroll:(UIScrollView *)sender{
    int currentPosition = floor(_scrollView.contentOffset.x);
    currentPage = MAX(0,floor(currentPosition / 340)); 
//340 is the width of the scrollview...
    if(currentPage != refPage)  { 
        refPage = currentPage;
        [self replaceHiddenLabels];
        }
    }

etvoilà!

現在,即使用戶從不停止動畫並且從不調用“scrollViewDidEndDecelerating”方法,也會在正確的位置正確替換子視圖!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM