2010-01-16 7 views
11

Estoy luchando con la escritura de la parte de una aplicación que debería comportarse como la aplicación nativa de fotos de iphone. Miré el libro de desarrollo de la aplicación iphone sdk de Orielly, que proporcionaba un código de ejemplo para implementar el llamado "flicking" de páginas. El código allí primero creó todas las subvistas y luego las oculta/muestra. En un momento dado, solo 3 subvistas son visibles, el resto está oculto. Después de mucho esfuerzo lo conseguí trabajando con la aplicación que en ese momento tenía solo alrededor de 15 páginas.¿Cómo implementar UIScrollView con más de 1000 subvistas?

Tan pronto como agregue 300 páginas, quedó claro que existen problemas de rendimiento/memoria con ese enfoque de asignar previamente tantas subvistas. Entonces pensé que podría ser para mi caso, debería asignar 3 subvistas y en lugar de ocultarlas/mostrarlas. Tal vez debería eliminar/agregar subvistas en tiempo de ejecución. Pero no puedo determinar si UIScrollView puede actualizar dinámicamente los contenidos. Por ejemplo, al inicio hay 3 fotogramas en diferentes x-offsets (0, 320, 640) de la pantalla tal como lo entiende UIScrollView. Una vez que el usuario se traslada a la 3ª página, ¿cómo puedo asegurarme de que puedo agregar la 4ª página y eliminar la 1ª página y, sin embargo, UIScrollView no se confunde?

Esperando que haya una solución estándar para este tipo de problema ... ¿alguien puede guiar?

+1

no es una respuesta concreta para usted, pero este tipo de problema a menudo se resuelve mediante el uso del patrón flyweight: http://en.wikipedia.org/wiki/Flyweight_pattern –

+0

Cualquiera que se acerque a esta pregunta ahora - utilice el 'UICollectionView' ... no estaba disponible en el momento de esta pregunta, pero es ahora y lo hará por usted. –

Respuesta

7

UIScrollView es solo una subclase de UIView por lo que es posible agregar y quitar subvistas en tiempo de ejecución. Suponiendo que tiene fotos de ancho fijo (320px) y hay 300 de ellas, su vista principal sería 300 * 320 píxeles de ancho. Al crear la vista de desplazamiento, inicialice el cuadro para que sea tan ancho.

Así que el marco de la vista de desplazamiento tendría las dimensiones (0, 0) a (96000, 480). Siempre que agregue una subvista, tendrá que cambiar su marco para que encaje en la posición correcta en su vista principal.

Digamos que estamos agregando la foto 4th a la vista de desplazamiento. Su marco sería de (960, 480) a (1280, 480). Eso es fácil de calcular, si de alguna manera puede asociar un índice con cada imagen. A continuación, utilice para calcular este plano de la imagen, donde los índices comienzan en 0:

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

a

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

Extracción de la primera imagen/subvista debe ser fácil. Mantenga una matriz de las 3 subvistas actualmente en pantalla. Siempre que agregue una nueva subvista a la pantalla, también agréguela al final de esta matriz y luego elimine la primera subvista en esta matriz de la pantalla también.

+0

Lo que dices tiene sentido. Déjame darle una oportunidad y luego actualizar este hilo. – climbon

+1

Hazlo funcionar ahora. Gracias por aclararlo aunque terminé haciéndolo de una manera ligeramente diferente (para minimizar los cambios al código existente). Mi código existente (tomado del ejemplo en el libro que mencioné) tenía el ancho del marco de mi scrollView = 320x3. Con la propiedad myScrollView.contentOffset, el código controla cuál de las 3 vistas mostrar. Anteriormente mi matriz contenía 300 vistas (asignadas al inicio) ahora contiene solo 3. Si el usuario accede a la 3ª página, elimino la 1ª página, inserto la 4ª y ajusto la visibilidad mediante la propiedad contentOffset. Si llega al 1er, eliminé el 3ro e inserté uno antes de la 1ra, etc. – climbon

+0

No sabía acerca de la propiedad contentOffset. Necesito algo similar al desplazamiento continuo para mis aplicaciones, y seguramente usaré ese – Anurag

15

Después de lo que se ha dicho, puede mostrar miles de elementos utilizando solo una cantidad limitada de recursos (y sí, es un poco un patrón Flyweight de hecho). Aquí hay un código que puede ayudarte a hacer lo que quieras.

La clase UntitledViewController solo contiene un UIScroll y se establece como su delegado. Tenemos un NSArray con instancias NSString dentro como modelo de datos (podría haber miles de NSStrings en él), y queremos mostrar cada uno en un UILabel, usando desplazamiento horizontal. Cuando el usuario se desplaza, desplazamos los UILabels para poner uno a la izquierda, otro a la derecha, para que todo esté listo para el próximo evento de desplazamiento.

Esta es la unión, más bien sencillo:

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

    NSArray *_objects; 

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

@end 

y aquí está la aplicación de esa clase:

@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 

Espero que esto ayude!

+0

Gracias a Adrián también por este ejemplo de código. La próxima vez o si tengo la oportunidad de hacer que mi código sea más limpio, probablemente también me referiré a esta pieza. – climbon

+0

Hola @akosma, He implementado el código anterior pero también necesito un desplazamiento infinito en ambos medios laterales si estoy en la posición 0 y deslizo hacia la derecha y luego me muevo a la posición 10. ¿Me puedes ayudar? ¿Cómo puedo implementar esta funcionalidad en tu código ...? –

6

Muchas gracias a Adrian por su muestra de código muy simple y potente. Hubo un solo problema con este código: cuando el usuario hizo un "doble desplazamiento" (I.E. cuando no esperó que la animación se detuviera y volviera a desplazar la vista de desplazamiento una y otra vez).

En este caso, la actualización para la posición de las 3 subvistas solo es efectiva cuando se invoca el método "scrollViewDidEndDecelerating", y el resultado es un retraso antes de la aparición de las subvistas en la pantalla.

Esto puede evitarse fácilmente mediante la adición de unas pocas líneas de código:

en la interfaz, sólo tiene que añadir esto:

int refPage, currentPage; 

en la implementación, inicializar refPage y currentPage en el método "viewDidLoad" como esto:

refpage = 0; 
curentPage = 0; 

en la aplicación, sólo tiene que añadir el método "scrollViewDidScroll", así:

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

y voilà!

Ahora, las subvistas se reemplazan correctamente en las posiciones correctas, incluso si el usuario nunca detiene la animación y si el método "scrollViewDidEndDecelerating" nunca se invoca.

+0

Hola @Chrysotribax, he usado el código anterior y funcionó bien, pero necesito hacer que el desplazamiento infinito signifique datos de deslizamiento del usuario y alcanzar el último objeto que irá a los primeros datos y si desliza el lado derecho, alcanzará el último objeto. Podrías ayudarme ? –

Cuestiones relacionadas