setStringValue in NSTextField will always change height - cocoa

I have a NSTextField in a view where layout is totally controlled by constraints and translatesAutoresizingMaskIntoConstraints is NO. I tried to use setStringValue to change the content like this:
[[self textfield] setStringValue:#"1\n2\n3\n"];
Then the height is changed to 4 lines which is not what I want. I need a NSTextField that can show only one line but still I can use up and down arrow keys to go into different lines. It is just like use option+enter to insert a newline in NSTextField.
I also tried to keep the height:
NSRect originalFrame = [[self textfield] frame];
[[self textfield] setStringValue:#"1\n2\n3\n"];
NSRect newFrame = [[self textfield] frame];
newFrame.size.height = originalFrame.size.height;
[[self textfield] setFrame:newFrame];
It doesn't work. I checked intrinsicContentSize and it returns (width=-1, height=73). Is there anything I can set to NSTextField so the height is of only one line like 22?

This is happening because you have set the auto layout of textfield with respect to the view. So just fixed the height of your textfield by clicking on the height checkbox like that below :-

Related

Creating right aligned NSView

I want to create a NSView container such that any NSControl object added should be right aligned.
I have added a method to MyCustomNSView class as following. Currently I am adding buttons which are getting left aligned.
- (void) _addButton:(NSString *)title withIdentifier:(NSString *)identifier {
NSButton *button = [[NSButton alloc] initWithFrame:NSMakeRect(100 * [_buttonIdentifierList count] + 10 , 5, 70, 20)];
[button setTitle:title];
[button setAction:#selector(actionButtonPressed:)];
[button setTarget:self];
[button setIdentifier:identifier];
[self addSubview:button];
[_buttonIdentifierList addObject:identifier];
}
So what modifications do I have to make to the above method so that it will add the objects from right side.
I was planning to do it mathematically(Generating frame origin that would generate right aligned origin point). I also tried out using NSLayoutConstrains but didnt work out..
How do I do it using autolayouts ?
To do it by manual positioning, you would compute the frame for the button something like this:
NSButton *button = [[NSButton alloc] initWithFrame:NSMakeRect(NSMaxX(self.bounds) - (100 * [_buttonIdentifierList count] + 10) - 70, 5, 70, 20)];
That is, you take your current calculation which is an offset toward the right (from the left edge) and negate it to make it an offset toward the left. You add the value of the right edge of the containing view so it's an offset from the right edge. That has computed the X position of the right edge of the button, so you subtract the button's width to get the origin of the button, which is on its left edge.
To use auto layout (which uses NSLayoutConstraint), you could do this:
NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect];
[button setTitle:title];
[button setAction:#selector(actionButtonPressed:)];
[button setTarget:self];
[button setIdentifier:identifier];
button.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:button];
__block NSButton* previousButton = nil;
if (_buttonIdentifierList.count)
{
NSString* previousButtonIdentifier = _buttonIdentifierList.lastObject;
[self.subviews enumerateObjectsUsingBlock:^(NSView* subview, NSUInteger idx, BOOL *stop){
if ([subview.identifier isEqualToString:previousButtonIdentifier])
{
previousButton = (NSButton*)subview;
*stop = YES;
}
}];
}
NSDictionary* metrics = #{ #"buttonWidth": #70,
#"buttonHeight": #20,
#"buttonSeparation": #30,
#"horizontalMargin": #10,
#"verticalMargin": #5 };
if (previousButton)
{
NSDictionary* views = NSDictionaryOfVariableBindings(button, previousButton);
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"[button(buttonWidth)]-(buttonSeparation)-[previousButton]" options:NSLayoutFormatAlignAllBaseline metrics:metrics views:views];
[self addConstraints:constraints];
}
else
{
NSDictionary* views = NSDictionaryOfVariableBindings(button);
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"[button(buttonWidth)]-(horizontalMargin)-|" options:0 metrics:metrics views:views];
[self addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[button(buttonHeight)]-(verticalMargin)-|" options:0 metrics:metrics views:views];
[self addConstraints:constraints];
}
[_buttonIdentifierList addObject:identifier];
Finding the previousButton would be simplified if you keep track of the buttons, rather than the identifiers. If you have a button object, it's easy to get its identifier, but the reverse (getting the button object when all you have is the identifier) is not as simple.
If you want to allow the buttons to be their natural width and height, rather than a fixed value, you can just leave out those width/height specifiers (that is, use [button] rather than [button(buttonWidth)]). If you want all of the buttons to have the same width, but let the system pick the width of the naturally widest button, you can use [button(==previousButton)]. Since a button's default compression resistance priority is higher than its content hugging priority, it will pick the smallest width that doesn't compress any of them.
If you want the buttons to be the standard distance away from each other, rather than the fixed value of 30 points, you can use use - instead of -(buttonSeparation)-. Similarly, if you want them to be the standard distance from the superview edge, you can use - instead of -(horizontalMargin)- or -(verticalMargin)-.

How do I use NSLayoutConstraint to make a subview take up remaining/total height of superview

I have a superview to wich I add two subviews (subview1 and subview2).
I want subview1 to have same width as superview and stretch with superview and also have a height of 30px and align to the top of superview. This I can get working with the following code:
NSDictionary *metrics = #{#"height":[NSNumber numberWithFloat:30.0f]};
NSDictionary *views = NSDictionaryOfVariableBindings(subview1);
NSArray *tabContainerConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[subview1(==height)]" options:NSLayoutFormatAlignAllTop metrics:metrics views:views];
[superview addConstraints:tabContainerConstraints];
metrics = nil;
tabContainerConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|[subview1]|" options:(NSLayoutFormatAlignAllLeading | NSLayoutFormatAlignAllTrailing) metrics:metrics views:views];
[superview addConstraints:tabContainerConstraints];
Then I want subview2 to also have same width as superview and stretch with superview and also I want subview2 to align its top to subview1's bottom and then I want subview2 to fill all of the remaining height of superview (align bottom to bottom) and stretch in height with superview. I try to do this with this code:
NSDictionary *metrics = nil;
NSDictionary *views = NSDictionaryOfVariableBindings(subview2);
NSArray *tabContainerConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|[subview2]|" options:(NSLayoutFormatAlignAllLeading | NSLayoutFormatAlignAllTrailing) metrics:metrics views:views];
[superview addConstraints:tabContainerConstraints];
views = NSDictionaryOfVariableBindings(subview1, subview2);
tabContainerConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[subview1][subview2]-0-|" options:0 metrics:metrics views:views];
[superview addConstraints:tabContainerConstraints];
But strange things happens... subview is always aligned to with its top to subview1's bottom and also has the full width of superview so this is good. But height is strange... When first drawn/displayed subview2 has a very limited height, somewhere between 20-30 pixel it seems, but I can force a redraw by switching to another tab/view and back and then it is drawn in full/correct height. My subview2 is a NSTextView, and when I type in text strange things happen, my subview2 suddenly does not take up all height and is no longer aligned with the bottom of superview.
I hope my explanation is ok, if not please ask any question. Any ideas on how to fix this? I thought the |-0-[view]-0-| would do the trick?
Thank you
Søren
It's not clear from your question whether you're running all of that code or the first snippet is for one view only and the second snippet is for two views.
You should be combining the two VFL strings into one, and you don't need some of the layout options that you are supplying. If you only have one view in the VFL string, the layout options are meaningless.
I'd suggest the following code for creating your constraints:
NSDictionary *metrics = #{#"height":[NSNumber numberWithFloat:30.0f]};
NSDictionary *views = NSDictionaryOfVariableBindings(subview1,subview2);
// Horizontal layout for subview 1
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[subview1]|" options:0 metrics:metrics views:views]];
// Vertical layout - the options here mean that subview2 will be the same width as subview1
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[subview1(==height)][subview2]|" options:NSLayoutFormatAlignAllLeft | NSLayoutFormatAlignAllRight metrics:metrics views:views]];
You may be having some issues with the intrinsic size of the text view, if it behaves differently when it has content inside it or not. Or, the superview itself may be changing its size - what are the constraints on the superview? You will need to set some borders / background colours or use an introspection tool to determine which frames are going funny.

NSTableView's frame inside a NSClipView/NSScrollView using auto layout

I'm trying to create a NSTableView inside a NSScrollView (the standard configuration, that is) in code, using auto layout. I can't figure out how to make this work.
Here's my loadView:
- (void)loadView
{
NSView *view = [[NSView alloc] init];
NSScrollView *tableScroll = [[NSScrollView alloc] init];
NSTableView *fileTable = [[NSTableView alloc] init];
[tableScroll setDocumentView:fileTable];
[tableScroll setHasVerticalScroller:YES];
[tableScroll setHasHorizontalScroller:NO];
fileTable.delegate = self;
fileTable.dataSource = self;
[fileTable setHeaderView:nil];
[fileTable setAllowsColumnReordering:NO];
NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:#"column1"];
[fileTable addTableColumn:column];
[tableScroll setTranslatesAutoresizingMaskIntoConstraints:NO];
[fileTable setTranslatesAutoresizingMaskIntoConstraints:NO];
[view addSubview:tableScroll];
NSDictionary *topViews = NSDictionaryOfVariableBindings(tableScroll);
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[tableScroll]|" options:0 metrics:nil views:topViews]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[tableScroll]|" options:0 metrics:nil views:topViews]];
self.fileTable = fileTable;
self.view = view;
}
What happens is that my table view's frame will be always equal to the bounds of the NSClipView. The view is inside a window and gets resized with it, and when I do that it'll resize the scrollview, the clip view and the table, but I can never scroll anywhere.
Looking at constraints I get, the NSScrollView gets constraints that set the clip view to fill it, the clip view has no constraints at all and the table view has a bunch of constraints related to the NSTableRowViews inside it.
If I add a constraint like |[fileTable(>=500)] to the clip view I'll get 500 pixels of NSTableView, but obviously I don't want to do that.
Even though this was answered by the poster in the comments above, I thought I’d put the answer here (having run into the same issue). If you are adopting auto layout, you would typically uncheck “Translates Mask Into Constraints” in the xib. However, for classes like NSScrollView and NSTableView, you should generally let them manage their own internal views by setting their translatesAutoresizingMaskIntoConstraints property to YES. It is still ok to set constraints that are external to these views, i.e. to resize in relation to their superview.
If you set translatesAutoresizingMaskIntoConstraints to NO, then you will need to supply constraints for all of the internal views, which unless you specifically need custom behavior (almost never), you will not want to do. This was the specific problem above.
An obvious side effect of not setting this correctly is that a table (for example) will not properly scroll beyond what is visible in the view.

cocoa - add NSTextfield to NSRect

I'm confused as to why the code below isn't working, what I would like to achieve is to have a NSTextfield in a NSRect but I'm not sure if it's possible and if it is how to do it, I tried the code below but it's not working...
NSRect city_label = NSMakeRect(20, 20, 7, 7);
NSTextField *label = [[NSTextField alloc] initWithFrame:city_label];
label.stringValue = #"Contents of NSTextfield";
The NSRect gets drawn in an NSView
Anyone any ideas?
An NSRect is not the sort of entity which could be "drawn in an NSView"--it is not an instance of a subview of NSView. An NSRect is just a C struct describe size (width and height) and origin (x and y).
After initializing your NSTextField with its frame (keep in mind that the origin here is relative to the view to which you will add the text field as a subview), you must add it to the view that you want to have as its superview. Assuming for a moment that we're in a custom subclass of NSViewController, your code just needs this additional line
[self.view addSubview:label];

Weird font behavior on the NSTextField

I added programmatically NSTextField to my NSView:
NSTextField *projectLabel = [[NSTextField alloc] initWithFrame:frame];
[projectLabel setStringValue:#"projectName"];
[projectLabel setBezeled:NO];
[projectLabel setDrawsBackground:NO];
[projectLabel setEditable:NO];
[projectLabel setSelectable:NO];
[projectLabel setFont:[NSFont controlContentFontOfSize:13]];
projectLabel.autoresizingMask = NSViewMaxXMargin | NSViewMinYMargin;
[self addSubview:projectLabel];
[self setAutoresizesSubviews:NO];
This field was added correctly, but when I change size of view (or even move window to second display), font on field changes very weird (see attached image).
on start
after change of the size
I do not know what I did wrong
I drew this label on drawRect every time, when the size changes.
So, you're manually telling the field to display in its parent view's drawRect:?
Don't do that. It's a subview, so it'll get told to draw in its turn anyway. Just let that happen.

Resources