Hidden property cannot be changed within an animation block Hidden property cannot be changed within an animation block swift swift

Hidden property cannot be changed within an animation block


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.

enter image description here


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


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.