5

Estoy buscando una forma de implementar algo así como celdas reutilizables para UI/NSTableView pero para NSScrollView. Básicamente, quiero lo mismo que el video de la WWDC 2011 "Sesión 104 - Técnicas avanzadas de visualización en desplazamiento", pero para Mac.NSScrollView infinite/scroll sin fin | subview reuse

Tengo varios problemas para darme cuenta de esto. El primero: NSScrollView no tiene -layoutSubviews. He intentado utilizar -adjustScroll lugar pero fallan al establecer un diferente contentOffset:

- (NSRect)adjustScroll:(NSRect)proposedVisibleRect { 
    if (proposedVisibleRect.origin.x > 600) { 
     // non of them work properly 
     // proposedVisibleRect.origin.x = 0; 
     // [self setBoundsOrigin:NSZeroPoint]; 
     // [self setFrameOrigin:NSZeroPoint]; 
     // [[parentScrollView contentView] scrollPoint:NSZeroPoint]; 
     // [[parentScrollView contentView] setBoundsOrigin:NSZeroPoint]; 
    } 
    return proposedVisibleRect; 
} 

La siguiente cosa que intenté fue establecer una vista de contenido realmente enorme con un width de millones de píxeles (que realmente funciona en comparación con iOS!) pero ahora la pregunta es, ¿cómo instalar un pool de reutilización?
¿Es mejor mover las subvistas mientras se desplaza a una nueva posición o eliminar todas las subvistas e insertarlas de nuevo? y cómo y dónde debería hacer eso?

Respuesta

2

Lo mejor que puedo decir es que -adjustScroll: no es el lugar donde desea acceder a los eventos de desplazamiento porque no recibe un llamado universal. Creo que -reflectScrolledClipView: es probablemente un mejor punto de conexión.

He cocinado el siguiente ejemplo que debería golpear los puntos más altos de una manera para hacer una vista de visualización de reutilización de vista. Para simplificar, configuré las dimensiones de documentView de scrollView en "enorme", como sugieres, en lugar de intentar "simular" el comportamiento de desplazamiento para que parezca infinito. Obviamente dibujar las vistas de mosaicos constituyentes de verdad depende de usted. (En este ejemplo he creado una vista ficticia que solo se llena de color rojo con un contorno azul de convencerme de que todo estaba funcionando.) Surgió así:

// For the header file 
@interface SOReuseScrollView : NSScrollView 
@end 

// For the implementation file 
@interface SOReuseScrollView() // Private 

- (void)p_updateTiles; 
@property (nonatomic, readonly, retain) NSMutableArray* p_reusableViews; 

@end 

// Just a small diagnosting view to convince myself that this works. 
@interface SODiagnosticView : NSView 
@end 

@implementation SOReuseScrollView 

@synthesize p_reusableViews = mReusableViews; 

- (void)dealloc 
{ 
    [mReusableViews release]; 
    [super dealloc]; 
} 

- (NSMutableArray*)p_reusableViews 
{ 
    if (nil == mReusableViews) 
    { 
     mReusableViews = [[NSMutableArray alloc] init]; 
    } 
    return mReusableViews; 
} 

- (void)reflectScrolledClipView:(NSClipView *)cView 
{ 
    [super reflectScrolledClipView: cView]; 
    [self p_updateTiles]; 
} 

- (void)p_updateTiles 
{ 
    // The size of a tile... 
    static const NSSize gGranuleSize = {250.0, 250.0}; 

    NSMutableArray* reusableViews = self.p_reusableViews; 
    NSRect documentVisibleRect = self.documentVisibleRect; 

    // Determine the needed tiles for coverage 
    const CGFloat xMin = floor(NSMinX(documentVisibleRect)/gGranuleSize.width) * gGranuleSize.width; 
    const CGFloat xMax = xMin + (ceil((NSMaxX(documentVisibleRect) - xMin)/gGranuleSize.width) * gGranuleSize.width); 
    const CGFloat yMin = floor(NSMinY(documentVisibleRect)/gGranuleSize.height) * gGranuleSize.height; 
    const CGFloat yMax = ceil((NSMaxY(documentVisibleRect) - yMin)/gGranuleSize.height) * gGranuleSize.height; 

    // Figure out the tile frames we would need to get full coverage 
    NSMutableSet* neededTileFrames = [NSMutableSet set]; 
    for (CGFloat x = xMin; x < xMax; x += gGranuleSize.width) 
    { 
     for (CGFloat y = yMin; y < yMax; y += gGranuleSize.height) 
     { 
      NSRect rect = NSMakeRect(x, y, gGranuleSize.width, gGranuleSize.height); 
      [neededTileFrames addObject: [NSValue valueWithRect: rect]]; 
     } 
    } 

    // See if we already have subviews that cover these needed frames. 
    for (NSView* subview in [[[self.documentView subviews] copy] autorelease]) 
    { 
     NSValue* frameRectVal = [NSValue valueWithRect: subview.frame]; 

     // If we don't need this one any more... 
     if (![neededTileFrames containsObject: frameRectVal]) 
     { 
      // Then recycle it... 
      [reusableViews addObject: subview]; 
      [subview removeFromSuperview]; 
     } 
     else 
     { 
      // Take this frame rect off the To-do list. 
      [neededTileFrames removeObject: frameRectVal]; 
     } 
    } 

    // Add needed tiles from the to-do list 
    for (NSValue* neededFrame in neededTileFrames) 
    { 
     NSView* view = [[[reusableViews lastObject] retain] autorelease]; 
     [reusableViews removeLastObject]; 

     if (nil == view) 
     { 
      // Create one if we didnt find a reusable one. 
      view = [[[SODiagnosticView alloc] initWithFrame: NSZeroRect] autorelease]; 
      NSLog(@"Created a view."); 
     } 
     else 
     { 
      NSLog(@"Reused a view."); 
     } 

     // Place it and install it. 
     view.frame = [neededFrame rectValue]; 
     [view setNeedsDisplay: YES];   
     [self.documentView addSubview: view]; 
    } 
} 

@end 

@implementation SODiagnosticView 

- (void)drawRect:(NSRect)dirtyRect 
{ 
    // Draw a red tile with a blue border. 
    [[NSColor blueColor] set]; 
    NSRectFill(self.bounds); 

    [[NSColor redColor] setFill]; 
    NSRectFill(NSInsetRect(self.bounds, 2,2));  
} 

@end 

Esto funcionó bastante bien lo mejor que podría decir . Una vez más, dibujar algo significativo en las vistas reutilizadas es donde está el trabajo real.

Espero que ayude.

+0

funciona como un encanto, gracias. Profundizaré en los siguientes días para apoyar el autoajuste y otras personalizaciones que hice =) –

Cuestiones relacionadas