NSScrollView бесконечная / бесконечная прокрутка | повторное использование подпредставления - PullRequest
5 голосов
/ 02 февраля 2012

Я ищу способ реализовать что-то вроде многоразовых ячеек для UI/NSTableView, но для NSScrollView. В основном я хочу то же самое, что и видео WWDC 2011 "Сессия 104 - Расширенные способы просмотра с прокруткой", но для Mac.

У меня есть несколько проблем с пониманием этого. Первый: NSScrollView не имеет -layoutSubviews. Я попытался использовать -adjustScroll вместо этого, но не смог установить другое 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;
}

Следующая вещь, которую я попробовал, состояла в том, чтобы установить действительно огромный вид контента с width миллионами пикселей (что на самом деле работает по сравнению с iOS!), Но теперь вопрос, как установить пул повторного использования?
Лучше ли перемещать подпредставления при прокрутке на новую позицию или удалять все подпредставления и вставлять их снова? и как и где мне это делать?

1 Ответ

2 голосов
/ 11 февраля 2012

Как я могу сказать, -adjustScroll: - это не то место, куда вы хотите подключиться к событиям прокрутки, потому что оно не вызывается повсеместно. Я думаю, что -reflectScrolledClipView:, вероятно, лучшая точка подключения.

Я подготовил следующий пример, который должен достигнуть вершин одного из способов сделать вид с повторным использованием прокрутки. Для простоты я установил размеры documentView scrollView на «огромный», как вы предлагаете, вместо того, чтобы пытаться «подделать» поведение прокрутки, чтобы оно выглядело бесконечным. Очевидно, что рисование составляющих плиточных видов по-настоящему зависит от вас. (В этом примере я создал фиктивный вид, который просто заполняется красным с синим контуром, чтобы убедить себя в том, что все работает.) Получилось так:

// 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

Это сработало очень хорошо, насколько я мог судить. Опять же, рисование чего-то значимого в повторно используемых представлениях - вот где настоящая работа.

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

...