2010-01-16 4 views
11

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

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

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

+1

не конкретный ответ для вас, но этот тип проблемы часто решается с помощью шаблона мухи: http://en.wikipedia.org/wiki/Flyweight_pattern –

+0

Любой, кто приходит к этому вопросу сейчас - используйте 'UICollectionView' ... он не был доступен во время этого вопроса, но он сейчас и сделает это за вас. –

ответ

7

UIScrollView - это всего лишь подкласс UIView, поэтому можно добавлять и удалять подпункты во время выполнения. Предполагая, что у вас есть фотографии с фиксированной шириной (320 пикселей), и их 300, тогда ваш основной вид будет 300 * 320 пикселей в ширину. При создании представления прокрутки инициализируйте рамку так широко.

Таким образом, рамка вида прокрутки будет иметь размеры (от 0, 0) до (96000, 480). Всякий раз, когда вы добавляете subview, вам придется изменить его фрейм, чтобы он соответствовал правильной позиции в своем родительском представлении.

Итак, мы добавляем фотографию в список прокрутки. Это будет рамка от (960, 480) до (1280, 480). Это легко вычислить, если вы можете каким-то образом связать индекс с каждым изображением. Затем использовать для расчета кадрировать снимок, где индексы начинаются с 0:

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

в

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

Удаление первого снимка/подвид должно быть легко. Храните массив из трех подзонов, отображаемых в настоящее время на экране. Всякий раз, когда вы добавляете новое представление на экран, также добавляйте его в конец этого массива, а затем удаляете первое подвью в этом массиве с экрана.

+0

То, что вы говорите, имеет смысл. Позвольте мне сделать снимок, а затем обновить эту тему. – climbon

+1

Теперь он работает. Спасибо за разъяснение, хотя я закончил делать это несколько иначе (чтобы свести к минимуму изменения существующего кода). Мой существующий код (заимствованный из примера в упомянутой книге) имел ширину рамки scrollView = 320x3. Используя свойство myScrollView.contentOffset, код управляет отображением из 3 видов. Раньше в моем массиве было 300 просмотров (выделено при запуске), теперь оно содержит только 3. Если пользователь добирается до 3-й страницы, я удаляю первую страницу и вставляю четвертый и настраиваю видимость с использованием свойства contentOffset. Если он добирается до 1-го, я удаляю 3-й и вставляю один до 1-го и т. Д. – climbon

+0

Я не знал о свойстве contentOffset. Мне нужно что-то похожее на непрерывную прокрутку для моих приложений, и, безусловно, будет использовать этот код – Anurag

15

После того, что было сказано, вы можете показать тысячи элементов, используя только ограниченное количество ресурсов (и да, это действительно немного в стиле 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 

Надеется, что это помогает!

+0

. Спасибо Adrian за этот пример кода. В следующий раз, или если у меня появится возможность сделать мой код чище, я бы, вероятно, тоже обратился к этой части. – climbon

+0

Привет, @akosma, Я реализовал ваш код выше, но мне также нужна бесконечная прокрутка с обеих сторон, если я нахожусь в положении «0» и «право», а затем переместимся на 10 позиций. Вы можете мне помочь? Как я могу реализовать эту функциональность в вашем коде ..? –

6

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

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

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

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

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» никогда не вызывается!

+0

Привет @Chrysotribax, я использовал выше код, и он отлично работает, но мне нужно сделать бесконечную прокрутку, это означает, что пользователь протащил данные и дошел до последнего объекта, он пойдет на первые данные, и если он прольет правую сторону, то он достигнет последнего объекта. Не могли бы вы мне помочь ? –

Смежные вопросы