Hidden property cannot be changed within an animation block

妖精的绣舞 提交于 2019-12-04 08:34:08

问题


I've got two UILabels embedded within a UIStackView. The top label stays visible constantly, but the bottom label is toggled on and off via the hidden property. I wanted this effect to be animated, so I stuck it in an animation block:

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

The problem is that the hidden property will not change unless I repeat the statement over and over (3 times in this case). I found this while breaking into the animation closure and seeing that the property would not change to it's assignment. Now I'm noticing the same problem occurring seemingly randomly again. The default value of the second label is true, if that's relevant.

Is there something I'm missing here, or is this a bug?

Update: For what it's worth, I got it working by adding removeArrangedSubview() and 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:


On iOS 11 and prior, when hiding an arrangedSubview of a UIStackView using UIView animation API multiple times, the hidden property values "stack", and it requires setting hidden to false multiple times before the value actually changes.

At work we decided to use a UIView extension with a workaround method that sets hidden only once for given value.

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

This is definitely a bug in UIKit, check out the sample project that reproduces it clearly.




回答2:


There seems to be correlation on how many times hidden flag is set to same state and how many times it must set to different state before it's actually changed back. In my case, I had hidden flag already set to YES before it was set to YES again in animation block and that caused the problem where I had to call hidden = NO twice in my other animation block to get it visible again. If I added more hidden = YES lines in first animation block for the same view, I had to have more hidden = NO lines in second animation block as well. This might be a bug in UIStackView's KVO observation for the hidden flag that doesn't check if the value is actually changed or not before changing some internal state that leads to this issue.

To temporarily fix the issue (until Apple fixes it), I made a category for UIView and swizzled setHidden: method to a version that first checks the original value and sets the new value only if it differs from the original. This seems to work without any ill effects.

@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:


Appears to be an Apple bug with UIStackView. See the following...

UIStackView: toggleing hidden with animations gets stuck in hidden mode http://www.openradar.me/22819594

My solution, although not ideal, was to hide the UIStackView without animation.




回答4:


In considering UIStackView bug I decide to check hidden property.

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

It's not the most elegant solution but it works for me.




回答5:


As per Raz0's answer, who found out that only setting isHidden when necessary solves the issue, here's a swift workaround that made it work for me. I'm avoiding method swizzling because it's inherently unsafe, in favor of a show/hide approach that shouldn't mess up with the original methods:

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

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

Use it like this:

view.show()
view.hide()



回答6:


What worked for me is to set the hidden property outside of the animation and then animating layoutIfNeeded(), just like you would with animating constraints:

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

where label is an arranged subview of a UIStackView.



来源:https://stackoverflow.com/questions/33240635/hidden-property-cannot-be-changed-within-an-animation-block

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!