How to change NSTableCellView's height when using storyboards in Interface Builder - xcode

When I create a non-storyboard OSX app and add a Table View from the Object Library to my main window in Interface Builder, I am able to change the height of an NSTableCellView inside that Table View.
Doing so, automatically changes the TableView's row height (even when I run the app).
But when I create a storyboard app and follow the exact same steps (adding Table View, changing cellView's height), the TableView's row height does not change, resulting in the NSTableCellView being cropped when I run the app.
I know you can implement the heightForRowAtIndexPath method, but I'm just wondering why this works when using xib files and stopped working when using storyboards. (I really find it a lot easier to design interfaces graphically instead of writing down arbitrary numbers in a text file.)
Is there something I'm missing here? What is the easiest way of doing this, using storyboards?

You have to set the height in the table view (size inspector -> size style: custom and set row height value).

Related

NSTableView inside an NSTableViewCell

I'm trying to use an NSTableView inside an NSTableViewCell and have problems disabling scrolling. The outer tableview visualises a list of objects - in my case a 3D material definition - and inside each cell I use (among other controls) another tableview to represent the properties of the material (which is a dynamic list that can change in size).
material A
property 1
property 2
property 3
material B
property 1
material C
...
This works fine except for one thing:
Both tableview's use dynamic row height and this causes the outer table view cell to collapse the inner tableview to a 0px height. Is there a way to disable scrolling for the inner table view and cause it to use enough height to show all elements? What makes it even more complicated is that the properties map to different cell definitions (based on the type of property) and all those cells are setup in Interface Builder. So using an NSView and setting its type to NSTableView to avoid the NSScrollView or doing a programmatic construction of all the controls is not really an option.
Any tips are highly appreciated!
Update: Using TableViews
You can also use NSTableView for what you want do achieve. Unlike UITableView, NSTableView does not do the scrolling itself. It is wrapped inside an NSClipView, which itself is wrapped in an NSScrollView. So you simply extract the tableView out of that and add some constraints.
Interface builder does not support that currently very well. You can't drag the tableView out of its enclosing clipView. But you can open the interface file as source code and remove everything beginning from the scrollView (except the table itself).
The tableView should display fine in the Interface Builder (tested on Xcode 9 and 10)
You have to add the constraints in code, but then the tableView should grow by itself.
Since the inner table view does not have to scroll, you can use just NSStackView to layout your views. Then you don’t have to fight the behaviors of NSTableView.
If you need an example, feel free to ask!

NSTableView inside NSOutlineView and NSView-Encapsulated-Layout-Height

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.

How is a subview of an NSView dynamically positioned?

I am attempting to change the coordinates of an NSButton that is contained within a parent NSView and something is clearly not working, because the button position does not change. Both elements are defined in a nib file and the parent view has animation applied to it using CoreAnimation.
I have tried the following.
button.frame.origin.x = 500
and...
var frame:CGRect = button.frame
frame.origin.x = 500
button.frame = frame
Even with the animations disabled, I can not seem to dynamically position the subview. Is there some feature that prevents children views from being positioned programmatically?
Please note that I am using Swift with XCode 6.3.1.
I'm guessing you're using AutoLayout constraints, given you're using the latest tools.
If so, setting a subview's frame directly won't work the way you're expecting (if it does anything at all, it'll cause strange drawing glitches / flashing when mixed with animation). You have to create outlets for your layout constraints and modify them.
If you're not using AutoLayout, I suggest having a look at your button outlet to make sure it's actually connected (ie, you're not talking to nil). Even if the outlet is connected, make sure it's not nil at runtime - you may be trying to talk to the button before the nib is loaded and the outlet / action connections are restored.

Move all subviews to container view keeping positions and constraints

I'm using Xcode 5 and autolayout in my project. I have a .xib file with a lot of subviews and constraints between them. Now I need to create an intermediate fullscreen container view and put all subviews there. So now I have view->subviews, and I want view->container view->subviews. When I do this in IB by dragging subviews into container view, they all get centred and all constraints get lost. It's going to be a little hard to restore all constraints manually. Is there a smart way to do this keeping all subviews' positions and constraints?
Add your container view to the hierarchy (to get an object ID) in Interface Builder and close the view. Edit the .xib file manually, in a text editor, and move all subviews to be under the container view. Constraints are created in IB with references to IB object IDs, so you will have to replace the constraints from referencing the superview with the container; a quick replace run should be sufficient.
You can do this in Xcode 6 by using cut&paste to a separate view (which you can later drag around to wherever you actually need it).

Can I disable autolayout for a specific subview at runtime?

I have a view that needs to have its frame manipulated programmatically - it's a kind of document view that wraps to its content which is then scrolled and zoomed around a superview by manipulating the frame origin. Autolayout fights with this at runtime.
Disabling autolayout completely seems a bit harsh because it could reasonably be used to handle layout for the other views. It seems like what I might want is some kind of "null constraint".
I had the same problem. But I have resolved it.
Yes, you can disable auto layout at runtime for a specific UIView, instead of disabling it for the whole xib or storyboard which is set by default in Xcode 4.3 and later.
Set translatesAutoresizingMaskIntoConstraints to YES, before you set the frame of your subview:
self.exampleView.translatesAutoresizingMaskIntoConstraints = YES;
self.exampleView.frame = CGRectMake(20, 20, 50, 50);
I had a similar issue where Autolayout was overriding some of my frame-setting at run time (I had a dynamic view that in some cases pushed a new view controller...pushing and then pressing Back would reset the initial view).
I got around this by putting my manipulation code in viewDidLayoutSubviews of my View Controller. This seems to get called after whatever constraint mojo gets called, but before viewDidAppear, so the user is none the wiser.
Perhaps just setting translatesAutoresizingMaskIntoConstraints to YES (and not adding additional constraints affecting that view) will let you set the frame without fighting the auto layout system.
In iOS 8 you can set an NSLayoutConstraint to be active or not. So if I'm using interface builder, I add all my constraints to an OutletCollection and then activate or deactivate using:
NSLayoutConstraint.deactivateConstraints(self.landscapeConstraintsPad)
NSLayoutConstraint.activateConstraints(self.portraitConstraintsPad)
The particular application I'm using it for here is having different constraints in portrait and landscape mode and I activate/deactivate based on the rotation of the device. It means I can create some complex layout changes all in interface builder for both orientations, and still use auto layout without the verbose auto layout code.
Or you can activate / deactivate using removeConstraints and addConstraints.
I don't know if this will help anyone else, but I wrote a category to make this convenient because I find myself doing this a lot.
UIView+DisableAutolayoutTemporarily.h
#import <UIKit/UIKit.h>
#interface UIView (DisableAutolayoutTemporarily)
// the view as a parameter is a convenience so we don't have to always
// guard against strong-reference cycles
- (void)resizeWithBlock:(void (^)(UIView *view))block;
#end
UIView+DisableAutolayoutTemporarily.m
#import "UIView+DisableAutoResizeTemporarily.h"
#implementation UIView (DisableAutoResizeTemporarily)
- (void)resizeWithBlock:(void (^)(UIView * view))block
{
UIView *superview = self.superview;
[self removeFromSuperview];
[self setTranslatesAutoresizingMaskIntoConstraints:YES];
__weak UIView *weakSelf = self;
block(weakSelf);
[superview addSubview:self];
}
#end
I use it like this:
[cell.argumentLabel resizeWithBlock:^(UIView *view) {
[view setFrame:frame];
}];
Hope it helps.
You can set the translatesAutoresizingMaskIntoConstraints type Boolean, Value Yes in the User Defined Runtime Attributes of the UIView you want in the xib/storyboard.
In my view I had a Label and a Text. The label had pan gesture. The label moves around fine during drag. But when I use the text box keyboard, the label resets its position to the original location defined in auto layout. The issue got resolved when I added the following in swift for the label. I added this in viewWillAppear but it can be added pretty much anywhere you have access to the target field.
self.captionUILabel.translatesAutoresizingMaskIntoConstraints = true
Open project in 4.5
Select storyboard
Open the file inspector
Under Interface Builder Document uncheck 'Use Autolayout'
You can split across multiple storyboards if you want to use autolayout for some views.
For me it worked to create the subview programmatically, in my case the auto layout was messing with a view that I needed to rotate around its center but once I created this view programmatically it worked.
I've encountered a similar scenario, where I joined a project that was initiated with auto-layout, but I needed to make dynamic adjustments to several views. Here is what has worked for me:
Do NOT have views or components laid out in interface builder.
Add your views purely programmatically starting with alloc/init and setting their frames appropriately.
Done.
This happened to me in a project without storyboards or xib files. All 100% code. I had an ad banner at the bottom and wanted the view bounds to stop at the ad banner. The view would resize itself automatically after loading. I tried every resolution on this page but none of them worked.
I ended up just creating a sub view with the shortened height and placed that in into the main view of the controller. Then all my content went inside the sub view. That solved the problem very easily without doing anything that felt like it was going against the grain.
I am thinking if you want a view that is not the normal size that fills the window then you should use a sub view for that.
Instead of disabling autolayout, I would just calculate the new constraint with the frame you are replacing. That appears to me to be the appropriate way. If you are adjusting components that rely on constraints, adjust them accordingly.
For example, if you have a vertical constraint of 0 between two views (myView and otherView), and you have a pan gesture or something that adjusts the height of myView then you can recalculate the constraint with the adjusted values.
self.verticalConstraint.constant = newMyViewYOriginValue - (self.otherView.frame.origin.y + self.otherView.frame.size.height);
[self.myView needsUpdateConstraints];
For those of you who are using auto layout, please check out my solution here. You should be making #IBOutlet's of the constraints you want to adjust and then change their constants.
if it's xib file:
select the .xib file
select the "File's Owner"
show the Utilities
click on: "File Inspector"
Under "Interface Builder Document" disable: "Use Autolayout"

Resources