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


У меня есть две 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 раза). Я нашел это, когда взломал закрытие анимации и увидел, что свойство не изменится на it's назначение. Теперь я замечаю, что та же проблема возникает, казалось бы, случайно снова. Значение по умолчанию для второй метки равно 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
    }
 }
6 16

6 ответов:

В iOS 11 и ранее, при скрытии arrangedSubview из UIStackView с помощью UIView animation API несколько раз, скрытые значения свойств "стекаются", и это требует установки скрытого значения 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, проверьте образец проекта , который воспроизводит его ясно.

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

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

Чтобы временно устранить проблему (до тех пор, пока Apple не исправит ее), я создал категорию для UIView и перевел метод 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

Похоже на ошибку Apple с UIStackView. Увидеть следующее...

UIStackView: toggleing hidden с анимацией застревает в hidden режим http://www.openradar.me/22819594

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

Рассматривая ошибку UIStackView, я решаю проверить скрытое свойство.

if myView.hidden != hidden {
   myView.hidden = hidden
}
Это не самое элегантное решение, но оно работает для меня.

Согласно ответу 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()

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

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

Где label-это упорядоченное подвидо UIStackView.