2013-03-08 34 views
24

我試圖建立與約束的titleview的,看起來像這樣:構建titleview的編程方式與約束(或一般建設有約束的視圖)

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的大小。它應該能夠通過其內容的大小來確定,但是...

  1. titleView確定它的大小是0,除非我硬編碼一幀。
  2. 如果我設置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

您的原始代碼可能不起作用,因爲您在垂直軸上沒有任何約束;你的第二個應該沒問題,無需設置任何框架。如果您創建該視圖並將其添加到其他位置(而不是導航欄,這可能會引入額外的複雜性),會發生什麼情況。 – jrturton 2013-04-21 15:43:35

+1

我試圖實現這個沒有設置框架,但我無法找到誰是標題視圖的父母,我們應該在其中添加約束... – 2013-07-03 18:44:45

+1

視圖的父母是UINavigationBar本身 - 但你是不允許由於某種原因添加約束 - 無法修改由控制器管理的UINavigationBar約束。 – Petar 2014-11-25 12:30:22

回答

7

你必須設置的titleView框架,因爲你沒有指定其在它的父position任何約束。自動佈局系統只能根據您指定的約束和其子視圖中的intrinsic content size找出sizetitleView

+0

雖然我不認爲是這種情況,因爲我沒有更改'translatesAutoresizingMaskIntoConstraints',所以它應該使用默認的自動調整掩碼。如果我將它設置爲「否」,那麼它應該能夠通過文本標籤和圖像內部來確定尺寸。 – 2013-04-20 23:50:31

+0

自動調整大小需要您定義初始大小;自動佈局需要您指定約束。如果你不這樣做,他們都不能幫助你。考慮一下,或者試試看 - 爲'titleView'設置大小或者公制約束,你就會看到。 – an0 2013-04-21 00:35:40

+0

對於自動調整大小是正確的,但即使禁用了'translatesAutoresizingMaskIntoConstraints',我的titleView也應該能夠根據我定義的約束來確定它的大小(標籤或標籤和圖像),然後應該有一個大小。我猜測它確實存在,但是由於沒有將其定位在父級中,所以它會崩潰,無法確定它的位置。 – 2013-04-21 01:35:30

18

an0的回答是正確的。但是,它不會幫助您獲得理想的效果。

這是我對房屋的所有權觀點,即自動擁有大小合適的配方:稍後將用作

  • 創建UIView子類,例如CustomTitleViewnavigationItemtitleView
  • CustomTitleView內使用自動佈局。如果你想讓你的CustomTitleView始終居中,你需要添加一個明確的CenterX約束(見下面的代碼和鏈接)。
  • 每次您的titleView內容更新時請致電updateCustomTitleView(見下文)。我們需要將titleView設置爲零,然後再次將其設置爲我們的視圖,以防止標題視圖偏移居中。當標題視圖從寬變爲窄時會發生這種情況。
  • 沒有禁用translatesAutoresizingMaskIntoConstraints

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

從您的視圖控制器更新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 
+0

謝謝你提到你不應該禁用'translatesAutoresizingMaskIntoConstraints'。我已經設置了一個以前在其他地方顯示爲'titleView'的元素,但是在返回後它總是被錯誤地定位。刪除該線固定它。 – Livven 2016-08-11 13:54:08

+0

我的標題視圖顯示在中心位置,其子視圖出現在(0,0)窗口座標中(最頂部,最左側)。除了用於限制的錨API以外,我的工作基本上與您一樣。 – 2017-03-30 08:38:46

5

對於內部UINavigationBartitleView和硬編碼的佈局邏輯自動佈局約束相結合,你必須實現自己的自定義titleView的類中的方法sizeThatFits:像這樣(的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)) 
    ); 
} 
8

謝謝@Valentin Shergin和@tubtub!根據他們的回答我的雨燕1.2製作導航欄標題與下拉箭頭圖像的實現:1)使用自動佈局子視圖而不是:

  1. 定製titleView
  2. 在子類中創建一個UIView子本身。設置translatesAutoresizingMaskIntoConstraintsfalse子視圖,truetitleView本身。 b)實施sizeThatFits(size: CGSize)
  3. 如果你的標題可以在那裏

這裏是改變呼叫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) 
        } 
    } 
    

    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() 
    } 
    
  • +0

    無法正常工作。在sizeThatFits()中計算的綁定rects在寬度和高度返回零。 – 2017-03-30 08:23:01

    47

    我真的需要約束,所以今天就玩弄它。我發現的作品是這樣的:

    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 
    

    工程就像一個魅力!

    +2

    我希望我之前找到了這個答案,因爲我剛剛得到了這個工作,儘管通過對所有子視圖使用'sizeThatFits',然後計算了視圖的框架。這個答案更簡潔可靠。謝謝!! – Alexander 2016-08-15 18:57:33

    +1

    感謝它節省了我的一天,掙扎着我的堆棧佈局:p – 2017-01-26 14:46:47

    +0

    這給了我對子視圖(大小)的限制:我設置的那些(非零)和自動生成的掩碼生成的(零)。 – 2017-03-30 07:56:06