Как реализовать UIScrollView с 1000+ подпредставлениями? - PullRequest
12 голосов
/ 17 января 2010

Я борюсь с написанием части приложения, которое должно вести себя как родное приложение для фотографий iphone. Посмотрел книгу по разработке приложений для iphone sdk от Orielly, в которой приведен пример кода для реализации этого так называемого перелистывания страниц. Код там сначала создал все подпредставления, а затем скрыл / показал их. В данный момент видны только 3 подпредставления, остальные скрыты. После долгих усилий я начал работать с приложением, которое в то время имело всего около 15 страниц.

Как только я добавил 300 страниц, стало ясно, что при таком подходе предварительного выделения большого количества подпредставлений возникают проблемы с производительностью / памятью. Тогда я подумал, что для моего случая я должен просто выделить 3 подпредставления и вместо того, чтобы скрывать / показывать их. Может быть, я должен просто удалить / добавить подпредставления во время выполнения. Но не могу понять, может ли UIScrollView динамически обновлять содержимое. Например, в начале есть 3 кадра с различными смещениями по оси X (0, 320, 640) от экрана, как это понимает UIScrollView. Как только пользователь переходит на 3-ю страницу, как мне убедиться, что я могу добавить 4-ю страницу и удалить 1-ю страницу, и все же UIScrollView не запутывается?

Надеясь, что есть стандартное решение для такого рода проблем ... может кто-нибудь направить?

Ответы [ 3 ]

15 голосов
/ 17 января 2010

После того, что было сказано, вы можете показать тысячи элементов, используя только ограниченное количество ресурсов (и да, это действительно что-то вроде модели Flyweight). Вот код, который может помочь вам сделать то, что вы хотите.

Класс UntitledViewController просто содержит UIScroll и устанавливает себя в качестве его делегата. У нас есть NSArray с экземплярами NSString внутри в качестве модели данных (в ней могут быть тысячи строк NSString), и мы хотим показать каждый из них в 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

Надеюсь, это поможет!

7 голосов
/ 17 января 2010

UIScrollView - это просто подкласс UIView, поэтому можно добавлять и удалять подпредставления во время выполнения. Если у вас есть фотографии фиксированной ширины (320 пикселей) и их 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 подпредставлений в настоящее время на экране. Всякий раз, когда вы добавляете новое подпредставление на экран, также добавляйте его в конец этого массива, а затем также удаляйте первое подпредставление в этом массиве с экрана.

6 голосов
/ 07 апреля 2010

Большое спасибо Адриану за его очень простой и мощный пример кода. Была только одна проблема с этим кодом: когда пользователь сделал «двойную прокрутку» (т. Е. Когда он не ждал остановки анимации и снова и снова прокручивал обзор прокрутки).

В этом случае обновление для позиции 3 подпредставлений действует только тогда, когда вызывается метод "scrollViewDidEndDecelerating", и результатом является задержка до появления подпредставлений на экране.

Этого легко избежать, добавив несколько строк кода:

в интерфейсе, просто добавьте это:

int refPage, currentPage;

в реализации, инициализировать refPage и currentPage в методе "viewDidLoad" следующим образом:

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];
        }
    }

et voilà!

Теперь подпредставления корректно заменяются в правильных положениях, даже если пользователь никогда не останавливает анимацию и если метод scrollViewDidEndDecelerating никогда не вызывается!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...