1

What I would like to achieve is something like this:
DevExpress Grid
Table with fixed columns

The table at the above links can have "fixed" columns, which does not scroll with the other content.

I'm aware of NSTableView's floatsGroupRows feature, and of NSScrollView's addFloatingSubview:forAxis: method; but to achieve the above one, these are not enough:

  • The columns are not NSViews, first of all
  • The table header and the table content is placed into 2 separate NSClipViews under the NSScrollView (this is the default operation of NSTableView)

So as long I could not find any built in solution for this. My only idea was to use 3 NSTableViews next to each other (+1 for the Left side, +1 for the right side); and sync the vertical scrolling in them manually. How to sync the horizontal scrolling, now that's a harder question. The Left and Right sides should not scroll, so should "float". For the table's content, the NSScrollView's addFloatingSubview:forAxis: method should work IMO(*); but the column headers are different animals. Ok, there still should be a way to achieve this floating behavior via hacking the drawing of the columns...

But still, I did not start to implement the above one, because my NSTableView is slow enough already (NSTableview View Based Scrolling Performance), and I'm sure these plus things would slow it down horribly.

Has anyone any (better) idea how to achieve floating columns in Cocoa? Any help much appreciated!

Edit

(*): NSScrollView's addFloatingSubview:forAxis: does not work for this. As I see it now, if the NSView given to this method is a subview of an NSTableView, it gets special treatment. Probably the table adds its own logic into; and it turned out now for me, that the NSTableView only can have 1 floating row at a time.

Community
  • 1
  • 1
nvirth
  • 1,599
  • 1
  • 13
  • 21
  • 1
    How to sync the horizontal scrolling: [Synchronizing Scroll Views](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/NSScrollViewGuide/Articles/SynchroScroll.html). – Willeke Nov 18 '15 at 13:06
  • Thanks, useful source. (For the sync of the vertical scrolling though, not the horizontal. The horizontal scrolling does not need any sync) – nvirth Nov 18 '15 at 14:19
  • I would caution against trying to emulate those screenshots by combining off-the-shelf cocoa components. As you seem to appreciate, combining and populating multiple table views, then syncing the scrolling, then overcoming performance issues would become an almighty headache. Instead, forego your ideal approach, and focus on coming up with a solution that uses the available components in a more conventional manner - if you do this I think you'll safe yourself a lot of effort, and end up with a UI that's much easier to work with. – Paul Patterson Nov 18 '15 at 17:16
  • The problem is, I could not find any use of the available components in conventional manner to solve this task :) Plus, I use a subclassed NSTableView, which already has been extended with a lot of features; this one now is only a change request. So writing an own table from scratch, or using a 3rd party component which has already written one, is not a solution for me now. – nvirth Nov 19 '15 at 10:02

1 Answers1

1

I have achieved synchronizing two NSTableViews vertically. Not really clear why you would need three, but anyways. Note that this is all c# code using the Xamarin.Mac lib. These are wrappers over the native Cocoa platform. You will have to convert this to obj c/swift yourself. This shouldn't be difficult.

In awake from nib:

table1.EnclosingScrollView.ContentView.PostsBoundsChangedNotifications = true;
NSNotificationCenter.DefaultCenter.AddObserver (NSView.BoundsChangedNotification, BoundsDidChangeNotification, table1.EnclosingScrollView.ContentView);
table2.EnclosingScrollView.ContentView.PostsBoundsChangedNotifications = true;
NSNotificationCenter.DefaultCenter.AddObserver (NSView.BoundsChangedNotification, BoundsDidChangeNotification, table2.EnclosingScrollView.ContentView);

So every time one of the tables scrolls, the BoundsDidChangeNotification is called, and it takes care of synchronizing the y axis. Note that this works even when the scroll happens due to inertia scrolling or programmatic changes of the bounds (or when zooming in/out or resizing the views etc). Such events might not always trigger events which are specifically intended for "user scroll", and for this reason, this is the better approach. Bellow is the BoundsDidChangeNotification method:

public void BoundsDidChangeNotification (NSNotification o)
    {
        if (o.Object == table1.EnclosingScrollView.ContentView) {
            var bounds = new CGRect (new CGPoint (table2.EnclosingScrollView.ContentView.Bounds.Left, table1.EnclosingScrollView.ContentView.Bounds.Top), table2.EnclosingScrollView.ContentView.Bounds.Size);
            if (bounds == table2.EnclosingScrollView.ContentView.Bounds)
                return;
            table2.ScrollPoint (bounds.Location);
        } else {
            var bounds = new CGRect (new CGPoint(table1.EnclosingScrollView.ContentView.Bounds.Left, table2.EnclosingScrollView.ContentView.Bounds.Top), table1.EnclosingScrollView.ContentView.Bounds.Size);
            if (table1.EnclosingScrollView.ContentView.Bounds == bounds)
                return;
            table1.ScrollPoint (bounds.Location);
        }
    }

Nothing too fancy here... there are some bounds equality checks to avoid an eventual infinite loop (table1 tells table2 to sync and then table2 tels table1 and so on, forever). AFAI remember, if you ScrollPoint to the current scrolled location, bounds did change is not triggered, but since an infinite loop would occur otherwise, I think an extra check is worth it - you can never know what happens in future os x versions.

Radu Simionescu
  • 4,518
  • 1
  • 35
  • 34