【问题标题】:NSScrollView infinite / endless scroll | subview reuseNSScrollView 无限/无限滚动 |子视图重用
【发布时间】:2012-02-25 07:42:02
【问题描述】:

我正在寻找一种方法来实现类似UI/NSTableView 的可重用单元格,但对于NSScrollView。基本上,我想要与 WWDC 2011 视频“Session 104 - Advanced Scroll View Techniques”相同,但适用于 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 相比,这实际上有效!)但现在的问题是,如何安装重用池? 滚动到新位置时移动子视图还是删除所有子视图并再次插入它们更好?我应该如何以及在哪里这样做?

【问题讨论】:

    标签: objective-c reusability nsscrollview infinite-scroll memory-efficient


    【解决方案1】:

    据我所知,-adjustScroll: 不是您想要利用滚动事件的地方,因为它不会被普遍调用。我认为-reflectScrolledClipView: 可能是一个更好的连接点。

    我编写了以下示例,该示例应该达到一种实现视图重用滚动视图的方法的高点。为简单起见,我按照您的建议将滚动视图的 documentView 的尺寸设置为“巨大”,而不是试图“伪造”滚动行为以使其看起来无限。显然,真实地绘制组成图块视图取决于您。 (在这个例子中,我创建了一个虚拟视图,它只是用红色和蓝色轮廓填充自己,以说服自己一切正常。)结果是这样的:

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

    据我所知,这非常有效。同样,在重用视图中绘制一些有意义的东西才是真正的工作所在。

    希望对您有所帮助。

    【讨论】:

    • 就像一个魅力,谢谢。将在接下来的几天深入挖掘以支持我所做的自动调整大小和其他自定义 =)
    猜你喜欢
    • 1970-01-01
    • 2019-01-30
    • 2013-10-28
    • 2013-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-06
    • 2014-12-12
    • 2020-11-23
    相关资源
    最近更新 更多