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.
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.
Related
I'm building a OS X application using Swift and Xcode 6.4 on OS X 10.10.5.
On a specific view of my application I would like to have a view like this one Xcode has on the Data Model Editor.
I tried to replicate this view using an OutlineView where each "row" would have a title and a TableView plus two buttons (for the plus and minus buttons). For tests purposes I've separated the title for the TableView+Buttons, something like this (this was one of many different attempts).
Everything is working as expected except the View that has the TableView+Buttons, that is never higher than 17 pixels. If I define everything in one view, I have the same problem. I've tried defining the needed constraints but in that case there is a problem with a constraint that seems automatic called NSView-Encapsulated-Layout-Height, that forces the height to 17 pixels:
NSLayoutConstraint:0x61800008ea10 'NSView-Encapsulated-Layout-Height'
> V:[NotesTable(17)] (Names: NotesTable:0x60000012e2e0 )
I'm not defining any constraint to 17 pixels, I've tried testing with some parameters that usually insert automatic constraints (autoresizesSubviews/translatesAutoresizingMaskIntoConstraints/autoresizingMask) but I was only able to translate that 'special' constraint to another format and the grow doesn't get bigger.
Tried to search the web but I only get cases where that Encapsulated constraint makes sense and is useful.
Do you know where or how can I disable that constraint or change its value to the height I need?
Table and outline views on OS X do not support automatically determining the row height from the dynamic height of the cell views. They either have an explicit static row height, a static row height determined by the design-time height of the cell views, or a dynamic row height determined by the delegate and its implementation of -tableView:heightOfRow: or -outlineView:heightOfRowByItem:.
For your case, you're going to have to implement the delegate method. Furthermore, the delegate method can't query the actual cell view because it may not exist and the outline view would need the row height before creating it. So, the delegate has to compute it some other way.
One way is to keep a standalone view hierarchy of a prototypical cell view. When the delegate is asked for the row height, it configures that view hierarchy as it would be for the actual cell view for that row/item, forces it to lay itself out, and then queries its height. Configuring the view hierarchy may be as simple as setting the objectValue of the top-level view (if it's an NSTableCellView, a control, or otherwise implements the setter). But if your delegate does other configuration, such as in its -outlineView:viewForTableColumn:item: method, then you'll need to replicate that for this prototype view hierarchy.
Also, when any factor that would affect a row's height changes, you have to call the outline view's -noteHeightOfRowsWithIndexesChanged: method to let it know that, so it will re-query your ...heightOfRow... method.
Finally, bare table views are not especially amenable to being constrained to sibling views or their superview. They really want to live in scroll views and continue using springs-and-struts to position and size themselves. See my answer to another question for a discussion of this. It is possible that this has been improved in recent versions of the OS. Anyway, you're going to have to observe the table view's frame-change notifications (and ask it to post such notifications) in order to know when it grows. And your ability to set constraints to relate it to any other views in the cell view hierarchy will be severely limited, because it will need translatesAutoresizingMaskIntoConstraints turned on.
In my Xcode 6.2 Swift project I have an issue with Auto-layout (I'm also using size classes) that I'm not able to figure out...
In Main.storyboard I have a view containing, among the others interface elements, a UIButton and a UISegmentedControl that are positioned at the same height on the opposite side of the view.
I'm setting manually all the constraints in Interface Builder (none in code) and my every view is working just fine, except in this case (and this particular issue only occurs when I have a long text).
The button is aligned to the left border of the view and its constraints are:
Leading space to superview == 0
Trailing space to segmented controller >= 8
Top and bottom space to other interface elements == 8
The segmented controller (which has 2 segments) is aligned to the right border of the view and its constraints are:
Trailing space to superview == 0
Leading space to button >= 8
Top and bottom space to other interface elements == 8
The button in the storyboard has a title "Some title", but actually the actual title is always set in code in ViewWillAppear:
myButton.setTitle(aStringThatSometimesIsPrettyLong, forState: .Normal)
The visual result I need to achieve (on every possibile device and orientation) is that the Button title I set in code, while it can be displayed in good length in the interface, should never compromise the size of the segmented control, compressing the labels of the two segments.
So, I want the size of the segmented control to be fixed and I'm willing to accept the fact that the Button title, if long, can be truncated with dots.
Instead, no matter what I try (and I'll explain what I've tried in a moment) when the Button title is very long it is not truncated, instead the segmented control is compressed and therefore its two segments labels are truncated.
So far, I've tried, separately and together, these steps:
Adding a width minimum constraint to the segmented control.
Incremented (in steps, up to 1000) the Content compression resistance of the segmented control while decreasing the correspondent value of the button.
Increased (up to 1000) the Content hugging priority of the button.
I think I can't set a maximum width to the Button, because it can stretch depending on the title set in code and, more important, on what device the app is run on.
My biggest issue is that, no matter what I try, when I run the app I always get the same behavior (button title completely shown, un-truncated, and compressed segmented control). It seems like adding these constraints doesn't change anything, and it never happened to me before with Auto-layout... messing up, a lot, but no change adding constraints, this is new!
Maybe the issue is that the button title is set in ViewWillAppear and not in the Storyboard, but my app wouldn't work properly if I couldn't set its title in code.
Last, but pretty important, I have to admit that, while I've managed so far to get Auto-layout and Size classes working on all devices and orientations for all the (over 10) viewcontrollers of my app, I've actually never written a single line of code for Auto.layout and Size classes: I've done everything in Interface Builder and, if possible, I'd really love to continue this way.
Any suggestion would be really, really appreciated!
Thanks in advance,
Cesare
As #KenThomases pointed out, the constraints I was setting were actually right (actually, it also works with less constraints now that, thanks to his answer, I figured out the issue), but the Segmented control wasn't getting its intrinsicContentSize. Fixed that, now everything's fine.
Ok. I've been at this over and over. I've seen blogs and cocoa dev threads.
I've seen Kyle Sluder's proposed solution, but have yet to find a solution that really works.
How can you position subviews of an NSScrollView with auto layout?
Is it just silently broken ?
Nothing seems to work.
Ok, old question, but this particular issue is a personal bugbear of mine so I'll answer it anyway!
The first thing to note is that an NSScrollView contains an NSClipView, which itself has a view outlet called documentView. These are all added for you when you drag a new scroll view into your storyboard or nib file. By default, the document view is an NSView called simply "View". If you're using a custom view, you can just select this and set its type in the inspector on the right to whatever you want. Otherwise, you'll be adding subviews to it.
The big thing that is easy to miss here is that, by default, the document view has its layout set to 'Translates Mask Into Constraints'. This is fine if the content size will never, ever change, and if that's the case you can simply set the frame of the document view to whatever you want and leave it at that. If you want it to automatically resize itself to fit its content however, there's a few things you'll need to do.
First off, that document view needs to have a completely unambiguous size. If you're using a custom view, I'd recommend giving it an intrinsicContentSize. You should also set 'Intrinsic Size' in IB's inspector to 'Placeholder' and give it a suitable value, or you'll get a bunch of autolayout warnings. If your document view gets its size from its content, all of the subviews must be linked in an unbroken chain from top to bottom, and from left to right, such that the content knows exactly how big it ought to be. This is quite an art in itself, so I won't go into it. A simple example where you have only one subview would be to pin its top, bottom, leading and trailing constraints to its parent, but as noted above if you're doing this, you might as well just set the type of the document view.
Now the fun bit. Select your document view and set its layout to 'Automatic'. Next, add top, bottom, leading and trailing constraints to its superview with a suitable value. I'm using zero, but you might want a small border. Finally, select the TRAILING and BOTTOM constraints you just made and set them to '>=' (greater than or equal) and a priority of 500 or less. The priority is very important, as it has to be less than the priority that the clip view uses to determine its own minimum size. Too high and the clip view will be forced to remain larger than its content, making it impossible in turn for the scroll view to be smaller than its content, rendering it useless.
The technical details aren't important. Just remember to set the document view to layout: automatic, pin all edges, and make the trailing and bottom constraints >= and priority 500.
Note that this will cause your content to hug the top-left corner.
Have you tried setting the document view's setTranslatesAutoresizingMaskIntoConstraints to TRUE?
[_scrollView.documentView setTranslatesAutoresizingMaskIntoConstraints:YES];
Yesterday I failed to get an answer, but perhaps I did not understand the problem deep enough to formulate correct question.
The story is about animating ListBox height. Here are subsequent screenshots:
a) "Medium" is a TextBlock
b) "Medium" TextBlock gets replaced by a ListBox. The user selects an item. That initiates animation of the ListBox.Height. After the animation completes, the ListBox is replaced by original TextBlock.
(Disregard the differences in data. Collection of the images was a painfull process, when I had to work with rendered frames. One of the images was shot for different record.)
This sequence works with occasional flickering. I wanted to know what's going on and after a while I got this screenshot:
What you see is the first frame after Storyboard.Completed event was intercepted. As far I understand this is the final result from the Storyboard.
Notes:
I checked the visual tree at this instant and did not find anything suspiceous.
This is just one of the effects that happen. Another frequent case is a resized 1-line ListBox with blue hatching; in this case all elements above the listbox disappear. 3rd possibility is a diagonal red line over the whole screen.
Here is the code defining the Storyboard:
private Storyboard GetDropDownAnimation(double from, double to)
{
double secs = this.IsExpanded ? 0.2 : 0.4;
CubicEase ease = new CubicEase() { EasingMode = EasingMode.EaseInOut };
DoubleAnimation animation = new DoubleAnimation()
{
Duration = new Duration(TimeSpan.FromSeconds(secs)),
From = from,
To = to,
FillBehavior = FillBehavior.HoldEnd,
EasingFunction = ease
};
Debug.WriteLine("Animation Height {0} -> {1}", from, to);
Storyboard.SetTarget(animation, this);
Storyboard.SetTargetProperty(animation, new PropertyPath("Height"));
Storyboard sb = new Storyboard();
sb.Children.Add(animation);
return sb;
}
I could explain other tricks done (for a long time I was convinced that the problem is there), but it looks like the problem concerns only the animation itself.
Anybody able to explain what's going on?
Have a look at these before you continue this way it might make your life a bit easier :)
That said, have a look at the starting value of the animation (from) and see if it is correct.
If all else fails you could start with a fully transparent listbox.
I made some progress on the problem that may be worth of reporting.
At first I replaced Storyboard by own animations. I started by using CompositionTarget.Rendering callbacks. The code is fairly trivial: In the callback you need to update the ListBox height proportionally to the elapsed time. I double-checked that for each height change there is one LayoutUpdated event, in other words the screen is in sync all the time. (A pleasant surprise.)
The result: Flickering remained.
Conclusion: Storyboard is innocent.
Then I found something interesting:
Because the height manipulation was in my hands I simply rounded the value to the whole multiple of the row height. And guess what - flickering disappeared!
Note the ListBox in question is set up using ItemsSource and DisplayMemberPath that refers to a string property. ItemTemplate is not set. In other words pretty standard ListBox use.
It is not an answer, I know, yet I find it interesting.
I am trying to create a view for a kind of brainstorming application like, for example, OmniGraffle, with elements that contain textviews and can be dragged around. (Also, the should be connectable with arrows, but that is not (yet) the problem)
I did my homework and searched via google and read books about cocoa, but there seems to be no similar example around.
Since I am also new to cocoa, I’m a bit helpless here.
The thing I am sure of is, that I need a custom view in which I can create my elements - what I tried until now to do that is:
First, I searched for the syntax to add subwindows to a window to create my elements. Subwindows, I imagined, would automatically be movable and come to front and so on.
The problem: As the experienced Cocoa-programmers of you probably are not surprised, I was stunned to find nothing about anything like that - this seems to be something, that is just not intended in Cocoa?!
Then I thought about creating subviews that contain a custom view for the title bar drawing (where the user can click to drag the element) and a NSTextView.
Problems:
I read, that it is not so clever to create dozens of subviews in a window because that would be very slow (or would that be not so bad in this case because all the subviews would be instances of always the same class?).
Also I can’t find out how to load a subview from a nib- or xib-file. Would I need a viewController? Or would that make the dozens-of-instances-problem even worse?
And Apple tells you not to overlap subviews (okay, that would be not so important, but I really wonder how the guys at OmniGroup made OmniGraffle...)
Because of that, I now wanted to do the title-bar-drawing in the surrounding custom view and create the textview programmatically (as I understand, a text-“view“ ist not really a view and takes its functionality from NSCell to reduce all the effort with the views?).
Problems:
Even that failed because I was not able to create a textview that doesn’t fill the complete window (the initWithFrame: of the [[NSScrollView alloc] initWithFrame: aRect] just seems to be ignored or do I get that wrong?).
Also, there should be some buttons on each element in the final application. I imagine that would be easier to accomplish with a subview from a nib-file for each element?
Well, now that nothing works and the more I read, the more problems seem to occur, I am pretty confused and frustrated.
How could I realize such a program? Could someone please push me in the right direction?
I created a class for the draggable elements where I save position, size and text in instance variables. In my view, every new element instance is added to an array (for now, this works without a controller). The array is used to draw all the elements in a loop in drawRect:. For the text of the element I just use a NSTextFieldCell which is set to the saved text from every element in the same loop.
That way it is also possible to overlap the elements.