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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 22:36:46  来源:igfitidea点击:

Building a titleView programmatically with constraints (or generally constructing a view with constraints)

iosnslayoutconstraint

提问by Bob Spryn

I'm trying to build a titleView with constraints that looks like this:

我正在尝试使用如下所示的约束构建 titleView:

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 的大小进行硬编码。它应该能够通过其内容的大小来确定,但是......

  1. The titleView is determining it's size is 0 unless I hardcode a frame.
  2. If I set translatesAutoresizingMaskIntoConstraints = NOthe app crashes with this error: 'Auto Layout still required after executing -layoutSubviews. UINavigationBar's implementation of -layoutSubviews needs to call super.'
  1. 除非我对框架进行硬编码,否则 titleView 正在确定它的大小为 0。
  2. 如果我设置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 titleViewbecause you don't specify any constraints for its positionin its superview. The Auto Layout system can only figure out the sizeof titleViewfor you from the constraints you specified and the intrinsic content sizeof its subviews.

您必须设置框架,titleView因为您没有position在其超级视图中为其指定任何约束。自动布局系统只能从您指定的约束及其子视图sizetitleView为您计算出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 UIViewsubclass, for instance CustomTitleViewthat will be later used as the navigationItem's titleView.
  • Use auto layout inside CustomTitleView. If you want to have your CustomTitleViewbeing 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's titleView
  • 在里面使用自动布局CustomTitleView。如果您想让自己CustomTitleView始终居中,则需要添加一个显式的 CenterX 约束(请参阅下面的代码和链接)。
  • updateCustomTitleView每次 titleView 内容更新时调用(见下文)。我们需要将 titleView 设置为 nil,然后再次将其设置为我们的视图,以防止标题视图偏移居中。当标题视图从宽变窄时会发生这种情况。
  • 不要禁用 translatesAutoresizingMaskIntoConstraints

Gist: https://gist.github.com/bhr/78758bd0bd4549f1cd1c

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

Updating CustomTitleViewfrom 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.hwith 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 中实现了带有下拉箭头图像的导航栏标题:

  1. Create a UIViewsubclass for custom titleView
  2. In your subclass: a) Use auto layout for subviews but not for itself. Set translatesAutoresizingMaskIntoConstraintsto falsefor subviews and truefor titleViewitself. b) Implement sizeThatFits(size: CGSize)
  3. If your title can change call titleLabel.sizeToFit()and self.setNeedsUpdateConstraints()inside titleView's subclass after text changes
  4. In your ViewController call custom updateTitleView()and make sure to call titleView.sizeToFit()and navigationBar.setNeedsLayout()in there
  1. UIView为自定义创建子类titleView
  2. 在您的子类中:a) 为子视图使用自动布局,但不为它自己使用。设置translatesAutoresizingMaskIntoConstraintsfalse为子视图,并truetitleView自身。b) 实施sizeThatFits(size: CGSize)
  3. 如果您的标题可以在文本更改后更改 calltitleLabel.sizeToFit()self.setNeedsUpdateConstraints()insidetitleView的子类
  4. 在您的视图控制器调用自定义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 titleViewand hardcoded layout logic inside UINavigationBaryou 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