Make NSView with fixed width and variable height that fits contents - cocoa

I want to create a view with fixed width, but variable height. It means that view should automatically be resized according to its contents height, but, at the same time, it should keep the same width.
How can I achieve that programmatically?
For example, I've got the next piece of code to create a label and a button:
NSTextField *label = [[NSTextField alloc] initWithFrame:[self frame]];
[label setEditable:NO];
[label setBackgroundColor:[NSColor clearColor]];
[label setBezeled:NO];
[label setFont:[NSFont fontWithName:#"Lucida Grande" size:13.0]];
[label setStringValue:#"Sample label text"];
NSButton *button = [[NSButton alloc] initWithFrame:primaryBounds];
[button setBezelStyle:10];
[button setTitle:#"Sample button text"];
[button setBounds:NSInsetRect([button bounds], -8.0, 0)];
[button sizeToFit];
[[self contentView] addSubview:label];
[[self contentView] addSubview:button];
They were set to fill the entire contentView frame. How can I set my label to have fixed width and variable height (based on text contents of itself), and my button to be attached to the bottom of the label?
Okay, I've managed to autosize label like this:
NSTextView *label = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, [self frame].size.width, 0)];
[label setEditable:NO];
[label setBackgroundColor:[NSColor clearColor]];
[label setFont:[NSFont fontWithName:#"Lucida Grande" size:13.0]];
[label setString:#"Sample label text"];
[label setHorizontallyResizable:NO];
[label sizeToFit];

Under Autolayout using Mountain Lion, you would tell the text field what its preferred width should be:
[textField setPreferredMaxLayoutWidth:200]
Now the text field will measure the size of its content as if it wraps at 200 points, and once the content reaches that width, the text field will prefer to grow vertically.
To attach the button to the bottom of the label, you would add a constraint that says the bottom of the label equals the top of the button, plus 22:
[parentView addConstraint:
[NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:button
attribute:NSLayoutAttributeTop
multiplier:1
constant:22]];
or using the visual format language and standard Aqua spacing:
NSDictionary *viewsDict = NSDictionaryOfVariableBindings(label, button);
[view addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:[label]-[button]"
options:0
metrics:nil
views:viewsDict]];

Related

Auto Layout on OS X - make NSTextField fill superview

I can't get Auto Layout to work on OS X. What I'm trying to do is pretty simple, namely I have an NSTextField that is supposed to fill the entire space of its superview. Here's the minimal working example code I'm using:
#import AppKit;
int main() {
#autoreleasepool {
NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 300, 300)
styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask)
backing:NSBackingStoreBuffered
defer:NO];
NSTextField *textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];
textField.stringValue = #"Lorem ipsum dolor sit atmet.";
[window.contentView addSubview:textField];
textField.translatesAutoresizingMaskIntoConstraints = NO;
window.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[textField setContentHuggingPriority:1
forOrientation:NSLayoutConstraintOrientationHorizontal];
[textField setContentHuggingPriority:1
forOrientation:NSLayoutConstraintOrientationVertical];
[window.contentView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[textField]-|"
options:0
metrics:nil
views:#{#"textField": textField}]];
[window.contentView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[textField]-|"
options:0
metrics:nil
views:#{#"textField": textField}]];
window.contentView.wantsLayer = YES;
window.contentView.layer.borderWidth = 5;
[window makeKeyAndOrderFront:nil];
[[NSApplication sharedApplication] run];
[textField release];
[window release];
}
return EXIT_SUCCESS;
}
When I run this, the text field doesn't appear. When you resize the window, you can clearly see the border of the content view resizing properly. What am I doing wrong?
I believe you shouldn't alter NSWindow's contentView behavior, e.g. setting its translatesAutoresizingMaskIntoConstraints property.
Removing the following line works for me:
window.contentView.translatesAutoresizingMaskIntoConstraints = NO;

How to place 2 NSTextView vertically aligned with a space in between, and make container view resize accordingly with auto layout

Hope the title is clear. Trying to have something below
--------------------
| [titleTextView] |
| | |
| [detailsTextView]|
--------------------
With the code that I tried, the container resized, but both titletextView and detailsTextView are placed together (overlapping each others). I know I init both at (16,0) but shouldn't the constrain place them correctly?
I also get the following error: Unable to simultaneously satisfy constraints:
("<NSLayoutConstraint:0x60000008fcd0 NSTextView:0x600000134be0.bottom == NSView:0x600000134c80.bottom + 20>",
"<NSAutoresizingMaskLayoutConstraint:0x608000093ce0 h=--& v=--& V:[NSTextView:0x600000134be0]-(0)-| (Names: '|':NSView:0x600000134c80 )>")
Code:
//title textView
self.titleTextView = [[NSTextView alloc] initWithFrame:NSMakeRect(16, 0, [self.view frame].size.width - 30, 0)];
[self.titleTextView setEditable:NO];
[self.titleTextView setBackgroundColor:[NSColor clearColor]];
[self.titleTextView setString:#"potentially long text."];
[self.titleTextView setHorizontallyResizable:NO];
[self.titleTextView sizeToFit];
//detail textView
self.detailsTextView = [[NSTextView alloc] initWithFrame:NSMakeRect(16, 0, [self.view frame].size.width - 30, 0)];
[self.detailsTextView setEditable:NO];
[self.detailsTextView setBackgroundColor:[NSColor clearColor]];
[self.detailsTextView setString:#"Very long text."];
[self.detailsTextView setHorizontallyResizable:NO];
[self.detailsTextView sizeToFit];
//Adding to self.view
[self.view addSubview: self.titleTextView];
[self.view addSubview: self.detailsTextView];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.titleTextView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.detailsTextView
attribute:NSLayoutAttributeTop
multiplier:1
constant:20]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.titleTextView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1
constant:20]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.detailsTextView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1
constant:20]];
When working with autolayout, you don't want to think about the layout in terms of frames at all. The constraints will determine the frame of your views.
Also, if you're creating the layout in code, you have to call setTranslatesAutoresizingMaskIntoConstraints:NO for the views which you want the autolayout engine to apply to.
So, you'd want to do something like:
UIView* titleTextView = [[UIView alloc] init];
[titleTextView setTranslatesAutoresizingMaskIntoConstraints:NO];
UIView* detailsTextView = [[UIView alloc] init];
[detailsTextView setTranslatesAutoresizingMaskIntoConstraints:NO];
...additional setup stuff...
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-[titleTextView]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(titleTextView)]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-[detailsTextView]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(detailsTextView)]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[titleTextView][detailsTextView]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(titleTextView, detailsTextView)]];
If working in code, I'd strongly recommend checking out the visual format language which will make setting up constraints much more efficient in code, but you can also do the same thing as above using individual constraints.
Forget all you've done earlier with rects and start thinking in relative positions.
Using my favorite category for autolayout:
https://github.com/jrturton/UIView-Autolayout
you can achieve what you want with these simple constraints (which I find MUCH more readable and intuitive than any of the official API solutions):
[self.titleTextView pinToSuperviewEdges:JRTViewPinLeftEdge|JRTViewPinTopEdge inset:20.0];
[self.detailsTextView pinToSuperviewEdges:JRTViewPinLeftEdge inset:20.0];
[self.detailsTextView pinEdge:NSLayoutAttributeTop toEdge:NSLayoutAttributeBottom ofView:self.titleTextView inset:20];
This will pin both textviews 20 pixels from the left, titleTextView 20 pixels from the top and detailsTextView 20 pixels below titleTextView. Also, the category will add the constraints to the correct view in each case.

Aligning NSTextField and an Image

I am trying to align an NSTextField and an NSImageView.. My current code is below. I have tried a bunch of different approaches including subclassing NSTextFieldCell (found here on SO), messing around with the frame of the text field, and tweaking constraints, but I just can't get it.. No matter what I do, it looks like the screenshot below..
I also have discovered that when I don't apply a font to the label, alignment works as I would expect -- it is vertically aligned with the image.
So the question is, a) why in the world does applying a font screw up the alignment, and b) how do I get around this, ideally in a dynamic way that will adapt if i change the font at runtime..
- (id)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
if (self) {
NSView *spacer1 = [[NSView alloc] init];
[spacer1 setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:spacer1];
NSView *spacer2 = [[NSView alloc] init];
[spacer2 setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:spacer2];
NSImage *icon = [NSImage imageNamed:#"05-arrow-west"];
NSImageView *iconView = [[NSImageView alloc] init];
[iconView setTranslatesAutoresizingMaskIntoConstraints:NO];
[iconView setImage:icon];
[self addSubview:iconView];
NSFont *font = [NSFont fontWithName:#"HelveticaNeue-Light" size:14];
NSTextField *label = [[NSTextField alloc] init];
[label setTranslatesAutoresizingMaskIntoConstraints:NO];
[label setEditable:NO];
[label setSelectable:NO];
[label setBezeled:NO];
[label setDrawsBackground:NO];
[label setFont:font];
[label setTextColor:[NSColor lightGrayColor]];
[label setStringValue:#"Test String"];
[self addSubview:label];
NSDictionary *views = NSDictionaryOfVariableBindings(iconView, label, spacer1, spacer2);
[self addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[spacer1(>=0)][iconView]-5-[label][spacer2(==spacer1)]|"
options: 0
metrics:nil
views:views]];
[self addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[iconView]|"
options: 0
metrics:nil
views:views]];
[self addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[label]|"
options: 0
metrics:nil
views:views]];
[self setContentHuggingPriority:200 forOrientation:NSLayoutConstraintOrientationVertical];
[iconView setContentHuggingPriority:200 forOrientation:NSLayoutConstraintOrientationVertical];
[label setContentHuggingPriority:200 forOrientation:NSLayoutConstraintOrientationVertical];
}
return self;
}
This is a very common problem related to AppKit calculating incorrect metrics for certain fonts. Helvetica Neue and its variants are susceptible to this problem. Autolayout depends on the intrinsicContentSize of the NSTextField, which uses broken metrics to calculate the appropriate size to display the text. The only way I know of to work around this problem is to use magic offsets in your layout constraints to manually align the text.
In the end, this did it:
[self addConstraint:[NSLayoutConstraint constraintWithItem:label
attribute:NSLayoutAttributeBaseline
relatedBy:NSLayoutRelationEqual
toItem:iconView
attribute:NSLayoutAttributeCenterY
multiplier:1
constant:0]];

