IOS/Objective-C:以编程方式在按钮下设置中心线

IOS/Objective-C: Center Line under Button Programmatically

我正在尝试在三个按钮下做一条简单的线来创建标签效果。虽然我可以在 Storyboard 中执行此操作,但我想在代码中执行此操作,并且理想情况下使该行成为视图中已有元素的子视图,以避免必须为其提供特殊约束。

我的方法是创建一个没有文本和背景颜色的 UILabel。我可以使 UILabel 成为视图的子视图,但是,这不会将它附加到按钮的底部。

另一方面,如果我将 UILabel 设为其中一个按钮的子视图,我将看不到它。

谁能推荐一个简单的方法来做到这一点?

[centerButton setTitle:@"Favorites" forState:UIControlStateNormal];

    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(20, 120, 280, 2)];
            label.backgroundColor = [UIColor redColor];
            label.textAlignment = NSTextAlignmentCenter;
             label.numberOfLines = 1;
             label.text = @"";
            [self.view addSubview: label];

提前感谢您的任何建议。

编辑:

我尝试以编程方式添加一些 NSLayoutConstraints:

    NSLayoutConstraint *con3 = [NSLayoutConstraint
                                constraintWithItem:label attribute:NSLayoutAttributeTop
                                relatedBy:NSLayoutRelationEqual toItem:centerButton
                                attribute:NSLayoutAttributeBottom multiplier:1 constant:0];

    [label addConstraints:@[con3]];
//tried with and without
    [self.view layoutIfNeeded];

这返回了异常:

     [LayoutConstraints] The view hierarchy is not prepared
 for the constraint: <NSLayoutConstraint:0x174e85500 V:
[UIButton:0x100b0baf0'Now']-(0)-[UILabel:0x108555160] 
  (inactive)>
        When added to a view, the constraint's items
 must be descendants of that view (or the view itself). 
This will crash if the constraint needs to be resolved 
before the view hierarchy is assembled. 
Break on -[UIView(UIConstraintBasedLayout) 
_viewHierarchyUnpreparedForConstraint:] to debug.

故事板中创建的按钮是 self.view 的子视图,我将标签添加到 self.view 作为代码中的子视图,所以不确定为什么会发生异常。

根据错误消息,我想知道您是否可能在将 redLabel 视图添加到它的 parentView 之前(或在生命周期的早期)尝试添加 NSLayoutConstraint。无论如何,我认为这与您要完成的目标非常接近:

#import "ViewController.h"

@interface ViewController ()
@property (strong, nullable) UILabel *previousRedLabel;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (IBAction)buttonClicked:(id)sender {
    if ([sender isKindOfClass:[UIButton class]]) {
        UIButton *clickedButton = (UIButton *)sender;
        UIView *buttonSuperview = [clickedButton superview];
        if (buttonSuperview != nil) {
            [self _putRedLineWithHeight:3.0f atTheBottomOfView:buttonSuperview animate:YES];
        }
    }
}

- (void)_putRedLineWithHeight:(CGFloat)height atTheBottomOfView:(UIView *)viewToPutUnder animate:(BOOL)animate {
    // remove our previous red line
    if (self.previousRedLabel) {
        // if you want it to be a no-op here if they click the same button
        // you'll need to add some logic to check if the superView == viewToPutUnder
        [self.previousRedLabel removeFromSuperview];
        self.previousRedLabel = nil;
    }
    UILabel *redLabel = [[UILabel alloc] init];
    // we're using autolayout so we don't want any resizing from it
    redLabel.translatesAutoresizingMaskIntoConstraints = NO;
    redLabel.backgroundColor = [UIColor redColor];
    // start out with alpha = 0
    redLabel.alpha = 0.0f;
    // add it to our parentView
    [viewToPutUnder addSubview:redLabel];
    // height (determined by passed in value)
    NSAssert(height >= 0, @"Height must be a positive number");
    NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:redLabel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:height];
    // width equal to parentView's width
    NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:viewToPutUnder attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:redLabel attribute:NSLayoutAttributeWidth multiplier:1.0f constant:0.0f];
    // center x == parentView's center x
    NSLayoutConstraint *centerConstraint = [NSLayoutConstraint constraintWithItem:viewToPutUnder attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:redLabel attribute:NSLayoutAttributeCenterX multiplier:1.0f constant:0.0f];
    // now the bottom constraint (place it at the bottom of the parent view)
    NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:viewToPutUnder attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:redLabel attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0.0f];
    // add the height constraint to our label
    [redLabel addConstraint:heightConstraint];
    // and all the other constraints to our parent view
    [viewToPutUnder addConstraints:@[widthConstraint, centerConstraint, bottomConstraint]];
    redLabel.alpha = 1.0f;
    if (animate) {
        [UIView animateWithDuration:0.6f animations:^{
            [redLabel layoutIfNeeded];
        }];
    }
    self.previousRedLabel = redLabel;
}

动画示例:

和非动画之一:

如果每个按钮不在其自己的监督中,则编辑答案以处理该案例

针对所有按钮是否在一个超级视图中进行了调整(宽度基于按钮宽度,中心到按钮中心,并将标签的顶部固定到按钮的底部)

#import "ViewController.h"

@interface ViewController ()
@property (strong, nullable) UILabel *previousRedLabel;
- (void)_putRedLineWithHeight:(CGFloat)height atTheBottomOfButton:(UIButton *)button animate:(BOOL)animate;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (IBAction)buttonClicked:(id)sender {
    if ([sender isKindOfClass:[UIButton class]]) {
        UIButton *clickedButton = (UIButton *)sender;
        // if you want it to be a no-op here if they click the same button
        // you'll need to add some logic to store the previous clicked button and check whether it's the same button
        [self _putRedLineWithHeight:3.0f atTheBottomOfButton:clickedButton animate:YES];
    }
}

- (void)_putRedLineWithHeight:(CGFloat)height atTheBottomOfButton:(UIButton *)button animate:(BOOL)animate {
    UIView *buttonSuperview = button.superview;
    NSAssert(buttonSuperview != nil, @"Button has to have a superview");
    // remove our previous red line
    if (self.previousRedLabel) {
        [self.previousRedLabel removeFromSuperview];
        self.previousRedLabel = nil;
    }
    UILabel *redLabel = [[UILabel alloc] init];
    // we're using autolayout so we don't want any resizing from it
    redLabel.translatesAutoresizingMaskIntoConstraints = NO;
    redLabel.backgroundColor = [UIColor redColor];
    // start out with alpha = 0
    redLabel.alpha = 0.0f;
    // add it to our parentView
    [buttonSuperview addSubview:redLabel];
    // height (determined by passed in value)
    NSAssert(height >= 0, @"Height must be a positive number");
    NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:redLabel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:height];
    // width equal to button's width
    NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:redLabel attribute:NSLayoutAttributeWidth multiplier:1.0f constant:0.0f];
    // center x == button's center x
    NSLayoutConstraint *centerConstraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:redLabel attribute:NSLayoutAttributeCenterX multiplier:1.0f constant:0.0f];
    // now pin the top of the label to the bottom of the button
    NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:redLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:button attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0.0f];
    // add the height constraint to our label
    [redLabel addConstraint:heightConstraint];
    // and all the other constraints to our parent view
    [buttonSuperview addConstraints:@[widthConstraint, centerConstraint, bottomConstraint]];
    redLabel.alpha = 1.0f;
    if (animate) {
        [UIView animateWithDuration:0.6f animations:^{
            [redLabel layoutIfNeeded];
        }];
    }
    self.previousRedLabel = redLabel;
}


@end

动画:

非动画: