我有一个UIView子类,它被添加为UIStackView的排列子视图。根据模型中的数据,我想隐藏或显示排列的子视图(称为myView
),但问题是,当我去隐藏它时,即使我设置了myView.hidden = NO
,它仍然显示myView.hidden = YES
。
例如,下面是我的代码。它从隐藏视图开始,根据是否设置了myModel.someProperty
,它将显示myView。或者这就是应该发生的事情。
我已经设置了一个断点,并逐步完成了这段代码,并使用LLDB来验证第4行之前的self.myView.hidden == YES
是否被执行。然后我在跨过第4行之后检查了这个值,结果仍然是YES。但是第4行明确地将其设置为NO,myView的实现中没有任何内容覆盖甚至设置或检查其自身的隐藏属性。因此,对其隐藏的设置只适用于标准的UIViewsetHidden:
方法。那么,它怎么可能仍然是YES呢?
1. //currently, self.myView.hidden is YES
2.
3. if (self->_myModel.someProperty) {
4. self.myView.hidden = NO;
5.
6. //for some reason, self.myView.hidden is still YES
7.
8. while (self.myView.isHidden) {
9. NSLog(@"myView is hidden, but it should not be");
10. self.myView.hidden = NO;
11. }
12. NSLog(@"myView is no longer hidden");
13. }
我在第8行添加了一个循环,这将导致视图再次被隐藏。这次有效。所以,如果我设置myView.hidden = NO
两次,那么它实际上会被设置为NO。但如果我只设置一次,它就会保持在YES。我不明白发生了什么事。
有人知道这里可能出了什么问题,或者如何进一步解决这个问题吗?我已经使用LLDB的po
命令来查看每个属性集前后的myView.isHidden
值。所以在第4行之前,它被设置为YES,这是正确的。然后,在第4行之后,我检查了它,它仍然被设置为YES,尽管在前一行它被明确设置为NO。然后,我检查了一下,它进入了第8行的循环(即使它不应该像应该的那样被隐藏)。然后我在第10行之前再次检查,myView.hidden
仍然是YES,我在第十行之后检查,它最终被正确设置为NO。
但我只是不确定发生了什么。这是非常违反直觉的,因为我明确地将其设置为NO,但直到我两次将其设置成NO,它才被设置。
有没有一个好的方法来解决这个问题或找出问题所在,或者有人对可能的问题有任何建议吗?
更新
我已经更新了代码,添加了一些额外的日志语句。在LLDB中检查该属性时,我也使用了p self.myView.hidden
。
1. // at this point, self.myView.hidden = YES
2.
3. if (self->_myModel.someProperty) {
4. NSLog(@"Before setting hidden=NO: %@", self->_myView);
5. self.myView.hidden = NO;
6. NSLog(@"After setting hidden=NO: %@", self->_myView);
7.
8. while ([self.myView isHidden]) {
9. NSLog(@"SHOULD NOT BE HERE - Before setting hidden=NO again: %@", self->_myView);
10. self.myView.hidden = NO;
11. NSLog(@"SHOULD NOT BE HERE - After setting hidden=NO again: %@", self->_myView);
12. }
13.
14. NSLog(@"Finally, no longer hidden: %@", self->_myView);
15. }
以下是此代码中的日志语句。第一条日志语句是正确的,因为它显示了myView.hidden==YES。然而,第二条日志语句对我来说似乎是错误的,因为它仍然显示myView.hidden==YES,尽管在前一行它只是设置为NO。
设置隐藏之前=否:<我的视图:0x117ef6eb0;帧=(0 49.6667;123.667 20.3333);hidden=YES;layer=<CA层:0x280ddaa20>gt;
在设置hidden=NO:<我的视图:0x117ef6eb0;帧=(0 49.6667;123.667 20.3333);hidden=YES;layer=<CA层:0x280ddaa20>gt;
下一组日志语句在循环中,由于我将myView.hidden设置为NO,它甚至不应该输入,但它仍然会输入,因为值仍然是YES。在这里,它看起来工作正常。第一条日志语句显示它是可见的,然后下一条日志语句则显示它是隐藏的。
不应该在这里-再次设置hidden=NO之前:<我的视图:0x117ef6eb0;帧=(0 49.6667;123.667 20.3333);hidden=YES;layer=<CA层:0x280ddaa20>gt;
不应该在这里-再次设置hidden=NO后:<我的视图:0x117ef6eb0;帧=(0 49.6667;123.667 20.3333);layer=<CA层:0x280ddaa20>gt;
最后,不再隐藏:<我的视图:0x117ef6eb0;帧=(0 49.6667;123.667 20.3333);layer=<CA层:0x280ddaa20>gt;
更新2
我知道这段代码似乎是自己工作的,但它在我的项目中不适合我。我将在这里显示我的视图类的代码,以及调试会话的输出,显示在代码中观察到的相同行为。
我知道它可能在我的代码中,但同时,我也不知道该怎么做。我的所有代码在这里都是对setHidden:
的调用。没有额外的。在调用setHidden之前,hidden的值为YES。调用setHidden:NO
后,该值仍然为YES。我不明白。我想知道这是否是编译器的问题。我知道这些编译器经过了很好的测试,但同时我也不明白我的代码是怎么回事。我只是简单地设置hidden=NO,但除非我做两次,否则它不起作用。
调试会话
这是LLDB的输出。我在视图即将取消隐藏之前设置了一个断点(前面代码片段中的第3行)。此时,myView.hidden = YES
。
所以我所做的只是打印该视图的隐藏值,它正确地显示了YES。之后,我运行了call self.myView.hidden = NO
来尝试更新它,但这不起作用,正如在调用语句下面打印的调试语句中所看到的那样。它仍然显示hidden = YES;
。我也继续打印了一遍,只是为了确定,它仍然显示隐藏=YES。
(lldb) p self.myView.hidden
(BOOL) $12 = YES
(lldb) call self.myView.hidden = NO
<MyView: 0x12b138980; frame = (0 49.6667; 123.667 20.3333); hidden = YES; layer = <CALayer: 0x283addfe0>> MyView::setHidden:NO
(BOOL) $13 = NO
(lldb) p self.myView.hidden
(BOOL) $15 = YES
接下来,我只是再次将值设置为NO,这一次它的工作原理可以从debug语句中看出,并且我还再次打印了值以进行良好的测量。
(lldb) call self.myView.hidden = NO
<MyView: 0x12b138980; frame = (0 49.6667; 123.667 20.3333); layer = <CALayer: 0x283addfe0>> MyView::setHidden:NO
(BOOL) $16 = NO
(lldb) p self.myView.hidden
(BOOL) $17 = NO
这是我的视图类的显示和隐藏代码。我没有重写或使用隐藏属性做任何事情,所以对setHidden:
的任何调用都会直接转到UIView上的方法。
MyView.h
#import <UIKit/UIKit.h>
#import "MyModel.h"
@interface MyView : UIView
@property (strong, nonatomic, nullable) MyModel *myModel;
@end
MyView.m
#import "MyView.h"
@interface MyView ()
@property (strong, nonatomic) UILabel *label;
//other UI components are here, but they are just more labels and an image view
@end
@implementation MyView
- (instancetype)init {
return [self initWithFrame:CGRectZero];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
[self initialize];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self initialize];
}
return self;
}
- (void)initialize {
[self addSubview:self.label];
//add other labels and the image view
[NSLayoutConstraint activateConstraints:@[
[self.label.leadingAnchor constraintGreaterThanOrEqualToAnchor:self.leadingAnchor],
[self.label.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor],
[self.label.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
//more constraints for the other labels and the image
]];
}
- (void)setMyModel:(MyModel *)myModel {
self->_myModel = myModel;
[self updateDisplay];
}
- (void)updateDisplay {
//set the text of all the labels based on the model
}
- (UILabel *)label {
if (!self->_label) {
self->_label = [[UILabel alloc] init];
self->_label.translatesAutoresizingMaskIntoConstraints = NO;
self->_label.numberOfLines = 0;
self->_label.text = @"My Text:";
[self->_label setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
[self->_label setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
}
return self->_label;
}
@end
请让我知道,如果我应该发布任何其他有帮助的东西,或者如果有什么我可以尝试的。我可以在代码中只写两次值,但在不理解为什么必须这样做的情况下,我觉得这有点危险,因为我怎么知道两次就足够了?另外,必须连续两次将一个变量设置为相同的值才能工作,这很奇怪。
感谢大家在这方面的帮助。
是的,在UIStackView
中设置显示/隐藏排列子视图的动画时存在一个错误/怪癖。
您应该能够通过将其添加到您的自定义视图类中来更正此问题:
- (void)setHidden:(BOOL)hidden {
if (self.isHidden != hidden) {
[super setHidden:hidden];
}
}
这里有一个完整的例子来说明这个问题;fix":https://github.com/DonMag/StackViewBug
这似乎是由于UIStackView中的一个错误造成的,如果您多次隐藏视图,它会累积该视图的隐藏计数。例如,想象一个视图层次结构如下:
- UIStackView
- MyView
然后,如果您将MyView
hidden=YES设置三次,则需要三次设置hidden=NO才能将其实际设置为NO。这似乎不是一个相反的问题。所以,如果你把它设置为hidden=NO三次,你可以把它设置成hidden=YES一次,它就会被隐藏。
此StackOverflow回答中有更多信息:https://stackoverflow.com/a/45599835/5140550
我不确定这个错误是否已经报告给苹果,但它似乎是UIStackView中的一个错误。现在我只需要找出一种干净的方法来处理代码中的这个问题。