ios 以编程方式构建带有约束的 titleView(或通常构建带有约束的视图)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15285698/
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
Building a titleView programmatically with constraints (or generally constructing a view with constraints)
提问by Bob Spryn
I'm trying to build a titleView with constraints that looks like this:
我正在尝试使用如下所示的约束构建 titleView:
I know how I would do this with frames. I would calculate the width of the text, the width of the image, create a view with that width/height to contain both, then add both as subviews at the proper locations with frames.
我知道我将如何使用框架来做到这一点。我会计算文本的宽度,图像的宽度,创建一个具有该宽度/高度的视图以包含两者,然后将两者添加为带有框架的适当位置的子视图。
I'm trying to understand how one might do this with constraints. My thought was that intrinsic content size would help me out here, but I'm flailing around wildly trying to get this to work.
我试图了解如何在约束条件下做到这一点。我的想法是内在的内容大小会帮助我在这里,但我正在疯狂地试图让它发挥作用。
UILabel *categoryNameLabel = [[UILabel alloc] init];
categoryNameLabel.text = categoryName; // a variable from elsewhere that has a category like "Popular"
categoryNameLabel.translatesAutoresizingMaskIntoConstraints = NO;
[categoryNameLabel sizeToFit]; // hoping to set it to the instrinsic size of the text?
UIView *titleView = [[UIView alloc] init]; // no frame here right?
[titleView addSubview:categoryNameLabel];
NSArray *constraints;
if (categoryImage) {
UIImageView *categoryImageView = [[UIImageView alloc] initWithImage:categoryImage];
[titleView addSubview:categoryImageView];
categoryImageView.translatesAutoresizingMaskIntoConstraints = NO;
constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|[categoryImageView]-[categoryNameLabel]|" options:NSLayoutFormatAlignAllTop metrics:nil views:NSDictionaryOfVariableBindings(categoryImageView, categoryNameLabel)];
} else {
constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|[categoryNameLabel]|" options:NSLayoutFormatAlignAllTop metrics:nil views:NSDictionaryOfVariableBindings(categoryNameLabel)];
}
[titleView addConstraints:constraints];
// here I set the titleView to the navigationItem.titleView
I shouldn't have to hardcode the size of the titleView. It should be able to be determined via the size of its contents, but...
我不应该对 titleView 的大小进行硬编码。它应该能够通过其内容的大小来确定,但是......
- The titleView is determining it's size is 0 unless I hardcode a frame.
- If I set
translatesAutoresizingMaskIntoConstraints = NO
the app crashes with this error:'Auto Layout still required after executing -layoutSubviews. UINavigationBar's implementation of -layoutSubviews needs to call super.'
- 除非我对框架进行硬编码,否则 titleView 正在确定它的大小为 0。
- 如果我设置
translatesAutoresizingMaskIntoConstraints = NO
应用程序崩溃并出现此错误:'Auto Layout still required after executing -layoutSubviews. UINavigationBar's implementation of -layoutSubviews needs to call super.'
Update
更新
I got it to work with this code, but I'm still having to set the frame on the titleView:
我让它可以使用此代码,但我仍然必须在 titleView 上设置框架:
UILabel *categoryNameLabel = [[UILabel alloc] init];
categoryNameLabel.translatesAutoresizingMaskIntoConstraints = NO;
categoryNameLabel.text = categoryName;
categoryNameLabel.opaque = NO;
categoryNameLabel.backgroundColor = [UIColor clearColor];
UIView *titleView = [[UIView alloc] init];
[titleView addSubview:categoryNameLabel];
NSArray *constraints;
if (categoryImage) {
UIImageView *categoryImageView = [[UIImageView alloc] initWithImage:categoryImage];
[titleView addSubview:categoryImageView];
categoryImageView.translatesAutoresizingMaskIntoConstraints = NO;
constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|[categoryImageView]-7-[categoryNameLabel]|" options:NSLayoutFormatAlignAllCenterY metrics:nil views:NSDictionaryOfVariableBindings(categoryImageView, categoryNameLabel)];
[titleView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[categoryImageView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(categoryImageView)];
[titleView addConstraints:constraints];
titleView.frame = CGRectMake(0, 0, categoryImageView.frame.size.width + 7 + categoryNameLabel.intrinsicContentSize.width, categoryImageView.frame.size.height);
} else {
constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|[categoryNameLabel]|" options:NSLayoutFormatAlignAllTop metrics:nil views:NSDictionaryOfVariableBindings(categoryNameLabel)];
[titleView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[categoryNameLabel]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(categoryNameLabel)];
[titleView addConstraints:constraints];
titleView.frame = CGRectMake(0, 0, categoryNameLabel.intrinsicContentSize.width, categoryNameLabel.intrinsicContentSize.height);
}
return titleView;
采纳答案by an0
You have to set the frame of titleView
because you don't specify any constraints for its position
in its superview. The Auto Layout system can only figure out the size
of titleView
for you from the constraints you specified and the intrinsic content size
of its subviews.
您必须设置框架,titleView
因为您没有position
在其超级视图中为其指定任何约束。自动布局系统只能从您指定的约束及其子视图size
中titleView
为您计算出intrinsic content size
。
回答by David H
I really needed constraints, so played around with it today. What I found that works is this:
我真的需要约束,所以今天玩了一下。我发现有效的是:
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
// add your views and set up all the constraints
// This is the magic sauce!
v.layoutIfNeeded()
v.sizeToFit()
// Now the frame is set (you can print it out)
v.translatesAutoresizingMaskIntoConstraints = true // make nav bar happy
navigationItem.titleView = v
Works like a charm!
奇迹般有效!
回答by tubtub
an0's answer is correct. However, it doesn't help you getting the desired effect.
an0的答案是正确的。但是,它并不能帮助您获得所需的效果。
Here's my recipe for building title views that automatically have the right size:
这是我构建自动具有正确大小的标题视图的秘诀:
- Create a
UIView
subclass, for instanceCustomTitleView
that will be later used as thenavigationItem
'stitleView
. - Use auto layout inside
CustomTitleView
. If you want to have yourCustomTitleView
being always centered, you'll need to add an explicit CenterX constraint (see code and link below). - Call
updateCustomTitleView
(see below) every time your titleView content updates. We need to set the titleView to nil and set it afterwards to our view again to prevent the title view being offset centered. This would happen when the title view changes from wide to narrow. - DON'T disable
translatesAutoresizingMaskIntoConstraints
- 创建一个
UIView
子类,例如CustomTitleView
,稍后将用作navigationItem
'stitleView
。 - 在里面使用自动布局
CustomTitleView
。如果您想让自己CustomTitleView
始终居中,则需要添加一个显式的 CenterX 约束(请参阅下面的代码和链接)。 updateCustomTitleView
每次 titleView 内容更新时调用(见下文)。我们需要将 titleView 设置为 nil,然后再次将其设置为我们的视图,以防止标题视图偏移居中。当标题视图从宽变窄时会发生这种情况。- 不要禁用
translatesAutoresizingMaskIntoConstraints
Gist: https://gist.github.com/bhr/78758bd0bd4549f1cd1c
要点:https: //gist.github.com/bhr/78758bd0bd4549f1cd1c
Updating CustomTitleView
from your ViewController:
CustomTitleView
从您的 ViewController更新:
- (void)updateCustomTitleView
{
//we need to set the title view to nil and get always the right frame
self.navigationItem.titleView = nil;
//update properties of your custom title view, e.g. titleLabel
self.navTitleView.titleLabel.text = <#my_property#>;
CGSize size = [self.navTitleView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
self.navTitleView.frame = CGRectMake(0.f, 0.f, size.width, size.height);
self.navigationItem.titleView = self.customTitleView;
}
Sample CustomTitleView.h
with one label and two buttons
CustomTitleView.h
带有一个标签和两个按钮的示例
#import <UIKit/UIKit.h>
@interface BHRCustomTitleView : UIView
@property (nonatomic, strong, readonly) UILabel *titleLabel;
@property (nonatomic, strong, readonly) UIButton *previousButton;
@property (nonatomic, strong, readonly) UIButton *nextButton;
@end
Sample CustomTitleView.m
:
样品CustomTitleView.m
:
#import "BHRCustomTitleView.h"
@interface BHRCustomTitleView ()
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *previousButton;
@property (nonatomic, strong) UIButton *nextButton;
@property (nonatomic, copy) NSArray *constraints;
@end
@implementation BHRCustomTitleView
- (void)updateConstraints
{
if (self.constraints) {
[self removeConstraints:self.constraints];
}
NSDictionary *viewsDict = @{ @"title": self.titleLabel,
@"previous": self.previousButton,
@"next": self.nextButton };
NSMutableArray *constraints = [NSMutableArray array];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(>=0)-[previous]-2-[title]-2-[next]-(>=0)-|"
options:NSLayoutFormatAlignAllBaseline
metrics:nil
views:viewsDict]];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[previous]|"
options:0
metrics:nil
views:viewsDict]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.titleLabel
attribute:NSLayoutAttributeCenterX
multiplier:1.f
constant:0.f]];
self.constraints = constraints;
[self addConstraints:self.constraints];
[super updateConstraints];
}
- (UILabel *)titleLabel
{
if (!_titleLabel)
{
_titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
_titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
_titleLabel.font = [UIFont boldSystemFontOfSize:_titleLabel.font.pointSize];
[self addSubview:_titleLabel];
}
return _titleLabel;
}
- (UIButton *)previousButton
{
if (!_previousButton)
{
_previousButton = [UIButton buttonWithType:UIButtonTypeSystem];
_previousButton.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_previousButton];
_previousButton.titleLabel.font = [UIFont systemFontOfSize:23.f];
[_previousButton setTitle:@"?"
forState:UIControlStateNormal];
}
return _previousButton;
}
- (UIButton *)nextButton
{
if (!_nextButton)
{
_nextButton = [UIButton buttonWithType:UIButtonTypeSystem];
_nextButton.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_nextButton];
_nextButton.titleLabel.font = [UIFont systemFontOfSize:23.f];
[_nextButton setTitle:@"?"
forState:UIControlStateNormal];
}
return _nextButton;
}
+ (BOOL)requiresConstraintBasedLayout
{
return YES;
}
@end
回答by Lion
Thanks @Valentin Shergin and @tubtub! According to their answers I made an implementation of navigation bar title with dropdown arrow image in Swift 1.2:
感谢@Valentin Shergin 和@tubtub!根据他们的回答,我在 Swift 1.2 中实现了带有下拉箭头图像的导航栏标题:
- Create a
UIView
subclass for customtitleView
- In your subclass: a) Use auto layout for subviews but not for itself. Set
translatesAutoresizingMaskIntoConstraints
tofalse
for subviews andtrue
fortitleView
itself. b) ImplementsizeThatFits(size: CGSize)
- If your title can change call
titleLabel.sizeToFit()
andself.setNeedsUpdateConstraints()
insidetitleView
's subclass after text changes - In your ViewController call custom
updateTitleView()
and make sure to calltitleView.sizeToFit()
andnavigationBar.setNeedsLayout()
in there
UIView
为自定义创建子类titleView
- 在您的子类中:a) 为子视图使用自动布局,但不为它自己使用。设置
translatesAutoresizingMaskIntoConstraints
到false
为子视图,并true
为titleView
自身。b) 实施sizeThatFits(size: CGSize)
- 如果您的标题可以在文本更改后更改 call
titleLabel.sizeToFit()
和self.setNeedsUpdateConstraints()
insidetitleView
的子类 - 在您的视图控制器调用自定义
updateTitleView()
,并确保电话titleView.sizeToFit()
和navigationBar.setNeedsLayout()
在那里
Here's minimal implementation of DropdownTitleView
:
这是 的最小实现DropdownTitleView
:
import UIKit
class DropdownTitleView: UIView {
private var titleLabel: UILabel
private var arrowImageView: UIImageView
// MARK: - Life cycle
override init (frame: CGRect) {
self.titleLabel = UILabel(frame: CGRectZero)
self.titleLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
self.arrowImageView = UIImageView(image: UIImage(named: "dropdown-arrow")!)
self.arrowImageView.setTranslatesAutoresizingMaskIntoConstraints(false)
super.init(frame: frame)
self.setTranslatesAutoresizingMaskIntoConstraints(true)
self.addSubviews()
}
convenience init () {
self.init(frame: CGRectZero)
}
required init(coder aDecoder: NSCoder) {
fatalError("DropdownTitleView does not support NSCoding")
}
private func addSubviews() {
addSubview(titleLabel)
addSubview(arrowImageView)
}
// MARK: - Methods
func setTitle(title: String) {
titleLabel.text = title
titleLabel.sizeToFit()
setNeedsUpdateConstraints()
}
// MARK: - Layout
override func updateConstraints() {
removeConstraints(self.constraints())
let viewsDictionary = ["titleLabel": titleLabel, "arrowImageView": arrowImageView]
var constraints: [AnyObject] = []
constraints.extend(NSLayoutConstraint.constraintsWithVisualFormat("H:|[titleLabel]-8-[arrowImageView]|", options: .AlignAllBaseline, metrics: nil, views: viewsDictionary))
constraints.extend(NSLayoutConstraint.constraintsWithVisualFormat("V:|[titleLabel]|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary))
self.addConstraints(constraints)
super.updateConstraints()
}
override func sizeThatFits(size: CGSize) -> CGSize {
// +8.0 - distance between image and text
let width = CGRectGetWidth(arrowImageView.bounds) + CGRectGetWidth(titleLabel.bounds) + 8.0
let height = max(CGRectGetHeight(arrowImageView.bounds), CGRectGetHeight(titleLabel.bounds))
return CGSizeMake(width, height)
}
}
and ViewController:
和视图控制器:
override func viewDidLoad() {
super.viewDidLoad()
// Set custom title view to show arrow image along with title
self.navigationItem.titleView = dropdownTitleView
// your code ...
}
private func updateTitleView(title: String) {
// update text
dropdownTitleView.setTitle(title)
// layout title view
dropdownTitleView.sizeToFit()
self.navigationController?.navigationBar.setNeedsLayout()
}
回答by Valentin Shergin
For combining auto-layout constraints inside titleView
and hardcoded layout logic inside UINavigationBar
you have to implement method sizeThatFits:
inside your own custom titleView
's class (subclass of UIView
) like this:
为了结合内部的自动布局约束titleView
和内部的硬编码布局逻辑,UINavigationBar
您必须sizeThatFits:
在自己的自定义titleView
类( 的子类UIView
)中实现方法,如下所示:
- (CGSize)sizeThatFits:(CGSize)size
{
return CGSizeMake(
CGRectGetWidth(self.imageView.bounds) + CGRectGetWidth(self.labelView.bounds) + 5.f /* space between icon and text */,
MAX(CGRectGetHeight(self.imageView.bounds), CGRectGetHeight(self.labelView.bounds))
);
}
回答by Omkar
Here is my implementation of ImageAndTextView
这是我对 ImageAndTextView 的实现
@interface ImageAndTextView()
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UITextField *textField;
@end
@implementation ImageAndTextView
- (instancetype)init
{
self = [super init];
if (self)
{
[self initializeView];
}
return self;
}
- (void)initializeView
{
self.translatesAutoresizingMaskIntoConstraints = YES;
self.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
self.imageView = [[UIImageView alloc] init];
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
self.textField = [[UITextField alloc] init];
[self addSubview:self.imageView];
[self addSubview:self.textField];
self.imageView.translatesAutoresizingMaskIntoConstraints = NO;
self.textField.translatesAutoresizingMaskIntoConstraints = NO;
//Center the text field
[NSLayoutConstraint activateConstraints:@[
[self.textField.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
[self.textField.centerYAnchor constraintEqualToAnchor:self.centerYAnchor]
]];
//Put image view on left of text field
[NSLayoutConstraint activateConstraints:@[
[self.imageView.rightAnchor constraintEqualToAnchor:self.textField.leftAnchor],
[self.imageView.lastBaselineAnchor constraintEqualToAnchor:self.textField.lastBaselineAnchor],
[self.imageView.heightAnchor constraintEqualToConstant:16]
]];
}
- (CGSize)intrinsicContentSize
{
return CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX);
}
@end