ios 使用自动布局设置动态 UIView 以匹配容器视图
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16157665/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Use autolayout to set dynamic UIView to match container view
提问by Feta
I have a UIView in IB that has another UIView within it, which I am using as a container view. In code, I create three different views and then animate the appropriate one into the container's view depending on the state of the app. Only one of the three different views will be valid at any given time. The problem is, when I run on different iPhone's in the simulator, my new subviews are not scaled to match the container view. I am using autolayout. For testing purposes, I've set up my subviews to just be a big button that has all it's edges constrained to the superview. And the container view also has it's edges constrained to it's superview. What I want is for the subview to match the container's view. i.e the button stretches the entire size of the container view. When run on different iPhones the size of the container view and therefore the subview should scale proportionally relative to the different iPhone's screen sizes.
我在 IB 中有一个 UIView,其中有另一个 UIView,我将其用作容器视图。在代码中,我创建了三个不同的视图,然后根据应用程序的状态将适当的视图动画化到容器的视图中。在任何给定时间,三种不同的视图中只有一种是有效的。问题是,当我在模拟器中的不同 iPhone 上运行时,我的新子视图没有缩放以匹配容器视图。我正在使用自动布局。出于测试目的,我将我的子视图设置为一个大按钮,它的所有边缘都限制在超级视图上。并且容器视图的边缘也被限制在它的超级视图上。我想要的是子视图与容器的视图相匹配。即按钮拉伸容器视图的整个大小。
Below is the code I use to init my subview and set up it's constraints relative to the container view.
下面是我用来初始化我的子视图并设置它相对于容器视图的约束的代码。
UIView *containerView = self.subView;
UIView *newSubview = [[mySubview alloc] init];
[newSubview setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.containerView addSubview:newSubview];
[self.containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.containerView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
[self.containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.containerView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0]];
[self.containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.containerView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0]];
[self.containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.containerView attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]];
I just can't seem to get this to work. I'm fairly new to autolayout and am not sure what I am doing wrong and would like to stop banging my head against this wall. Any help would be terrific. :)
我似乎无法让它发挥作用。我对自动布局还很陌生,不确定我做错了什么,并希望停止将头撞在这堵墙上。任何帮助都会很棒。:)
************* ADDITIONAL INFO **************
************* 附加信息 **************
Sorry, I've not stated my problem as clearly as I could have. So here is some more info with screen shots. First, I'll describe what I've done code-wise.
抱歉,我没有尽可能清楚地说明我的问题。所以这里有更多关于屏幕截图的信息。首先,我将描述我在代码方面所做的工作。
In didFinishLaunchingWithOptions in AppDelegate.m, I create MyViewController like this,
在 AppDelegate.m 中的 didFinishLaunchingWithOptions 中,我像这样创建了 MyViewController,
self.myViewController = [[MyViewController alloc] initWithNibName:@"MyViewController" bundle:nil];
In viewDidLoad in MyViewController.m, I create mySubview and add it to my containerView and create constraints for it like this,
在 MyViewController.m 的 viewDidLoad 中,我创建了 mySubview 并将其添加到我的 containerView 并像这样为它创建约束,
UIView *containerView = self.containerView;
UIView *mySubview = [[MySubview alloc] init];
[mySubview setTranslatesAutoresizingMaskIntoConstraints:NO];
[containerView addSubview:mySubview];
NSDictionary *views = NSDictionaryOfVariableBindings(mySubview);
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[mySubview]|"
options:0
metrics:nil
views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[mySubview]|"
options:0
metrics:nil
views:views]];
And finally in init in MySubview.h I add the nib as a subview like this,
最后在 MySubview.h 中的 init 中,我将笔尖添加为这样的子视图,
- (id)init
{
if(self = [super init])
{
NSArray *nibArray = [[NSBundle mainBundle]loadNibNamed:@"MySubview" owner:self options:nil];
[self addSubview:[nibArray objectAtIndex:0]];
}
return self;
}
A couple of things to note that may help,
需要注意的几件事可能会有所帮助,
In MyViewController.xib, I have a UIView that I am using as the containerView. It has an IBOutlet to UIView* containerView and is the one referenced above. The only constraints I have for the containerView in IB are, leading, trailing and bottom space to Superview and top space to Superview = 44.
在 MyViewController.xib 中,我有一个用作 containerView 的 UIView。它有一个到 UIView* containerView 的 IBOutlet,就是上面提到的那个。我对 IB 中的 containerView 的唯一限制是,Superview 的前导、尾随和底部空间以及 Superview 的顶部空间 = 44。
For MySubview.xib, the height and width are 300, (no constraints are used for height or width). I feel like these sizes should not matter since mySubview is supposed to get constrained to containerView.
对于 MySubview.xib,高度和宽度为 300,(高度或宽度不使用任何约束)。我觉得这些大小应该无关紧要,因为 mySubview 应该被限制为 containerView。
In MySubview.xib I have 3 objects, topButton: height = 29, middleView: height = 242 and bottomButton: height = 29. (see attached image) TopButton has leading, trailing and top to Superview constraints and a bottom to middleView constraint. MiddleView has leading and trailing to Superview constraints and a top to topButton constraint and a bottom to bottomButton constraints. And finally bottomButton has leading, trailing and bottom to Superview constraints and a top to middleView constraint.
在 MySubview.xib 中,我有 3 个对象,topButton: height = 29、middleView: height = 242 和 bottomButton: height = 29。(见附图)TopButton 具有前导、尾随和顶部到 Superview 的约束以及底部到 middleView 的约束。MiddleView 具有前导和尾随到 Superview 的约束以及一个从顶部到顶部按钮的约束以及一个从底部到底部按钮的约束。最后,bottomButton 具有前导、尾随和底部到 Superview 的约束以及一个从顶部到 middleView 的约束。
What I want to happen is for mySubview to scale to fit the containerView since constraints have been create and added but instead, mySubview gets very large and clips containerView.
我想要发生的是 mySubview 缩放以适应 containerView,因为已经创建并添加了约束,但是 mySubview 变得非常大并剪辑了 containerView。
Here are some screen shots:
以下是一些屏幕截图:
MyViewController.xib, the blue rectangle below the title is my container view and has the IBOutlet containerView.
MyViewController.xib,标题下方的蓝色矩形是我的容器视图,并具有 IBOutlet 容器视图。
MySubview.xib
我的子视图.xib
And finally, the result, which are incorrect.
最后,结果,这是不正确的。
Instead I would want this, which I have faked just to get the screen shot.
相反,我想要这个,我只是为了得到屏幕截图而伪造的。
On iPhone 4,
在 iPhone 4 上,
On iPhone 5,
在 iPhone 5 上,
As you can see in the fake screen shots, mySubview scales to fit the containerView, even as containerView scales a bit to adjust for the different phone screen sizes.
正如你在假屏幕截图中看到的那样,mySubview 会缩放以适应 containerView,即使 containerView 会缩放以适应不同的手机屏幕尺寸。
Hope I did not go overboard with the info. Any help would be terrific. I feel like I am close but must be missing one key step. Grrrrr.
希望我没有对信息过分。任何帮助都会很棒。我觉得我很接近,但必须错过一个关键步骤。咕噜噜。
回答by Rob
A couple of thoughts:
一些想法:
This basically looks fine, other than some variable name weirdness: Your local variable,
containerView
is equal toself.subView
, but all of your other lines refer to a different variable, a class property,self.containerView
. Did you omit a line where you set thatcontainerView
class property? But when I fixed that, your code worked fine for me.Make sure you're not trying to look at the
frame
immediately after setting the constraints, as the change will not yet be reflected in theframe
settings. You can do a[containerView layoutIfNeeded];
if you want to force it to relayout everything based upon the constraints. Also, if you want to confirmframe
settings, it's better to defer looking at those values until afterviewDidAppear
(i.e.viewDidLoad
is too early in the view construction process).A minor tweak on your code (and unrelated to your problem), but when I'm setting the constraints within a container view, I often will set not only
NSLayoutAttributeTop
andNSLayoutAttributeLeading
, like you did, but alsoNSLayoutAttributeBottom
andNSLayoutAttributeTrailing
(rather thanNSLayoutAttributeWidth
andNSLayoutAttributeHeight
). It accomplishes the same thing as your code, but when using non-zero values, it is a fraction more intuitive.Anyway, I just did the following code, permutation of yours, and it works fine:
- (IBAction)didTouchUpInsideAddView:(id)sender { UIView *containerView = self.containerView; UIView *newSubview = [[UIView alloc] initWithFrame:CGRectZero]; // initializing with CGRectZero so we can see it change newSubview.translatesAutoresizingMaskIntoConstraints = NO; newSubview.backgroundColor = [UIColor lightGrayColor]; [containerView addSubview:newSubview]; [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0]]; [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0.0]]; [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0]]; [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0.0]]; // the frame is still `CGRectZero` at this point NSLog(@"newSubview.frame before = %@", NSStringFromCGRect(newSubview.frame)); // if not doing this in `viewDidLoad` (e.g. you're doing this in // `viewDidAppear` or later), you can force `layoutIfNeeded` if you want // to look at `frame` values. Generally you don't need to do this unless // manually inspecting `frame` values or when changing constraints in a // `animations` block of `animateWithDuration`. [containerView layoutIfNeeded]; // everything is ok here NSLog(@"containerView.bounds after = %@", NSStringFromCGRect(containerView.bounds)); NSLog(@"newSubview.frame after = %@", NSStringFromCGRect(newSubview.frame)); }
You can simplify that code a little using visual format language, e.g.:
NSDictionary *views = NSDictionaryOfVariableBindings(newSubview); [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[newSubview]|" options:0 metrics:nil views:views]]; [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[newSubview]|" options:0 metrics:nil views:views]];
I find it easier to get the constraints right using visual format language. A little less error-prone (for me, at least). There are, though, some constraints that cannot be represented in visual format language, in which case I fall back to the syntax you outline.
In your revised question, you show us an
init
method for your subview, which does anotheraddSubview
. You have to set constraints there, too. Bottom line, wherever you doaddSubview
, you have to set your constraints, e.g.- (id)init { if(self = [super init]) { NSArray *nibArray = [[NSBundle mainBundle]loadNibNamed:@"MySubview" owner:self options:nil]; UIView *subview = [nibArray objectAtIndex:0]; subview.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:subview]; NSDictionary *views = NSDictionaryOfVariableBindings(subview); [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[subview]|" options:0 metrics:nil views:views]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[subview]|" options:0 metrics:nil views:views]]; } return self; }
这基本上看起来不错,除了一些奇怪的变量名称:您的局部变量
containerView
等于self.subView
,但您的所有其他行都引用了不同的变量,类属性,self.containerView
。您是否省略了设置containerView
该类属性的行?但是当我解决这个问题时,你的代码对我来说很好。确保您没有
frame
在设置约束后立即查看,因为更改尚未反映在frame
设置中。[containerView layoutIfNeeded];
如果您想强制它根据约束重新布局所有内容,您可以执行 a 。此外,如果您想确认frame
设置,最好将查看这些值推迟到之后viewDidAppear
(即viewDidLoad
在视图构建过程中为时过早)。对您的代码稍作调整(与您的问题无关),但是当我在容器视图中设置约束时,我经常不仅会像您一样设置
NSLayoutAttributeTop
andNSLayoutAttributeLeading
,还会设置NSLayoutAttributeBottom
andNSLayoutAttributeTrailing
(而不是NSLayoutAttributeWidth
andNSLayoutAttributeHeight
)。它完成与您的代码相同的事情,但是当使用非零值时,它更直观一些。无论如何,我只是做了以下代码,你的排列,它工作正常:
- (IBAction)didTouchUpInsideAddView:(id)sender { UIView *containerView = self.containerView; UIView *newSubview = [[UIView alloc] initWithFrame:CGRectZero]; // initializing with CGRectZero so we can see it change newSubview.translatesAutoresizingMaskIntoConstraints = NO; newSubview.backgroundColor = [UIColor lightGrayColor]; [containerView addSubview:newSubview]; [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0]]; [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0.0]]; [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0]]; [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0.0]]; // the frame is still `CGRectZero` at this point NSLog(@"newSubview.frame before = %@", NSStringFromCGRect(newSubview.frame)); // if not doing this in `viewDidLoad` (e.g. you're doing this in // `viewDidAppear` or later), you can force `layoutIfNeeded` if you want // to look at `frame` values. Generally you don't need to do this unless // manually inspecting `frame` values or when changing constraints in a // `animations` block of `animateWithDuration`. [containerView layoutIfNeeded]; // everything is ok here NSLog(@"containerView.bounds after = %@", NSStringFromCGRect(containerView.bounds)); NSLog(@"newSubview.frame after = %@", NSStringFromCGRect(newSubview.frame)); }
您可以使用视觉格式语言稍微简化该代码,例如:
NSDictionary *views = NSDictionaryOfVariableBindings(newSubview); [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[newSubview]|" options:0 metrics:nil views:views]]; [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[newSubview]|" options:0 metrics:nil views:views]];
我发现使用视觉格式语言更容易获得正确的约束。不太容易出错(至少对我而言)。但是,有一些约束无法用可视化格式语言表示,在这种情况下,我会回到您概述的语法。
在您修改后的问题中,您向我们展示了
init
一种用于子视图的方法,该方法执行另一个addSubview
. 你也必须在那里设置约束。底线,无论你做什么addSubview
,你都必须设置你的约束,例如- (id)init { if(self = [super init]) { NSArray *nibArray = [[NSBundle mainBundle]loadNibNamed:@"MySubview" owner:self options:nil]; UIView *subview = [nibArray objectAtIndex:0]; subview.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:subview]; NSDictionary *views = NSDictionaryOfVariableBindings(subview); [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[subview]|" options:0 metrics:nil views:views]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[subview]|" options:0 metrics:nil views:views]]; } return self; }
回答by James
In the problem you state "my new subviews are not scaled to match the container view" - but from the description I think they are - the problem is that your container view has no fixed size.
在您陈述的问题中,“我的新子视图未按比例缩放以匹配容器视图” - 但从描述中我认为它们是 - 问题是您的容器视图没有固定大小。
I think that if you set your container view to have a fixed width and height that should do it, you may also need to call
我认为,如果您将容器视图设置为具有固定的宽度和高度,那么您可能还需要调用
[self.containerView layoutSubviews]
to force a resize after updating the constraints.
在更新约束后强制调整大小。
I'd also suggest you swap to using the text based formatting, you could swap out your lines above for something like "H:|[newSubview]|" and "V:|[newSubview]|"
我还建议您改用基于文本的格式,您可以将上面的行替换为“H:|[newSubview]|” 和 "V:|[newSubview]|"