Анимировать ограничения NSView (представление с поддержкой слоя) в macOS objective-c

#objective-c #constraints #core-animation #nsview

#objective-c #ограничения #ядро-анимация #nsview

Вопрос:

Я использую XCode 12, macOS, objective-c, а не iOS.

У меня есть меню (горизонтальный вид стека) с 3 пунктами:

введите описание изображения здесь

Синее подчеркивание создается программно:

 - (void)viewDidLoad {

    [super viewDidLoad];     
    [self createAnimatedLineAtButton:self.importButton];
}

// Create line
- (void)createAnimatedLineAtButton:(NSButton*)button {

    // This view is layer backed
    SHViewCustomBackground* underlineView = [[SHViewCustomBackground alloc] init];
    
    underlineView.backgroundColor = [NSColor colorNamed:@"color_spot"]; // blue color
    underlineView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:underlineView];
    self.underlineView = underlineView;    

    [self updateAnimatedLineUnderButton:self.importButton];
}
  

Следующие коды обновляют ограничения (первоначально вызываемые при создании строки выше), а затем при нажатии каждой кнопки.

 - (void)updateAnimatedLineUnderButton:(NSButton *)button {
    
    // Constraints are stored here
    if (self.underlineConstraints) {
        [self.view removeConstraints:self.underlineConstraints];
    }

    [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
        context.duration = 1;
        context.allowsImplicitAnimation = YES;
    
        NSView* underlineView = self.underlineView;
        NSDictionary *views = NSDictionaryOfVariableBindings(underlineView, button);
        self.underlineConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[button]-[underlineView(1)]" options:NSLayoutFormatAlignAllLeading | NSLayoutFormatAlignAllTrailing metrics:nil views:views];
        [self.view addConstraints:self.underlineConstraints];
    
        [self.view layoutSubtreeIfNeeded];
    } completionHandler:nil];    
}
  

Вот действия кнопки

 - (IBAction)buttonClicked:(id)sender {
    
    [self updateAnimatedLineUnderButton:(NSButton*)sender];
}
  

Теперь мой вопрос: когда я нажимаю кнопки в таком порядке «Импорт»> «Экспорт»> «Импорт», строка анимируется, как и ожидалось, от импорта до экспорта и обратно. Всякий раз, когда я нажимаю на «Заполнитель» (средняя кнопка), строка просто прыгает и не анимируется. Когда я нажимаю «Импорт» или «Экспорт», после этого он тоже прыгает.

введите описание изображения здесь

Я бьюсь головой о том, что здесь не так. Все кнопки подключены правильно, все действия выполняются правильно.

РЕДАКТИРОВАТЬ: после сравнения всех кнопок я обнаружил, что кнопка импорта и экспорта (где она работает) имеют одинаковую ширину.

Комментарии:

1. Я вижу, что ширина этого подчеркивания также изменяется в анимации, и обратите внимание на ваш комментарий о ширине. Может быть, выровнять по центру и установить ширину как два отдельных явных ограничения (или даже просто дать ему фиксированную ширину на данный момент) и посмотреть, как это происходит?

2. он работает с фиксированной шириной. даже ограничение выравнивания по центру в сочетании с «button-equalWidth» не работает. Всякий раз, когда дело доходит до динамической ширины, она показывает поведение в формате GIF. К сожалению, я хочу, чтобы динамическая ширина была локализована с разной длиной строки

3. Должно работать, если вы выполняете анимацию разделения — возможно, обновите свой код? Также, возможно, переместить layoutSubtree в блок завершения?

4. LayoutSubtree запускает анимацию … поэтому не может перейти к блоку завершения. Что вы подразумеваете под разделенной анимацией?

5. Я имею в виду, где вы анимируете ограничения ширины и выравнивания по центру отдельно.

Ответ №1:

Вот идея … это работает. Изначально я собирался сделать это по-своему, но, возможно, вам это не понравится, в итоге анимировал два ограничения. Но это работает хорошо.

введите описание изображения здесь

Идея очень похожа на вашу, но разница здесь в том, что я создаю начальное и конечное ограничение и анимирую их вместо.

 @implementation ViewController

- ( void ) viewDidLoad
{
    super.viewDidLoad;
    [self animateLine:self.longButton];
}

// Animate the line
- ( void ) animateLine:( NSButton * ) button
{
    [NSAnimationContext runAnimationGroup: ^ ( NSAnimationContext * context ) {

        context.duration = 1;
        context.allowsImplicitAnimation = YES;
        self.leadingConstraint.animator.constant = button.frame.origin.x;
        self.trailingConstraint.animator.constant = self.stackView.bounds.size.width - button.frame.origin.x - button.frame.size.width;
        NSLog ( @"Setting leading to %f trailing to %f", self.leadingConstraint.constant, self.trailingConstraint.constant );

        self.lineField.layoutSubtreeIfNeeded;

    }];
}

- ( IBAction ) buttonAction:( id ) sender
{
    [self animateLine:sender];
}

  

Это оно. Остальное происходит в раскадровке. Я установил ограничения так, чтобы расстояния имели смысл, поменяв местами первый и второй элемент по вкусу.

Комментарии:

1. Это здорово. Хотя я хотел создать все в коде, а не в IB.

2. Он, он, он, тебе нравится страдать… Я сделал как можно больше в раскадровке, чтобы сэкономить время и протестировать идею. Именно поэтому я в конечном итоге сделал это таким образом, поскольку это было самым быстрым. Я думаю, что вы можете легко перенести это в код.

3. Я попытаюсь настроить ограничения для viewdidload и сохранить их в некоторых ссылочных указателях. Если это сработает, я определенно приму ваш ответ, поскольку он проверен на работоспособность. Спасибо