使用约束编程构buildtitleView(或者通常构造带约束的视图)

我试图build立一个约束看起来像这样的titleView:

titleview的

我知道我会如何做到这一点的框架。 我会计算文本的宽度,图像的宽度,使用该宽度/高度创build一个包含两个视图的视图,然后将这两个视图添加为带有框架的正确位置的子视图。

我试图了解如何用约束来做到这一点。 我的想法是,内在的内容大小将帮助我在这里,但我疯狂地试图让这个工作。

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 

我不应该硬编码titleView的大小。 它应该能够通过其内容的大小来确定,但是…

  1. titleView是确定它的大小是0,除非我硬编码一个框架。
  2. 如果我设置translatesAutoresizingMaskIntoConstraints = NO应用程序崩溃与此错误: 'Auto Layout still required after executing -layoutSubviews. UINavigationBar's implementation of -layoutSubviews needs to call super.' 'Auto Layout still required after executing -layoutSubviews. UINavigationBar's implementation of -layoutSubviews needs to call super.'

更新

我得到它与这个代码一起工作,但我仍然必须在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; 

您必须设置titleView的框架,因为您没有在其超级视图中为其position指定任何约束。 自动布局系统只能根据您指定的约束条件和子视图的intrinsic content size找出titleViewintrinsic content size

我真的需要约束,所以今天玩了这个。 我发现的作品是这样的:

  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 

奇迹般有效!

an0的答案是正确的。 但是,它并不能帮助您获得理想的效果。

这里是我的build立标题视图的食谱,自动有正确的大小:

  • 创build一个UIView子类,例如CustomTitleView ,稍后将用作navigationItemtitleView
  • CustomTitleView使用自动布局。 如果你想让你的CustomTitleView始终居中,你需要添加一个明确的CenterX约束(见下面的代码和链接)。
  • 每次您的titleView内容更新时调用updateCustomTitleView (见下文)。 我们需要将titleView设置为nil,然后再次将其设置为我们的视图,以防止标题视图偏移居中。 标题视图从宽变窄时会发生这种情况。
  • 不要禁用translatesAutoresizingMaskIntoConstraints

要点: https : //gist.github.com/bhr/78758bd0bd4549f1cd1c

从您的ViewController更新CustomTitleView

 - (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, eg 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; } 

示例CustomTitleView.h带有一个标签和两个button

 #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 

示例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 

谢谢@Valentin Shergin和@tubtub! 根据他们的回答,我在Swift 1.2中使用了下拉箭头图像来实现导航栏标题:

  1. 为自定义titleView创build一个UIView子类
  2. 在你的子类中:a)为子视图使用自动布局,但不为自己使用。 为子视图设置translatesAutoresizingMaskIntoConstraintsfalse ,为titleView设置true 。 b)实现sizeThatFits(size: CGSize)
  3. 如果您的标题可以在文本更改后更改titleView的子类中的titleLabel.sizeToFit()self.setNeedsUpdateConstraints()
  4. 在您的ViewController调用自定义updateTitleView() ,并确保在那里调用titleView.sizeToFit()navigationBar.setNeedsLayout()

这是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) } } 

和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() } 

为了将titleView内的自动布局约束与UINavigationBar硬编码布局逻辑相结合,您必须在您自己的自定义titleView类( UIView子类)中实现方法sizeThatFits: ,如下所示:

 - (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)) ); }