IOS / Objective-C: центральная линия под кнопкой программно - PullRequest
0 голосов
/ 28 августа 2018

Я пытаюсь сделать простую линию под тремя кнопками, чтобы создать эффект вкладок. Хотя я мог бы сделать это в раскадровке, я хотел бы сделать это в коде и в идеале сделать строку подпредставлением элемента, уже находящегося в представлении, чтобы не создавать для него особых ограничений.

Мой подход заключался в создании UILabel без текста и цвета фона. Однако я могу сделать UILabel подпредставлением View, которое не прикрепляет его к нижней части кнопок.

С другой стороны, если я сделаю 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 в качестве подпредставления в коде, поэтому не уверен, почему происходит исключение.

1 Ответ

0 голосов
/ 29 августа 2018

Интересно, из сообщения об ошибке, возможно, вы пытаетесь добавить NSLayoutConstraint до того, как представление redLabel будет добавлено в его parentView (или слишком рано в жизненном цикле). В любом случае, я думаю, что это довольно близко к тому, что вы пытаетесь достичь:

#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;
}

Пример анимации:

enter image description here

И один из неанимированных:

enter image description here

РЕДАКТИРОВАТЬ ОТВЕТ, ЧТОБЫ ОБРАЩАТЬСЯ С ДЕЛОМ, ЕСЛИ КАЖДАЯ КНОПКА НЕ НАХОДИТСЯ НА СОБСТВЕННОМ НАБЛЮДЕНИИ

С учетом того, что все кнопки находятся в одном суперпредставлении (ширина основана на ширине кнопки, от центра к центру кнопки и закреплении верхней части надписи на нижней части кнопки)

#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

Animated:

enter image description here

Не анимация:

enter image description here

...