Reputation: 11305
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?
Upvotes: 8
Views: 5985
Reputation: 190
CuriousKea actually proposed a working but dirty solution.
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 {}
Upvotes: 1
Reputation: 2419
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];
Upvotes: 18
Reputation: 348
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];
Upvotes: 23
Reputation: 7272
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.
Upvotes: 2
Reputation: 4623
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.
Upvotes: 2