NSTableView 带有动画的scrollRowToVisible

发布于 2024-12-12 03:04:49 字数 114 浏览 0 评论 0原文

我正在尝试实现一个滚动到 NSTableView 顶部和 NSTableView 底部的操作。我正在使用 scrollRowToVisible 但我希望该动作是动画的。有办法做到这一点吗?

I am trying to implement an action to scroll to the top of a NSTableView, and the bottom of the NSTableView. I am using scrollRowToVisible but I'd love the action to be animated. Is there a way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

中二柚 2024-12-19 03:04:49

虽然 NSTableView 没有可以直接制作动画的滚动属性,但您可以通过一些数学动画来制作 NSTableView 所在的 NSClipView 的滚动动画。

以下是我的做法(在 NSTableView 的自定义子类中)如果可能的话,平滑地将 rowIndex 处的行设置为滚动到视图中心的动画:

        NSRect rowRect = [self rectOfRow:rowIndex];
        NSRect viewRect = [[self superview] frame];
        NSPoint scrollOrigin = rowRect.origin;
        scrollOrigin.y = scrollOrigin.y + (rowRect.size.height - viewRect.size.height) / 2;
        if (scrollOrigin.y < 0) scrollOrigin.y = 0;
        [[[self superview] animator] setBoundsOrigin:scrollOrigin];

While the NSTableView does not have a scroll property you can directly animate, you can instead, with a bit of math animate the scrolling of the NSClipView that the NSTableView lives in.

Here is how I did this (within a custom subclass of NSTableView) to smoothly animate the row at rowIndex to be scrolled to the center of the view, if possible:

        NSRect rowRect = [self rectOfRow:rowIndex];
        NSRect viewRect = [[self superview] frame];
        NSPoint scrollOrigin = rowRect.origin;
        scrollOrigin.y = scrollOrigin.y + (rowRect.size.height - viewRect.size.height) / 2;
        if (scrollOrigin.y < 0) scrollOrigin.y = 0;
        [[[self superview] animator] setBoundsOrigin:scrollOrigin];
紫瑟鸿黎 2024-12-19 03:04:49

如果您的目标是 10.8+ 并且您的表格视图是图层支持的,您可以执行以下操作:

[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
    context.allowsImplicitAnimation = YES;
    [self.tableView scrollRowToVisible:someRow];
} completionHandler:NULL];

If you're targeting 10.8+ and your table view is layer backed, you can do this:

[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
    context.allowsImplicitAnimation = YES;
    [self.tableView scrollRowToVisible:someRow];
} completionHandler:NULL];
哎呦我呸! 2024-12-19 03:04:49

这似乎不可能。 NSTableView 不支持 10.6 以下的任何类型的动画。从MasOSX10.7开始,一些简单的动画添加到了类中。您可以以动画方式插入、删除行以及将行移动到新位置。到目前为止就是这样。

It does not seem to be possible. NSTableView has not supported any kind of animations up to 10.6. Starting from MasOSX10.7 some simple animations added to the class. You can animate inserting, removing and moving rows to new positions. This is it so far.

纵情客 2024-12-19 03:04:49

没有简单的方法,但我会通过子类化 NSAnimation 来实现它,并且当它从 0.0 进展到 1.0 时,将其乘以总滚动距离以获得偏移量,并连续调用scrollToPoint: 以给出平滑滚动操作的外观。理论上它应该有效,尽管我不确定滚动视图的配合效果如何。

There's no easy way, but I would approach it by subclassing NSAnimation, and as it progresses from 0.0 to 1.0, multiply that by the total scroll distance to get your offset, and successively call scrollToPoint: to give the appearance of a smooth scrolling action. It should work in theory, though I'm not sure how well the scrollview would cooperate.

゛清羽墨安 2024-12-19 03:04:49

CuriousKea 实际上提出了一个有效但肮脏的解决方案。

  1. 无需子类化 NSTableView。你会用 NSOutlineView 做什么? DRY,使用协议及其扩展。
  2. scrollOrigin 的 Y 位置计算不正确。 NSTableView 滚动到行底部,而不是垂直中心。结果动画持续时间是错误的。
  3. 不支持 async/await 语法。

NSTableView 实现滚动动画的正确方法是:

@MainActor
protocol ScrollableTableView where Self: NSTableView {
    func scroll(to rowIndex: Int, withAnimation animated: Bool) async
    func scroll(to rowIndex: Int, withAnimation animated: Bool, completion: (() -> Void)?)
}

extension ScrollableTableView {
    func scroll(to rowIndex: Int, withAnimation animated: Bool = true) async {
        guard let superview else {
            return
        }
        
        let scrollOrigin = scrollOrigin(forRow: rowIndex)
        
        await animate(duration: animated ? 0.25 : 0, timingFunction: .easeInEaseOut) {
            superview.animator().setBoundsOrigin(scrollOrigin)
        }
    }
    
