使用约束编程构buildtitleView(或者通常构造带约束的视图)
我试图build立一个约束看起来像这样的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的大小。 它应该能够通过其内容的大小来确定,但是…
- titleView是确定它的大小是0,除非我硬编码一个框架。
- 如果我设置
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
找出titleView
的intrinsic 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
,稍后将用作navigationItem
的titleView
。 - 在
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中使用了下拉箭头图像来实现导航栏标题:
- 为自定义
titleView
创build一个UIView
子类 - 在你的子类中:a)为子视图使用自动布局,但不为自己使用。 为子视图设置
translatesAutoresizingMaskIntoConstraints
为false
,为titleView
设置true
。 b)实现sizeThatFits(size: CGSize)
- 如果您的标题可以在文本更改后更改
titleView
的子类中的titleLabel.sizeToFit()
和self.setNeedsUpdateConstraints()
- 在您的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)) ); }