隐藏 属性 无法在动画块内更改

Hidden property cannot be changed within an animation block

我在 UIStackView 中嵌入了两个 UILabel。顶部标签始终可见,但底部标签通过 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
    }
 }

隐藏标志被设置为相同状态的次数与它必须设置为不同状态的次数之间似乎存在相关性,然后才能真正变回。就我而言,在动画块中再次将其设置为 YES 之前,我已将隐藏标志设置为 YES,这导致了我不得不在其他动画块中调用 hidden = NO 两次以使其再次可见的问题。如果我在同一视图的第一个动画块中添加了更多 hidden = YES 行,我也必须在第二个动画块中添加更多 hidden = NO 行。这可能是 UIStackView 对隐藏标志的 KVO 观察中的一个错误,该隐藏标志在更改导致此问题的某些内部状态之前不检查值是否实际更改。

为了暂时解决这个问题(直到 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 with animations gets stuck in hidden mode http://www.openradar.me/22819594

我的解决方案虽然不理想,但隐藏了没有动画的 UIStackView。

考虑到 UIStackView 错误,我决定检查隐藏 属性。

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

这不是最优雅的解决方案,但对我有用。

根据 Raz0 的回答,谁发现只有在必要时设置 isHidden 才能解决问题,这里有一个 swift 解决方法,使它对我有用。我正在避免方法调配,因为它本质上是不安全的,支持 show/hide 不应与原始方法混淆的方法:

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

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

这样使用:

view.show()
view.hide()

在 iOS 11 和之前,当多次使用 UIView 动画 API 隐藏 UIStackViewarrangedSubview 时,隐藏的 属性 值 "stack",并且需要多次设置 hidden 为 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 中的一个错误,查看清楚地重现它的 sample project

对我有用的是在动画之外设置隐藏 属性,然后为 layoutIfNeeded() 设置动画,就像使用动画约束一样:

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

其中标签是 UIStackView 的排列子视图。