Get frame of a view after autolayout - ios

I have a method:
- (void)underlineTextField:(UITextField *)tf {
CGFloat x = tf.frame.origin.x-8;
CGFloat y = tf.origin.y+tf.frame.size.height+1;
CGFloat width = self.inputView.frame.size.width-16;
UIView *line = [[UIView alloc] initWithFrame:(CGRect){x,y,width,1}];
line.backgroundColor = [UIColor whiteColor];
[self.inputView addSubview:line];
}
That underlines an input UITextField; The textfield has a width that changes depending on the screen width (nib autolayout).
I have tried using
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
and
[self.inputView setNeedsLayout];
[self.inputView layoutIfNeeded];
before I call this method with no change in result. the resulting line is much wider than the UITextField (it matches the original size in the Nib).
I just want the resulting frame of the UITextField in question after being processed by the autolayout
SOLUTION: (using 'Masonry' Autolayout)
- (UIView *)underlineTextField:(UITextField *)tf {
UIView *line = [[UIView alloc] initWithFrame:CGRectZero];
line.backgroundColor = [UIColor whiteColor];
[self.inputView addSubview:line];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(tf.mas_centerX);
make.width.equalTo(tf.mas_width).with.offset(16);
make.height.equalTo(#1);
make.top.equalTo(tf.mas_bottom);
}];
return line;
}

Your underline view has a static frame, it is not connected to the textField through constraints. Instead of setting the frame, add constraints to self.inputView
- (void)underlineTextField:(UITextField *)tf {
UIView *line = [[UIView alloc] init];
line.backgroundColor = [UIColor whiteColor];
[line setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.inputView addSubview:line];
// Vertical constraints
[self.inputView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[line(==1)]-(-1)-|" options:0 metrics:nil views:#{ #"line": line}]];
// Horizontal constraints
[self.inputView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(-8)-[line]-8-|" options:0 metrics:nil views:#{ #"line": line}]];
[self.inputView layoutIfNeeded];
}
After the layoutIfNeeded call, the frame for your view should be right. I hoped I got the constants right. Because the line appears one unit under the textView make sure to unset Clip Subviews in the storyboard for the textField
I hope this works for you. Let me know if you have questions!

You could adjust the frame of the underline in viewDidLayoutSubviews which is called after auto layout has set all the frames.
Note that you're creating a new view and calling addSubview: each time. You should do that once when you create the text view, outside of this method. Otherwise you'll create a new UIView every time your user rotates the device.

Related

Why we will get "Could not load quick look data for "" in iOS?

Though i added subView to the containerview.
Printing description of containerView:
<UIView: 0x7fd800b0a1a0; frame = (0 0; 0 0); layer = <CALayer: 0x6040006251e0>
Why i am getting view frame as (0 0; 0 0)
UIView* containerView = [UIView new];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
[containerView addSubview:contentView];
[containerView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|-15-[contentView]-15-|" options:0
metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
[containerView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[contentView]-15-|" options:0
metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
You created view with zero frame. Use other initializer:
UIView *containerView = [[UIView alloc] initWithFrame: CGRectMake(0, 0, w, h)];
But after you add view as subview its frame will still be wrong. The right frame will be after viewDidLayoutSubviews (for ViewController) or layoutSubviews (for View)
Edit:
containerView is not yet added to a view , it's logical not to add a subview to it until itself finds a parent like
[self.view addSubview:containerView]
Your problem is you are trying to constraint a view (contentView) to another (containerView ) that hasn't been yet know it's position
UIView* containerView = [UIView new];
change to this
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0,0,320,35)];
Always when you create a view programmatically the parent view has it's layout ready to add subviews to it and hook constraints between them
Also programmatically created constraints when VC launches can only be in viewDidLayoutSubviews
-(void)viewDidLayoutSubviews
{
if(Once)
{
// Add constraints here
Once = NO
[self.view layoutIfNeeded];
}
}

Auto Layout - UILabel inside UITableViewCell is not wrapping on initial load for iOS 6

I am seeing a weird issue with UILabel wrapping in my custom UITableViewCell. I am using auto-layout and while this works fine on iOS > 6, the UILabel inside UITableViewCell is not wrapping on first load for iOS 6. When I tap on the cell and show details view and then come back, then UILabel wraps up as expected.
Here is how I am adding label to the cell:
self.productNameLabel = [[UILabel alloc] init];
self.productNameLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.productNameLabel.backgroundColor = [UIColor clearColor];
self.productNameLabel.textAlignment = NSTextAlignmentLeft;
self.productNameLabel.font = kFontDynamicSubHead;
self.productNameLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.productNameLabel.numberOfLines = 2;
[self.contentView addSubview:self.productNameLabel];
Then I add some constraints to position the label. I do not set any Height/Width constraint.
Finally, in my cell' layoutSubviews I set the preferredMaxLayoutWidth for my label.
- (void)layoutSubviews {
[super layoutSubviews];
self.productNameLabel.preferredMaxLayoutWidth = self.productNameLabel.bounds.size.width;
}
If anyone faced this before or know the workaround then kindly suggest.
Try adding constraints for your UILabel in your code, try adding this implementation for your cell initWithCoder
-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if(self)
{
self.productNameLabel = [[UILabel alloc] init];
self.productNameLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.productNameLabel.backgroundColor = [UIColor clearColor];
self.productNameLabel.textAlignment = NSTextAlignmentLeft;
self.productNameLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.productNameLabel.numberOfLines = 2;
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.contentView addSubview:self.productNameLabel];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[view]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:#{#"view":self.productNameLabel}]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[view]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:#{#"view":self.productNameLabel}]];
[self setNeedsLayout];
}
return self;
}
Hope this helps

