Instantiating and setting size of a UIButton subclass with NSLayoutConstraint - uibutton

How do I set the size of a UIButton when instantiating it, ready to use for NSLayoutConstraint?
Code:
UIButtonRaisedSilver *loginButton = [UIButtonRaisedSilver buttonWithType:UIButtonTypeCustom];
[loginButton addTarget:self action:#selector(login) forControlEvents:UIControlEventTouchUpInside];
[loginButton setTitle:#"Log in" forState:UIControlStateNormal];
NSDictionary *views = #{#"tableView" : self.tableView, #"loginButton": loginButton, #"signupButton": signupButton};
[NSLayoutConstraint constraintsWithVisualFormat:#"V:[tableView]-50-[loginButton()]"
options:NSLayoutFormatAlignAllCenterX
metrics:nil
views:views];

It turns out, width and height are also part of the constraints system in iOS6/(OSX 10.8)(i think its 10.8 - havent developed for Mac OS yet).
Make sure the constraint is vertical for height and set the height in the brackets as below...
For example:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:[loginButton(30)]"
options:NSLayoutFormatAlignAllCenterX
metrics:nil
views:views];

Related

UIButton tap area got smaller on iOS 11

I notice the tap area for my UIButton got smaller since iOS 11. To confirm, I commented out the desired background color on its subviews and put purple color on the button itself.
iOS 10.0.3 (Button shown as desired)
iOS 11.1 (Tap area got smaller)
My code follows.
UIImage *ringImage = [UIImage imageNamed:#"ball20r"];
UIEdgeInsets insets = UIEdgeInsetsMake(10,10,10,10); //padding
UIImage *stretchableImage = [ringImage resizableImageWithCapInsets:insets];
UIImageView *imageView = [[UIImageView alloc]initWithImage:stretchableImage];
// imageView.backgroundColor = backgroundColor; //disabled for testing
imageView.frame = CGRectMake(0, 0, label.frame.size.width + 16.0, label.frame.size.height + 16.0);
[imageView addSubview:label];
label.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = #{#"label":label};
NSArray *constraint_H = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(8)-[label]-(8)-|"
options:0
metrics:nil
views:viewsDictionary];
NSArray *constraint_V = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(8)-[label]-(8)-|"
options:0
metrics:nil
views:viewsDictionary];
[imageView addConstraints:constraint_H];
[imageView addConstraints:constraint_V];
UIButton *calendarButton = [UIButton buttonWithType:UIButtonTypeCustom];
[calendarButton addTarget:self action:#selector(chooseACalendarToSave) forControlEvents:UIControlEventTouchUpInside];
calendarButton.frame = imageView.frame;
[calendarButton addSubview:imageView];
//added for testing only
calendarButton.backgroundColor = [UIColor purpleColor];
_calendarButton = [[UIBarButtonItem alloc]initWithCustomView:calendarButton];
How can I make the area for iOS just like iOS 10.0.3 and earlier?
Thanks for your help.
The answer is here.
iOS 11 UIBarButtonItem images not sizing
if (#available(iOS 9, *)) {
[cButton.widthAnchor constraintEqualToConstant: standardButtonSize.width].active = YES;
[cButton.heightAnchor constraintEqualToConstant: standardButtonSize.height].active = YES;
}
As soon as I inserted above code, the button started working as expected. Thank you.

Objective C Visual Autolayout NSTextField dynamic height

I am trying to create a dynamic size NSView using visual auto layout. I am trying to achieve something like the following diagram.
I added following constraints to achieve this.
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-15-[_iconImageView(39)]-12-[_textView(239)]-15-|"
options:NSLayoutFormatAlignAllTop metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-15-[_textView]-4-[_mainButton]-5-|"
options: NSLayoutFormatAlignAllLeft metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"[_mainButton]-15-[_secondaryButton]"
options:NSLayoutFormatAlignAllTop metrics:nil views:views]];
But this creates the following layout.
As per my understanding, the following VFL will layout _textView 15px from top and then layout _mainButton after 4px from _textView bottom. But in actual _mainbottom is layout after 4px from top of _textView. Am i missing something here?
#"V:|-15-[_textView]-4-[_mainButton]-5-|"
Update
I replaced NSTextView with NSTextField. But now the problem is, NSTextField does not grow more than a single line height, but NSTextField is multi-line. Complete code for layouting and setting up views is as follow.
-(void)setupView {
_textField = [[NSTextField alloc] initWithFrame:NSZeroRect];
[[_textField cell] setWraps:YES];
[_textField setLineBreakMode:NSLineBreakByWordWrapping];
[[_textField cell] setTitle:#"This is a _textView, This will contain dynamic resizing text."];
[_textField setSelectable:NO];
[_textField setEditable:NO];
[_textField setDelegate:self];
[_textField setBordered:NO];
[_textField sizeToFit];
[_textField setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:_textField];
_iconImageView = [[NSImageView alloc] initWithFrame:NSZeroRect];
[_iconImageView setImage:[NSImage imageNamed:#"notify-warning-icon"]];
[_iconImageView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:_iconImageView];
_mainButton = [[NSButton alloc] init];
_mainButton.translatesAutoresizingMaskIntoConstraints = NO;
[_mainButton setTitle:#"_mainButton"];
[self addSubview:_mainButton];
_secondaryButton = [[NSButton alloc] init];
_secondaryButton.translatesAutoresizingMaskIntoConstraints = NO;
[_secondaryButton setTitle:#"_secondaryButton"];
[self addSubview:_secondaryButton];
NSDictionary *views = NSDictionaryOfVariableBindings(_textField,_iconImageView,_mainButton,_secondaryButton);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-15-[_iconImageView(39)]-12-[_textField(239)]-15-|"
options:NSLayoutFormatAlignAllTop metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-15-[_textField]-4-[_mainButton]-5-|"
options: NSLayoutFormatAlignAllLeft metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"[_mainButton]-15-[_secondaryButton]"
options:NSLayoutFormatAlignAllTop metrics:nil views:views]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:0.f constant:320.f]];
}
But if it changed this
#"V:|-15-[_textField]-4-[_mainButton]-5-|"
with this
#"V:|-15-[_textField(>=40)]-4-[_mainButton]-5-|"
I get the following output
But the problem is textfield content is dynamic and could change at runtime, it could be 2 lines, 3 lines etc. So how could i add some height constraint to NSTextField that will change NSTextField height depending on its content.
You can use a text field rather than a text view and set its preferredMaxLayoutWidth property.
By default, if preferredMaxLayoutWidth is 0, a text field will compute its intrinsic size as though its content were laid out in one long line (or, at least, without any maximum width). Even if you apply a constraint that limits its actual width, that doesn't change its intrinsic height and therefore it typically won't be tall enough to contain the text as wrapped.
If you set preferredMaxLayoutWidth, then the text field will compute its intrinsic size based on the text as wrapped to that width. That includes making its intrinsic height tall enough to fit.

How do I simply programmatically place a UIButton upon a host view via Constraints?

I want to programmatically generate an overview (UIButton) in the lower left-hand corner.
This is currently being done using Interface Builder. However I need to repeat this for multiple host UIViews. So I decided to write a utility class method to do this.
+ (UIButton *)attachGreenPlusButtonTo:(UIView *)hostView {
NSLog(#"--- {attachGreenPlusButtonTo} ---");
// Green Button --------------------------------------------------------------------
UIImage *greenPlusImage = [UIImage imageNamed:#"btn_plus"];
UIButton *greenButton = [UIButton buttonWithType:UIButtonTypeCustom];
[greenButton setImage:greenPlusImage forState:UIControlStateNormal];
greenButton.tag = 22;
[greenButton setTranslatesAutoresizingMaskIntoConstraints:NO];
[hostView addSubview:greenButton];
// Positioning the Green '+' Button:
// Button is 4 pts from superview right edge:
NSArray *horizontalConstraints =
[NSLayoutConstraint constraintsWithVisualFormat:#"H:[greenButton(==57)]-4-[hostView]"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(hostView,greenButton)];
[hostView addConstraints:horizontalConstraints];
NSArray *verticalConstraints =
[NSLayoutConstraint constraintsWithVisualFormat: #"V:[greenButton(==57)]-34-[hostView]"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(hostView,greenButton)];
[hostView addConstraints:verticalConstraints];
[hostView layoutIfNeeded];
return greenButton;
}
I'm getting close, but now the button is being positioned at (-61, -91).
What gives?
Call this after creating the greenButton:
[greenButton setTranslatesAutoresizingMaskIntoConstraints:NO];
otherwise the system will create (unwanted) constraints for you.
Your visual layout doesn't look right either. Use vertical bar | to refer to the superview:
"H:[greenButton(==57)]-4-|"
and
"V:[greenButton]-34-|"
I thought I had a problem using '|' for superview within the method due to a bug which eventually appeared to be due do something else. So...
Upon feedback, I return to the use of '|' as the superview vs the 'hostView':
// Positioning the Green '+' Button:
// Button is 4 pts from superview right edge:
NSArray *horizontalConstraints =
[NSLayoutConstraint constraintsWithVisualFormat:#"H:[greenButton(==57)]-4-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(hostView,greenButton)];
[hostView addConstraints:horizontalConstraints];
NSArray *verticalConstraints =
[NSLayoutConstraint constraintsWithVisualFormat: #"V:[greenButton(==57)]-34-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(hostView,greenButton)];
[hostView addConstraints:verticalConstraints];
[hostView layoutIfNeeded];
This worked.
All appears to be okay now.

How to use UIBezierPath in a auto layout view?

As far as I know, views displayed by constraints don't got a frame, so what should I do when I want to draw some lines in these views? Methods like moveToPoint do need a CGRect.
Here's my check: NSLog(#"%f,%f,%f,%f",self.contentView.frame.origin.x,self.contentView.frame.origin.y,self.contentView.frame.size.width,self.contentView.frame.size.height);
And the result is 0.000000,0.000000,0.000000,0.000000
For more details, here's my code:
-(void)loadView
{
self.view = [[UIView alloc]init];
self.titleView = [[UIView alloc]init];
self.placesHolder = [[UIView alloc]init];
self.contentView = [[UIView alloc]init];
self.titleView.translatesAutoresizingMaskIntoConstraints = NO;
self.placesHolder.translatesAutoresizingMaskIntoConstraints = NO;
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
self.titleView.backgroundColor = [UIColor grayColor];
self.placesHolder.backgroundColor = [UIColor blueColor];
self.contentView.backgroundColor = [UIColor redColor];
[self.view addSubview:self.titleView];
[self.view addSubview:self.placesHolder];
[self.view addSubview:self.contentView];
NSDictionary *timeLineViewMap = #{#"titleView":self.titleView,
#"placesHolder":self.placesHolder,
#"contentView":self.contentView
};
NSArray *titleHorizon = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[titleView]|" options:0 metrics:nil views:timeLineViewMap];
NSArray *placesHolderHorizon = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[placesHolder(==58)]-0-[contentView]|" options:0 metrics:nil views:timeLineViewMap];
NSArray *titleVertical = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[titleView(==58)]-0-[placesHolder]|" options:0 metrics:nil views:timeLineViewMap];
NSArray *contentViewConstrain = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[titleView]-0-[contentView]|" options:0 metrics:nil views:timeLineViewMap];
[self.view addConstraints:titleHorizon];
[self.view addConstraints:placesHolderHorizon];
[self.view addConstraints:titleVertical];
[self.view addConstraints:contentViewConstrain];
}
Of course UIView does have a frame even with AutoLayout. You just don't set the values manually, AutoLayout is doing that for you. Methods like setFrame: are still called. Simply implement drawRect: like you always did.
Well, problem kind of solved, it seems that a frame of a view don't get a size until it's on the viewDidAppear move. Then I checked some other questions like iOS AutoLayout - get frame size width, which indicate that addConstraints should be put in the viewDidLayoutSubviews method, which is not in the viewController life cycle.

UIButtons in Landscape dont show their titles

I have a strange thing and I would like to fi that.
I have a View which is in landscape mode. Due to landscape mode I have a button which need to change his frame:
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self
action:#selector(aMethod:)
forControlEvents:UIControlEventTouchDown];
[button setTitle:#"Show View" forState:UIControlStateNormal];
button.frame = CGRectMake(80.0, 210.0, 40.0, 160.0);
[view addSubview:button];
Normal:
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
Landscape:
button.frame = CGRectMake(80.0, 210.0, 40.0, 160.0);
All is fine, except the Title of the Button: "SHow View"
Instead of saying Show view it says this:
w
e
i
V
w
o
h
S
read this from button to top :P
This is my button title, because its landscape, but the titles go still for Portrait.
Any help appreciated :)
First of all, it seems you are not setting the rect of your button right. Remember that the third parameter, is the width:
CG_INLINE CGRect
CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)
{
CGRect rect;
rect.origin.x = x; rect.origin.y = y;
rect.size.width = width; rect.size.height = height;
return rect;
}
In button.frame = CGRectMake(80.0, 210.0, 40.0, 160.0); you are setting the width of your button to 40.
More importantly, you should use strings and struts, or even better, auto layout. Life is so much easier when you let computers do the math. For just one button, springs and struts is all you need, but if you want to create a more flexible interface, and you can target iOS 6, then you should use autolayout.
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self
action:#selector(aMethod:)
forControlEvents:UIControlEventTouchDown];
[button setTitle:#"Show View" forState:UIControlStateNormal];
[button setTranslatesAutoresizingMaskIntoConstraints:NO];
[[self view] addSubview:button];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:button
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:nil
attribute:NSLayoutAttributeWidth
multiplier:1
constant:200];
[button addConstraint:widthConstraint];
NSLayoutConstraint *bottomContraint = [NSLayoutConstraint constraintWithItem:button
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:[self view]
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-20];
NSLayoutConstraint *centerContraint = [NSLayoutConstraint constraintWithItem:button
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:[self view]
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0];
NSArray *constraints = [[NSArray alloc] initWithObjects:bottomContraint, centerContraint, nil];
[[self view] addConstraints:constraints];
And if you want to use springs and struts, you have to set the autoresizing mask:
[button setAutoresizingMask:(UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin)];
These two things are much more easier in Interface Builder.