我正在尝试使用如下所示的约束构建titleView:
我知道如何用帧来做这件事。我会计算文本的宽度,图像的宽度,创建一个宽度/高度的视图来包含两者,然后将两者作为子视图添加到带有框架的适当位置。
我试图理解如何通过约束来做到这一点。我的想法是内在的内容大小会帮助我在这里,但我正在疯狂地试图让这个工作。
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的大小。它应该能够通过其内容的大小来确定,但是......
translatesAutoresizingMaskIntoConstraints = NO
应用程序崩溃时出现此错误:'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;
答案 0 :(得分:59)
我真的需要限制,所以今天就玩了。我发现有效的是:
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
像魅力一样!
答案 1 :(得分:18)
an0的回答是正确的。但是,它并没有帮助您获得理想的效果。
这是我建立标题视图的方法,它自动具有正确的大小:
UIView
子类,例如CustomTitleView
,稍后将用作navigationItem
' titleView
。CustomTitleView
内使用自动布局。如果您希望CustomTitleView
始终居中,则需要添加明确的CenterX约束(请参阅下面的代码和链接)。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, 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;
}
示例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
示例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
答案 2 :(得分:8)
谢谢@Valentin Shergin和@tubtub!根据他们的回答,我在Swift 1.2中使用下拉箭头图像实现了导航栏标题:
UIView
titleView
子类
translatesAutoresizingMaskIntoConstraints
到false
,为true
设置titleView
。 b)实施sizeThatFits(size: CGSize)
titleLabel.sizeToFit()
和self.setNeedsUpdateConstraints()
内的titleView
子类,则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()
}
答案 3 :(得分:7)
您必须设置titleView
的框架,因为您没有在其超级视图中为其position
指定任何约束。自动布局系统只能根据您指定的约束和其子视图的size
为您找出titleView
intrinsic content size
。
答案 4 :(得分:5)
要将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))
);
}
答案 5 :(得分:0)
这是我对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