How to fill iOS Stack View (UIStackView) with an image as a background?

I want to set stack view background to an image. For example, in Android, if I use 'Linear Layout' (equivalent to UIStackView), I can set a background image to the 'Linear Layout' irrespective of whatever the content (views) I add to it.
How could I do this using XCode?
You can't do this, UIStackView is a non-drawing view, meaning that drawRect() is never called. If you want a background image, consider placing the stack view inside a UIImageView.
As the answers stated above you can't add an image background to a stackview.
The way to achieve it is by adding a UIView and then nest an ImageView and the existing stackview to the UIView just added. Also, you need to apply the corresponding constraints to the imageview in order to fit the full background (0, 0 , 0 , 0)
The outline should look like this:
If you program UIStackview in code maybe you can try below ~
UIScrollView *scrollview = [[UIScrollView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[[scrollview layer] setContents:(__bridge id)[[UIImage imageNamed:#"your_background.jpg"] CGImage]];
[[scrollview layer] setContentsGravity:kCAGravityResizeAspectFill];
[self.view addSubview:scrollview];
UIStackView *stackview = [[UIStackView alloc] init];
[stackview setTranslatesAutoresizingMaskIntoConstraints:NO];
[stackview setAxis:UILayoutConstraintAxisVertical];
[stackview setDistribution:UIStackViewDistributionFill];
[scrollview addSubview:stackview];
NSDictionary *dic_vfl = NSDictionaryOfVariableBindings(stackview,scrollview);
NSArray *array = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[stackview(scrollview)]|" options:0 metrics:nil views:dic_vfl];
[scrollview addConstraints:array];
array = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[stackview]|" options:0 metrics:nil views:dic_vfl];
[scrollview addConstraints:array];
//add view to stackview for test
srand48(arc4random());
for(int i=0;i<50;i++){
UIColor *rc = [UIColor colorWithRed:drand48() green:drand48() blue:drand48() alpha:0.5];
UIView *view_test = [[UIView alloc]init];
[view_test setTranslatesAutoresizingMaskIntoConstraints:NO];
[view_test setBackgroundColor:rc];
[stackview addArrangedSubview:view_test];
dic_vfl = NSDictionaryOfVariableBindings(view_test);
array = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view_test]|" options:0 metrics:nil views:dic_vfl];
[stackview addConstraints:array];
array = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[view_test(50)]" options:0 metrics:nil views:dic_vfl];
[stackview addConstraints:array];
}
hope this is useful!!

How do I make my superview resize to match the size of its subviews with Autolayout?

I've got a UIView ("superview") that has a couple of UILabels as subviews, set up in Interface Builder. I've got Auto-Layout turned on to properly space all the labels in a list, one after another. This works well.
What I'm trying to do now is make it so that my Superview resizes vertically to match the height of all my labels and I can't quite figure out how to do this.
How does this work?
Here's an example using visual layouts... so ignore the translatesAutoresizingMaskIntoConstraints business.
This relies on the fact that the container view does NOT have any height constraints and it seems to rely on the spacing between views. The system will resize the view to match the requirements of the subviews.
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor redColor];
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
_labelOne = [[UILabel alloc] init];
_labelOne.text = #"Banana";
_labelOne.backgroundColor = [UIColor blueColor];
[_labelOne setTranslatesAutoresizingMaskIntoConstraints:NO];
_labelTwo = [[UILabel alloc] init];
_labelTwo.text = #"Dinosaur";
_labelTwo.backgroundColor = [UIColor yellowColor];
[_labelTwo setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:_labelOne];
[self addSubview:_labelTwo];
NSDictionary *views = NSDictionaryOfVariableBindings(_labelOne, _labelTwo);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_labelOne]|" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_labelTwo]|" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-10-[_labelOne(30)]-15-[_labelTwo(30)]-10-|" options:0 metrics:nil views:views]];
}
return self;
}
I know the basic idea is to have the intrinsic content size of your subviews drive the height of superview by making sure the content compression resistance and content hugging constraints in the vertical dimension for each subview are not being overridden by higher-priority constraints you have added.
Try use the UIView method sizeToFit?

