iOS自动布局 - 获取框架大小宽度

59

我正在使用iOS 6自动布局进行开发。

我想记录一个消息,显示视图的框架宽度。

我可以在屏幕上看到textView。

但是我得到的宽度和高度为零,我是不是漏了什么?

NSLog(@"textView    = %p", self.textView);
NSLog(@"height      = %f", self.textView.frame.size.height);
NSLog(@"width       = %f", self.textView.frame.size.width);

textView    = 0x882de00
height      = 0.000000
width       = 0.000000
9个回答

156

实际上,以上答案都不是很正确。我按照它们的方法一次又一次地得到了零。

诀窍是将帧相关的代码放在viewDidLayoutSubviews方法中,该方法会:

通知视图控制器,其视图刚刚布置了其子视图。

不要忘记,该方法会被多次调用,并不属于ViewController的生命周期的一部分,因此在使用时要小心。

希望对某些人有所帮助。

只想补充一下,对于没有横向模式的程序,不使用自动布局要简单得多……虽然我试过 =D


10
根据我的经验,这是自动布局完成后最早的回调。我建议使用BOOL类型来捕获第一次调用并执行基于frame的逻辑。虽然不太优美,但可以解决问题,直到有特定自动布局约束的适当回调可用。 - ArkReversed
3
@dreamzor,如何知道这是最后一次viewDidLayoutSubviews?我发现在iOS 7上,在viewDidAppear之前只会调用一个viewDidLayoutSubviews,但是在iOS 8上,我看到多个viewDidLayoutSubviews在viewDidAppear之前调用。这很烦人,因为第一个viewDidLayoutSubviews会在iOS 8上给出错误的框架大小,所以我必须忽略它第一次。 - Wingzero
@Wingzero 在我的项目中,第五次是正确的,那么我该如何忽略其他的呢? - Elijah_Lam
@Elijah_Lam 我认为你无法控制这个系统行为。只要iOS认为视图需要布局,它就会触发它。 - Wingzero
@Wingzero,你最终的解决方案是什么? - Elijah_Lam
显示剩余3条评论

59
我认为在您调用此函数时,自动布局还没有对您的视图进行布局。因为当调用viewDidLoad时,自动布局尚未发生。这是因为该函数在视图加载后立即调用,只有在此之后,视图才会被放置到视图控制器的视图层次结构中,并最终布局(在视图的layoutSubviews方法中)。
注:本回答指出问题的情况为何不起作用。@dreamzor's answer指出了您需要将代码放置在哪里以解决这个问题。

1
不,viewDidLoad 在自动布局之前发生。自动布局会在将视图添加到另一个视图后才惰性地发生(这通常是在调用 viewWillAppear 之后不久,但可能不可靠地在 viewDidAppear 之前)。唯一可靠的方法是知道何时在视图控制器的视图上完成了 layoutSubviews - Jesper
1
你为什么要获取框架呢?如果你想基于它进行计算并自己进行布局,那么你可能最终会与自动布局作斗争。 - Jesper
1
我试图计算可以适合UITextView的字母数量,以便将其剪辑并赋予“…”的外观。 - user1046037
谢谢Jasper,但使用UILabel的唯一问题是,当UILabel中只有一行文本时,文本显示在垂直居中(高度/2)而不是标签顶部。 - user1046037
非常感谢您的帮助。为了完整起见,请您更新您的答案,包括“框架宽度为零的原因是因为viewDidLoad发生在自动布局之前”。这样我就可以将其标记为已回答。 - user1046037
显示剩余4条评论

35

在你的viewDidLoad()函数中加入

self.view.setNeedsLayout()
self.view.layoutIfNeeded()

访问yourview.frame.size.width之前


我认为这是最好的答案! - Murat Yasar

31

解决方案

  • 将代码放置在viewDidAppear方法中

原因

  • viewDidLoad方法发生在自动布局完成之前,所以自动布局在xib中指定的位置尚未设置
  • viewDidAppear方法发生在自动布局完成之后。

1
我将代码放在viewDidAppear中,但是我的视图的frame NSLog仍然显示为零。 - dreamzor
实际上,在iOS 6.0中,viewDidAppear在viewDidLayoutSubviews之前被调用,所以dreamzor是正确的。 - vedrano
5
根据我的测试,在iOS 6中,我在第二个控制器中使用了导航控制器。在viewDidLayoutSubviews之前,该方法被调用,但在viewDidLayoutSubviews中,子视图的宽度仍然为零。只有在viewDidAppear中,子视图的宽度才似乎具有正确的值。 - user1046037

13

实际上,在viewDidLoad中我的代码执行之前,我成功地强制更新了布局:

override func viewDidLoad() {
        super.viewDidLoad()

        println("bounds before \(self.previewContainer.bounds)");
        //on iPhone 6 plus -> prints bounds before (0.0,0.0,320.0,320.0)

        self.view.setNeedsLayout()
        self.view.layoutIfNeeded()

        println("bounds after \(self.previewContainer.bounds)");
        //on iPhone 6 plus -> prints bounds after (0.0,0.0,414.0,414.0)

        //Size dependent code works here
        self.create()
    }

更新:这似乎不再起作用了


10

这是一个非常奇怪的特性,但我发现:

如果您想在不使用layoutsubviews方法的情况下获取框架,请使用以下方法:

dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"View frame: %@", NSStringFromCGRect(view.frame));
    });

这真的很奇怪!!!


7

以上答案都不能完全解决我的问题,除了viewDidLoad,但这会导致视图动画展示之后才显示我想要的内容,这看起来不太好。

viewDidLayoutSubviews是正确运行依赖于自动布局完成的代码的地方,但正如其他人指出的那样,在最近的iOS版本中会多次调用它,你无法知道哪一个是最终调用。

所以我用了一个小技巧来解决这个问题。在我的故事板中,mySubview应该比其包含的self.view小。但是当第一次调用viewDidLayoutSubviews时,mySubview仍然有600个单位的宽度,而self.view似乎设置正确(这是一个iPhone项目)。所以我只需要监视后续调用并检查相对宽度。一旦mySubviewself.view小,我就可以确定它已经被正确布局了。

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if self.mySubview.bounds.size.width < self.view.bounds.size.width {

        // mySubview's width became less than the view's width, so it is
        // safe to assume it is now laid out correctly...

    }
}

这样做的好处是不依赖硬编码数字,因此可以适用于所有iPhone形态,例如。当然,在某些情况或某些设备上可能并非万能药,但可能有许多巧妙的方法来进行类似的相对大小检查。
不,我们不应该这样做,但在Apple提供更可靠的回调之前,我们都被困在这里。

3

只有在第一次布局后才能确定大小。或者在获取视图的实际宽度之后,只需调用下面的方法即可。

[yourView layoutIfNeeded];

0

我同意"user1046037"的解决方案。

如果您同时使用自动布局指南和代码,请最好将框架(或NSlayoutConstraints)编程放在"ViewDidAppear"中,特别是当您为多个设备设计UI时。

根据我的测试,iOS14将在“ViewDidLoad”之前从屏幕(默认iPhone 11 Pro Max)中获取自动布局,并在“ViewDidAppear”中更新它。

对于不熟悉视图生命周期的人,它的顺序是“从Storyboard加载视图” ->“ViewDidLoad” ->“ViewWillAppear” ->“ViewDidAppear”。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接