Скрытое свойство не может быть изменено в блоке анимации

У меня есть два UILabels, встроенных в UIStackView. Верхняя метка остается видимой постоянно, но нижняя метка включается и выключается через свойство hidden. Я хотел, чтобы этот эффект был анимированным, поэтому я застрял в блоке анимации:

private func toggleResultLabel(value:Double) {
    if value == 0 {
        UIView.animateWithDuration(0.25) { () -> Void in
            self.resultLabel.hidden = true
        }
    } else {
        UIView.animateWithDuration(0.25) { () -> Void in
            // Something weird is happening. I had to add 3 of the same statements to get 
            // the hidden flag to be false
            self.resultLabel.hidden = false
            self.resultLabel.hidden = false
            self.resultLabel.hidden = false
        }
    }
}

Проблема заключается в том, что скрытое свойство не изменится, если я повторяю утверждение снова и снова (в этом случае 3 раза). Я нашел это, когда вломился в закрытие анимации и увидел, что свойство не изменит его назначение. Теперь я замечаю ту же проблему, которая кажется случайно случайной. Значением по умолчанию для второй метки является true, если это необходимо.

Есть ли что-то, что мне здесь не хватает, или это ошибка?

Обновление: Для чего это стоит, я получил его работу, добавив removeArrangedSubview() и addArrangedSubview():

if value == 0 {
    UIView.animateWithDuration(0.25) { () -> Void in
        self.resultLabel.hidden = true
        self.heroStackView.removeArrangedSubview(self.resultLabel)
    }
 } else {
    UIView.animateWithDuration(0.25) { () -> Void in
        self.heroStackView.addArrangedSubview(self.resultLabel)
        self.resultLabel.hidden = false
    }
 }

Ответ 1

В iOS 11 и ранее при скрытии arrangedSubview of UIStackView с использованием API анимации UIView несколько раз значения скрытых свойств "стекают", и для этого требуется установить скрытый код false несколько раз до фактического значения изменения.

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

extension UIView {

    // Workaround for the UIStackView bug where setting hidden to true with animation
    // mulptiple times requires setting hidden to false multiple times to show the view.
    public func workaround_nonRepeatingSetHidden(hidden: Bool) {
        if self.hidden != hidden {
            self.hidden = hidden
        }
    }
}

Это определенно ошибка в UIKit, проверьте образец проекта, который четко воспроизводит его.

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

Ответ 2

Кажется, что существует корреляция того, сколько раз скрытый флаг установлен в одно и то же состояние, и сколько раз он должен устанавливать в другое состояние до того, как он действительно изменится. В моем случае у меня был скрытый флаг, уже установленный в YES, прежде чем он снова будет установлен в YES снова в блоке анимации, и это вызвало проблему, когда мне пришлось дважды вызвать скрытый = НЕТ в моем другом блоке анимации, чтобы снова увидеть его. Если бы я добавил несколько скрытых строк = YES в первый блок анимации для одного и того же представления, мне пришлось бы иметь более скрытые строки = НЕТ во втором блоке анимации. Это может быть ошибкой в ​​UIStackView KVO наблюдения для скрытого флага, который не проверяет, действительно ли значение было изменено или нет, прежде чем изменять какое-то внутреннее состояние, которое приводит к этой проблеме.

Чтобы временно исправить проблему (пока Apple не установит ее), я сделал категорию для UIView и swizzled setHidden: метод для версии, которая сначала проверяет исходное значение и устанавливает новое значение, только если оно отличается от оригинала. Это, кажется, работает без каких-либо негативных последствий.

@implementation UIView (MethodSwizzling)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(setHidden:);
        SEL swizzledSelector = @selector(UIStackViewFix_setHidden:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod =
        class_addMethod(class,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)UIStackViewFix_setHidden:(BOOL)hidden
{
    if (hidden != self.hidden) {
        [self UIStackViewFix_setHidden:hidden];
    }
}

@end

Ответ 3

При рассмотрении ошибки UIStackView я решил проверить скрытое свойство.

if myView.hidden != hidden {
   myView.hidden = hidden
}

Это не самое элегантное решение, но оно работает для меня.

Ответ 4

Появляется ошибка Apple с UIStackView. См. Следующее...

UIStackView: включение скрытых анимаций застревает в скрытом mode http://www.openradar.me/22819594

Мое решение, хотя и не идеальное, заключалось в том, чтобы скрыть UIStackView без анимации.

Ответ 5

В соответствии с ответом Raz0, который узнал, что только настройка isHidden, когда это необходимо, решает проблему, вот быстрое решение, которое заставило его работать на меня. Я избегаю метода swizzling, потому что он по своей сути небезопасен, в пользу подхода show/hide, который не должен испортить оригинальные методы:

extension UIView {
    func show() {
        guard isHidden else {
            return
        }
        isHidden = false
    }

    func hide() {
        guard !isHidden else {
            return
        }
        isHidden = true
    }
}

Используйте его следующим образом:

view.show()
view.hide()

Ответ 6

Что сработало для меня - установить скрытое свойство вне анимации, а затем анимировать layoutIfNeeded(), как и с анимационными ограничениями:

label.isHidden = true
UIView.animate(withDuration: 3) {
    self.view.layoutIfNeeded()
}

где метка представляет собой упорядоченное подчинение UIStackView.