UIScrollView with iOS Auto Layout Constraints: Wrong size for subviews

I'm trying to generate a view in code. Here's the hierachy of my view object
UIScrollView
UIView
UIButton
The ScrollView should be the same size as the window.
The button should be as big as possible.
I'm using iOS auto layout, so the constraint strings for all of my objects look like this
H:|[object]|
V:|[object]|
I've also set translatesAutoresizingMaskIntoConstraints to NO for each object.
The problem is that the button only gets the default button-size. Its parent view object (UIView) only gets the size its subviews need.
red: UIScrollView / yellow: UIView
How can I force those views to be as big as the scrollView?
When I use a UIView instead of th UIScrollView everything works great...
Here's some code:
- (void) viewDidLoad {
[super viewDidLoad];
// SCROLL VIEW
UIScrollView* scrollView = [UIScrollView new];
scrollView.backgroundColor=[UIColor redColor];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
//CONTAINER VIEW
UIView *containerView = [UIView new];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
containerView.backgroundColor = [UIColor yellowColor];
[scrollView addSubview:containerView];
// CONSTRAINTS SCROLL VIEW - CONTAINER VIEW
[scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[containerView]|"
options:0 metrics:nil
views:#{#"containerView":containerView}]];
[scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[containerView]|"
options:0 metrics:nil
views:#{#"containerView":containerView}]];
// BUTTON
UIButton* button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.translatesAutoresizingMaskIntoConstraints = NO;
[button setTitle:#"I'm way to small" forState:UIControlStateNormal];
[containerView addSubview:button];
// CONSTRAINTS CONTAINER VIEW - BUTTON
[containerView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[button]|"
options:0 metrics:nil
views:#{#"button":button}]];
[containerView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[button]|"
options:0 metrics:nil
views:#{#"button":button}]];
self.view = scrollView;
}
UPDATE:
I really don't know, why this is happening. If you set up the view in IB, connect the outlets and instanciate the view in code, the scrollview behaves like a normal view (which bounces vertically). Its contentSize is not calculated correctly. More here. But how to do it correctly?
A couple of observations:
Constraints for subviews in scroll views don't work like constraints in other views. They're used to set the contentSize of the scroll view. (See TN2154.) That way, you throw a bunch of stuff on a scroll view, set the constraints for the stuff inside it, and the contentSize is calculated for you. It's very cool feature, but it's antithetical to what you're trying to do here.
Worse, buttons will, unless you set an explicit constraint for their width and height of a button, will resize according to their content.
The net effect of these two observations is that your existing constraints say "(a) set my container to be the size of my button; (b) let my button resize itself dynamically to the size of the text; and (c) set my scrollview's contentSize according to the size of my container (which is the size of the button)."
I'm unclear as to what the business problem is. But here are some constraints that achieve what I think your technical question was:
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *view = self.view;
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.backgroundColor = [UIColor redColor]; // just so I can see it
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:scrollView];
UIView *containerView = [[UIView alloc] init];
containerView.backgroundColor = [UIColor yellowColor]; // just so I can see it
containerView.translatesAutoresizingMaskIntoConstraints = NO;
[scrollView addSubview:containerView];
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.translatesAutoresizingMaskIntoConstraints = NO;
[button setTitle:#"I'm the right size" forState:UIControlStateNormal];
[containerView addSubview:button];
NSDictionary *views = NSDictionaryOfVariableBindings(scrollView, button, view, containerView);
// set the scrollview to be the size of the root view
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|"
options:0
metrics:nil
views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|"
options:0
metrics:nil
views:views]];
// set the container to the size of the main view, and simultaneously
// set the scrollview's contentSize to match the size of the container
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[containerView(==view)]|"
options:0
metrics:nil
views:views]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[containerView(==view)]|"
options:0
metrics:nil
views:views]];
// set the button size to be the size of the container view
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[button(==containerView)]"
options:0
metrics:nil
views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[button(==containerView)]"
options:0
metrics:nil
views:views]];
}
Frankly, I don't understand the business intent of your UI, as this feels like a contortion of auto layout to achieve a very simply UI. I don't know why you have a scroll view if you have "screen sized" content in it (unless you were paging through buttons). I don't know why you'd have a content view with a single item in it. I don't understand why you're using a full-screen button (I'd just put a tap gesture on the root view at that point and call it a day).
I'll assume you have good reasons for all of this, but it might make sense to back up, ask what is your desired user experience is, and then approach the problem fresh to see if there's a more efficient way to achieve the desired effect.

Resources