Old label text doesn't get cleared when I use the UIColor clearColor method in Xcode 4.5.2. The new text is getting superimposed on the old

Old label text doesn't get cleared when I use the UIColor clearColor method in Xcode 4.5.2. The new text is getting superimposed on the old.
Here is my code below. Whenever I press restart, I want my labels to get reset to #"". But it is not getting cleared.
CGRect frame=CGRectMake(intX, intY, 15, 18);
UILabel *label = [[UILabel alloc] initWithFrame:frame];
[self.view addSubview:label];
[label setFont:[UIFont fontWithName:#"ChalkboardSE-Light" size:17]];
CGRect frame1=CGRectMake(intSerialNoX, intY, 20, 18);
UILabel *label1 = [[UILabel alloc] initWithFrame:frame1];
[self.view addSubview:label1];
[label1 setFont:[UIFont fontWithName:#"ChalkboardSE-Light" size:17]];
label1.textColor = color;
label.backgroundColor=[UIColor clearColor];
label1.backgroundColor=[UIColor clearColor];
if([strAction isEqualToString:#"GO"])
{
[label1 setText:[myLableSerialNoArray objectAtIndex:i]];
[label setText:[[myLableWordsArray objectAtIndex:i] substringWithRange:NSMakeRange(j,1)]];
}
else if([strAction isEqualToString:#"RESTART"])
{
[label1 setText:#""];
[label setText:#""];
}
Have you set breakpoints to be sure [label1 setText:#""]; is getting called?
In any case I think the problem is that you are alloc and init'ing new labels every time this method is called, and placing them on top of labels that are already there from previous calls to the same method. You probably only want to do the alloc/init once, and then add a property to hold a reference to each label, which you can then use inside the method to set / reset the text.

How does NSScrollView works?

How does NSScrollView works in mac applications? I've write the following code but the scrolling is not working.
NSDictionary *temp=[[ NSDictionary alloc] init ];
NSScrollView *scroll = [[NSScrollView alloc ] initWithFrame:CGRectMake(0, 0, 100, 100)];
NSArray *dicArray=[NSArray arrayWithObjects:dict,dict1,dict2,dict3,dict4, nil];
for (int i=0; i<[dicArray count]; i++)
{
int offset=100;
int x=10;
int y=20;
y=y+(i*offset);
temp= [dicArray objectAtIndex:i];
NSRect titleRect=NSMakeRect(x,y,100,30);
NSRect subtitleRect=NSMakeRect(x, y+20, 400, 20);
NSTextField *title=[[NSTextField alloc] initWithFrame:titleRect];
[[title cell] setStringValue:[temp objectForKey:key]];
[[title cell] setWraps:NO];
[[title cell] setScrollable:YES];
[[title cell] setEditable:NO];
[[title cell] setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSMinusSetExpressionType]]];
[title setDrawsBackground:NO];
[title setBordered:NO];
NSTextField *subtitle=[[NSTextField alloc] initWithFrame:subtitleRect ];
[[subtitle cell] setStringValue:[temp objectForKey:subkey]];
[[subtitle cell] setWraps:NO];
[[subtitle cell] setScrollable:YES];
[[subtitle cell] setEditable:NO];
[[subtitle cell] setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]]];
[subtitle setDrawsBackground:NO];
[subtitle setBordered:NO];
[scroll addSubview:title];
[scroll addSubview:categoryOkButton];
[scroll addSubview:subtitle];
}
[[self window] setContentView:scroll];
[scroll release];
My Cocoa is a bit rusty, but NSScrollView works in a different way than UIScrollView. You don't just add subviews, that doesn't work with NSScrollView.
What you need is a container view into which you add all your subviews. So you create an NSView of the appropriate size and add all your views to that container view. Then you set:
[myScrollView setDocumentView:myContainerView];
See the Scroll View Programming Guide for more details.

Resources