更新 UILabel 文本会重置故事板吗?

Updating UILabel Text resets storyboard?

我遇到了一个奇怪的场景,我自己想不通。

我开始了一个小测试场景,其中有一个几乎是空的 iOS 单视图应用程序和一个故事板。故事板包含一个标签和一个 10x10 的 UIView,仅用于展示问题。当然,他们有适当的 IBOutlets 分配和连接:

@property (nonatomic, weak) IBOutlet UILabel *label;
@property (nonatomic, weak) IBOutlet UIView *dot;

在 (void)viewDidLoad 方法中,我启动了一个计时器并将其添加到 运行 循环中,如下所示:

- (void)viewDidLoad {
    [super viewDidLoad];

    NSTimer * timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

在 (void)doSomething 方法中,我执行以下操作:

-(void)doSomething {
    // Lets move the dot to the right a bit further
    _dot.center = CGPointMake(_dot.center.x+1, _dot.center.y);

    // About every ten steps, update the label with the current
    // X position, to better show the problem
    CGFloat moduloResult = (float)((int)_dot.center.x % (int)10.0);
    if(moduloResult <= 0)
        _label.text = [NSString stringWithFormat:@"%f", _dot.center.x+1];
}

所以基本上,我每次将 10x10 点视图的中心更改 1,并且每第 10 次更新标签以显示当前的 X 位置。就此而言,我更新标签文本的内容甚至都不重要,重要的是它会发生变化。而且我每 10 次只更改文本以更好地显示这里的问题。

如果我 运行 该代码点按预期向右移动,但是当我更新标签文本时,点视图会重置并跳回其原始位置。所以更新我的标签文本会重置我在 Storyboard 上的其他视图,这让我很困惑。

有人可以重现甚至解释一下吗?

提前谢谢大家:)

亚历克斯

我能够重现所描述的行为,这就是它发生的原因:

  1. 默认情况下,元素的位置由自动生成的 AutoLayout 约束决定
  2. 更改 dot 的位置时,您只是在更新它的当前帧,而不是基础约束
  3. 更改 UILabel 的文本可能会更改 UILabel 内容的大小,因此需要重新计算其框架。这里整个布局是根据约束重新计算的,因此也重置了 dot.
  4. 的框架

为了解决,我建议正确指定AutoLayout约束并更新它们而不是直接定位。

差不多就是这样,AutoLayout 约束总是将视图推回到它的原始位置,我想这是有道理的,这就是 AutoLayout 应该做的。

所以,因为我想让我的 10x10 小点视图在屏幕上移动(出于练习目的,我更改了我的代码。我试着解释一下,目前缺乏声誉使我无法发布适当的图像。

我制作了两个自定义 AutoLayout 约束,告诉它始终保持视图距父视图左侧 120 像素和距父视图顶部 100 像素。我还为每个 10 的固定宽度和高度指定了约束。因为对于有效约束,您需要 x 和 y 以及高度和宽度的组合(至少据我所知)。

                  -
                  |
                  | Constraint *n* from top
                  |

                  |------|
  Constraint      | view |
  *n* from left   | _dot |
|---------------  |------|

然后我为两个约束创建了两个 IBOutlets:

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *c1X;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *c1Y;

我将我的 (void)doSomething 方法更改为:

-(void)doSomething {

    // Lets move the dot to the right a bit
    // further by changing its Constraint
    _c1X.constant++;

    // About every ten steps, update the labe with the current
    // X position, to better show the problem
    CGFloat moduloResult = (float)((int)_dot.center.x % 10);
    if(moduloResult <= 0)
        _label.text = [NSString stringWithFormat:@"%f - %f - %f", _c1X.constant, _dot.center.x, _dot.center.y];
}

它起作用了,改变约束而不是实际视图现在将视图移到屏幕上。如果这是最佳实践,我不知道。如果我没有出于练习目的尝试过它,按照一个陈旧的教程,我一开始就不会想到它。

希望有一天这会有所帮助。欢迎提出任何意见或改进:)

亚历克斯

这似乎是一个老问题,但它也困扰着我。 我感到困惑的是,更改视觉层次结构底部 UILabel 中的文本会使整个故事板重新布局。

不仅如此,由于具有一些 Android 开发背景,我从未在 Android 中使用(类似的)TextView 目睹过此类行为。您可以按照自己的意愿更改其中的文本——永远不会更新 UI.

的更高级别

但是@Jakub Vano 的回答让我意识到一件事:

Android 中的 TextView 始终固定宽度和高度,视图内文本的更新不会改变其边框。 相反,iOS' UILabel 可以根据其中的文本保留自定义格式的宽度和高度。 不幸的是,即使修复故事板中的宽度和高度也不能解决问题:更新文本仍然会触发重新布局所有内容的请求。

我找到了使用 CATextLayer 的解决方案 -- 它不会影响布局,您可以在其中放置任何您想要的文本。但这样做的代价是您必须以编程方式处理所有事情,请记住您处理的是真实像素,而不是点,如下所示:

            let mScale = UIScreen.main.scale

            indicatorLeft = CATextLayer ()
            indicatorLeft.fontSize = FONT_SIZE * mScale
            indicatorLeft.string = "text in CA layer"
            indicatorLeft.foregroundColor = UIColor.white.cgColor
            indicatorLeft.shadowColor = UIColor.gray.cgColor
            indicatorLeft.isHidden = false
            indicatorLeft.contentsScale = mScale

            indicatorLeft.frame = CGRect(x: 0, y:  0,
                                 width: VIEW_WIDTH * mScale, height: VIEW_HEIGHT * mScale )

            leftView.layer.addSublayer(indicatorLeft)