I've just started experimenting with NSStackView, and I'm having a very interesting problem which I can't solve. I've scoured the auto layout guides on Apple's website as well as the NSStackView documentation, and can't seem to find anything.
My problem is that I have two identical NSScrollView objects (each with an embedded NSTextView) which are loaded from nib files. When I add these views to my stack view, the one that is added first takes up 100% of the available space, and the second collapses completely down to the bottom with a height of 2 pixels while taking up all available horizontal space. In effect, this looks like the first view is the only one in the window. Here's what it currently looks like:
It's nearly impossible to see in this example because of the background color, but the scroll view ends a couple pixels above the bottom of the window. Here's a better view from the view hierarchy inspector, where I have this 2 pixel high view selected (click here to view larger):
Here's the relevant setup code:
// Load the stack view
self.inputViewController = [[NSViewController alloc] initWithNibName:#"Document_TextInputView" bundle:nil];
self.textView = (NSTextView *)[[(NSScrollView *)self.inputViewController.view contentView] documentView];
self.outputViewController = [[NSViewController alloc] initWithNibName:#"Document_TextOutputView" bundle:nil];
self.outputView = (NSTextView *)[[(NSScrollView *)self.outputViewController.view contentView] documentView];
// Add all views into the stack view
[self.stackView addView:self.inputViewController.view inGravity:NSStackViewGravityTop];
[self.stackView addView:self.outputViewController.view inGravity:NSStackViewGravityBottom];
self.stackView.orientation = NSUserInterfaceLayoutOrientationVertical;
// Load the text into the window.
[self.textView setString:self.cachedText];
[self.outputView setString:#"=== PROGRAM OUTPUT ===\n"];
[self.codeActionSegmentedControl setEnabled:NO forSegment:1];
From what I understand, the intrinsic content size should prohibit the view from getting shrunk this small. I'm not too familiar with NSStackView, so any help would be appreciated.
Alright, I've found the solution to my own problem and I am posting it so everyone who searches for this and finds the question will have the answer.
The issue is that NSScrollView does not have an intrinsic content size, which prohibits the NSStackView which knowing what height it ought to be, hence the second NSScrollView was being collapsed. The constraints created by default in Xcode give the NSScrollView's relation to other elements, but this information does not tell the stack view anything about what its height should be.
The solution is to add a height constraint to the NSScrollView (programmatically or in Interface Builder) so that NSStackView can lay out the views properly. Then, it all just magically works.
Related
I am developing in Cocoa, and I am currently having problems with filling the background of a NSWindowController.
I understand that subclassing is the way forward if you want to customise your cocoa app. So I created a custom NSView named whiteView and added this view as a subview to my windowController's contentView; however, there are some issues with completely filling the background of the window. Can anyone explain how I can have the color cover the complete surface area of the window's frame pls. Thank you
These are the results that I have so far.
1) This is the window when I leave it as it is, notice the white color only having covered half of the window.
2)Here is the same window again when I adjust the window far to the right and bottom. The white screen seems to stretch enough so that it covers the elements.
This is how I create the custom view
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
[[NSColor whiteColor] set];
NSRectFill([self bounds]);
}
And this how I achieve plaster the view onto my window.
WhiteView *whiteBackgroundView = [[WhiteView alloc] initWithFrame:self.window.frame];
[self.window.contentView addSubview:whiteBackgroundView positioned:NSWindowBelow relativeTo:self.window.contentView];
What do I need to do to correctly allow for my window's background to be fully covered in white?
First, the simple solution is to use -[NSWindow setBackgroundColor:] to just set the window's background color. No need for a view.
If you're still interested in how to fix the view-based approach, probably what's wrong is that you haven't set the autoresizing mask of the view to make it follow the changes in the window size. For example, you could do [whiteBackgroundView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable].
However, you could also set the whiteBackgroundView as the window's contentView rather than as a subview of it. The window's content view is always kept at the size necessary to fill the window's content rect. All of the other views of your window would be subviews of the white background view. In my opinion, this is better than making it a sibling that just happens to be at the back. Using relative ordering among siblings views to achieve a particular rendering order is a hack.
Finally, there's no reason to invoke super's implementation in your -drawRect: if the superclass is NSView itself. NSView doesn't do any drawing in its -drawRect:. Also, your subclass takes over full responsibility for the entire drawn contents of its bounds, so you'd overdraw whatever super had drawn, anyway. (Also, you need only fill dirtyRect rather than [self bounds].)
While you're at it, since your class fills its bounds, you should override -isOpaque to return YES for optimization.
Update: regarding the frame of the view: if it's not going to be the window's content view, then you want to set its frame to be its prospective superview's bounds. So, you should have used self.window.contentView.bounds if you wanted whiteBackgroundView to fill the content view.
More generally, if you want the content rect of a window, you would do [window contentRectForFrameRect:window.frame]. But if a view is going to be a window's content view, there's no need to set its frame to anything in particular. It will be resized automatically.
Update 2:
To transfer the view hierarchy from the original content view to the new content view (when you're making the white background view the content view):
NSArray* subviews = [self.window.contentView.subviews copy];
[subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
[whiteBackgroundView setSubviews:subviews];
[subviews release];
(Written for manual retain-release. If using ARC, just drop the -release invocation.)
Regarding the frame to use, as mentioned in the first update: keep in mind that the view's frame should be expressed in the coordinate system of its superview. So, as I said, self.window.contentView.bounds would work if you're putting the new view into the content view. The window's frame and content rect are in screen coordinates. They would be completely incorrect for positioning a view.
I have a custom view controller class. I want to instantiate multiple custom view controllers and add their views to my NSStackView. I add a new view to the stack view by clicking a button. The button calls this method:
[stackView insertView:myCustomViewController.view atIndex:0 inGravity:NSStackViewGravityBottom];
However, when a new view is added, it's added stackView.spacing below where the previous view was, but that previous view is no longer visible, however it is still listed as a view in my bottom gravity as evidenced when I call
NSLog(#"%lu",(unsigned long)[stackView viewsInGravity:NSStackViewGravityBottom].count);
I don't have this problem if I try adding NSButtons, so it's something to do with my custom view but I can't figure out what.
Please help. Thanks!
You should check that your custom view has constraints (or an intrinsic content size) that completely define the height of the view. If they don't, these views are collapsing down to 0 heights (this same thing has happened to me before).
You can check if this is happening by:
[stackView insertView:myCustomViewController.view atIndex:0 inGravity:NSStackViewGravityBottom];
[stackView layoutSubtreeIfNeeded];
NSLog(#"%f - %d", NSHeight(myCustomViewController.view.frame), [myCustomViewController.view hasAmbiguousLayout]);
With StackView, you'll see that the later ones print "0 - 1". The heights are 0, and they're ambiguous.
Vertical NSStackView's will only position views vertically, but won't give specific heights. (Same for horizontal stack views and widths).
Apologies if this has been asked before. I can't find any reference to this particular problem though.
I have an app which is basically a table view nested within a navigation controller. Each item from the table segues to a fresh view (via a generic push transition), containing some content within a scrollview. I have set this all up using storyboard for ease of layout.
When you click an item in the table, the intention is for the table to slide off the screen to the left and be replaced by the content view. This works fine in iOS 6, but since testing the app on iOS 7 I've noticed the functionality is different.
In iOS 7 the content view slides into frame as normal, but the table view only slides a little way to the left - still visible behind my new content. It disappears very suddenly after half a second or so, but the effect is very jarring as it creates a momentary overlap of two views.
This is only a problem because my content views have transparent backgrounds, but this is important to maintain for the effect I want. So just to be clear, my content view slides in over the top of the menu, which subsequently disappears. Looks very odd.
Any help on this would be much appreciated. I'd be curious to know the reason for this change and if there's a way I can fix it. Preferably by making the menu slide all the way offscreen again.
Thanks!
I had the same problem.
Try to add into target ViewController (that shows up after push)
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.backgroundColor = [UIColor redColor];
}
If all is ok you can change background to something like that
self.tableView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"GreenBkg.png"]];
ps. tableView needs to be defined as #property in your .h filw
I have a quite big problem, I am really not able to solve myself.
The result should look like this:
This image was made with photoshop and is part of the interface I try to build.
In the middle you see something, that should be a list of projects, you should be able to scroll, if it the list is bigger then the view.
So I am making a scrollview like this: (for some reason I cannot do this in the interface builder and want this to work programmatically)
NSScrollView *projectsListView = [[NSScrollView alloc] initWithFrame:NSMakeRect(15, 2, 801, 588)];
[projectsListView setHasVerticalScroller:YES];
Then I create the content view and set a pattern image as backgroundcolor:
NSClipView *contentView = [[NSClipView alloc] initWithFrame:NSMakeRect(0, 0,
[projectsListView frame].size.width, [projectsListView frame].size.height+(98*2))];
[contentView setBackgroundColor:[NSColor colorWithPatternImage:[NSImage imageNamed:#"BoxLineBackground"]]];
[contentView setDrawsBackground:YES];
Then set the view as document view:
[projectsListView setDocumentView:contentView];
Should work, right?
However the content view gets clipped and looks like this while scrolling:
I tried this to fix it, but it does nothing:
[[projectsListView documentView] setCopiesOnScroll:NO];
I also tried this, but it causes the contentview not to scroll at all.
The image stays the same, but I can move the scroller normally.
[[projectsListView contentView] setCopiesOnScroll:NO];
If I try to set the contentview with setContentView: instead of using setDocumentView:
it may work, but the scroller is gone, so it is also not working correctly.
I would really like to use the patternimage method, because I cannot tell how long the list will be. It depends on the user.
An additional problem then would be to get the whole thing rounded, but that does not matter that much. I tried to use a transparent border image and to overlay the NSScrollView with it using an NSImageView, but again this causes corruption, because it clips and moves the overlaying parts of the image view together with the content of the scrollview.
Anyone having an idea, how to achieve this?
Thanks
Rather than re-inventing the wheel, this interface should be implemented with a view-based NSTableView. The table cell UI could then be created in Interface Builder and you could control the background of the cells using the various NSTableView delegate methods. NSTableView will handle redraws upon scrolling correctly.
To handle the pattern color, just make the background of your cell a custom subclass of NSTableCellView and implement your pattern drawing code.
Regardless of all this, the problem you are having is due to an NSScrollView drawing optimisation. You can turn this off by calling [[yourScrollView contentView] setCopiesOnScroll:NO] on your NSScrollView instance. You can also set this in Interface Builder, just un-check the Copies on Scroll checkbox.
I fixed the problem by setting the Background Color on the NSScrollView instead on the NSClipView.
I though the background would be static in that case and I need to set it for the content view for that reason, but it works pretty well and does scroll together with the content view.
And thanks for Rob Keniger's answer. I will probably try this out.
Guys, I am a newbee for iOS.
I need to create dynamic layout since the GUI will be generated according to the data.
I checked the UIView references, it seems the standard way to add subview is like:
CGRect rect = CGRectMake(0, 0, width, height);
UILabel *label = [[UILabel alloc] initWithFrame: rect];
[someView addSubView: label];
But, maybe I can't be sure that the width and the height. In Java, container use layout manager to automatically deal with the width and height based on some rules. In iOS, can I use something like layout manager in Java?
Thanks.
Any clue will be OK.
You can do this in iOS, although it is not one-to-one with Java layouts. The get the idea of what is possible, use the Size Inspector in Interface Builder. Anything that is done there, such as allowing an item to grow horizontally or stay the same distance from the top, can be done programmatically. If further customization is needed, you can override event hooks in your view or controller, such as UIView's -layoutSubviews method.
iOS 9 provides UIStackView class which in essence is a layout manager:
The UIStackView class provides a streamlined interface for laying out
a collection of views in either a column or a row. Stack views let you
leverage the power of Auto Layout, creating user interfaces that can
dynamically adapt to the device’s orientation, screen size, and any
changes in the available space. The stack view manages the layout of
all the views in its arrangedSubviews property. These views are
arranged along the stack view’s axis, based on their order in the
arrangedSubviews array. The exact layout varies depending on the stack
view’s axis, distribution, alignment, spacing, and other properties.
Note that it's not applicable if you're supporting iOS 8 which is still pretty actual at the moment.