    func scroll(to rowIndex: Int, withAnimation animated: Bool = true, completion: (() -> Void)? = nil) {
        guard let superview else {
            return
        }
        
        let scrollOrigin = scrollOrigin(forRow: rowIndex)
        
        animate(duration: animated ? 0.25 : 0, timingFunction: .easeInEaseOut) {
            superview.animator().setBoundsOrigin(scrollOrigin)
        } completion: {
            completion?()
        }
    }
    
    private func scrollOrigin(forRow rowIndex: Int) -> CGPoint {
        guard let superview else {
            return .zero
        }
        
        let rowRect = rect(ofRow: rowIndex)
        let viewRect = superview.frame
        
        var scrollOrigin = rowRect.origin
        scrollOrigin.y += rowRect.size.height - viewRect.size.height
        scrollOrigin.y = max(0, scrollOrigin.y)
        
        return scrollOrigin
    }
    
    private func animate(duration: Double,
                         timingFunction timingFunctionName: CAMediaTimingFunctionName,
                         animations: () -> Void,
                         completion: (() -> Void)?) {
        NSAnimationContext.runAnimationGroup { context in
            context.duration = duration
            context.timingFunction = CAMediaTimingFunction(name: timingFunctionName)
            animations()
        } completionHandler: {
            completion?()
        }
    }
    
    private func animate(duration: Double,
                         timingFunction timingFunctionName: CAMediaTimingFunctionName,
                         animations: () -> Void) async {
        await NSAnimationContext.runAnimationGroup { context in
            context.duration = duration
            context.timingFunction = CAMediaTimingFunction(name: timingFunctionName)
            animations()
        }
    }
}

extension NSTableView: ScrollableTableView {}

CuriousKea actually proposed a working but dirty solution.

  1. No need to subclass NSTableView. What will you do with NSOutlineView? DRY, use a protocol and it extensions instead.
  2. Y position of scrollOrigin calculated incorrectly. NSTableView scrolls to bottom of row, not to a vertical center. As a result animation duration was wrong.
  3. No support for async/await syntax.

The right way to implement scroll animation for NSTableView is:

@MainActor
protocol ScrollableTableView where Self: NSTableView {
    func scroll(to rowIndex: Int, withAnimation animated: Bool) async
    func scroll(to rowIndex: Int, withAnimation animated: Bool, completion: (() -> Void)?)
}

extension ScrollableTableView {
    func scroll(to rowIndex: Int, withAnimation animated: Bool = true) async {
        guard let superview else {
            return
        }
        
        let scrollOrigin = scrollOrigin(forRow: rowIndex)
        
        await animate(duration: animated ? 0.25 : 0, timingFunction: .easeInEaseOut) {
            superview.animator().setBoundsOrigin(scrollOrigin)
        }
    }
    
    func scroll(to rowIndex: Int, withAnimation animated: Bool = true, completion: (() -> Void)? = nil) {
        guard let superview else {
            return
        }
        
        let scrollOrigin = scrollOrigin(forRow: rowIndex)
        
        animate(duration: animated ? 0.25 : 0, timingFunction: .easeInEaseOut) {
            superview.animator().setBoundsOrigin(scrollOrigin)
        } completion: {
            completion?()
        }
    }
    
    private func scrollOrigin(forRow rowIndex: Int) -> CGPoint {
        guard let superview else {
            return .zero
        }
        
        let rowRect = rect(ofRow: rowIndex)
        let viewRect = superview.frame
        
        var scrollOrigin = rowRect.origin
        scrollOrigin.y += rowRect.size.height - viewRect.size.height
        scrollOrigin.y = max(0, scrollOrigin.y)
        
        return scrollOrigin
    }
    
    private func animate(duration: Double,
                         timingFunction timingFunctionName: CAMediaTimingFunctionName,
                         animations: () -> Void,
                         completion: (() -> Void)?) {
        NSAnimationContext.runAnimationGroup { context in
            context.duration = duration
            context.timingFunction = CAMediaTimingFunction(name: timingFunctionName)
            animations()
        } completionHandler: {
            completion?()
        }
    }
    
    private func animate(duration: Double,
                         timingFunction timingFunctionName: CAMediaTimingFunctionName,
                         animations: () -> Void) async {
        await NSAnimationContext.runAnimationGroup { context in
            context.duration = duration
            context.timingFunction = CAMediaTimingFunction(name: timingFunctionName)
            animations()
        }
    }
}

extension NSTableView: ScrollableTableView {}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文