可怕的是,layoutIfNeeded 序列在 iPad 和 iPhone 上的行为不同;怎么修?
Terrifyingly, layoutIfNeeded sequence acts differently on iPad versus iPhone; how to fix?
启动 Xcode 并为清楚起见只构建 9.3,通用应用程序。因此,将 9.3 iPads 与 9.3 iPhones 进行比较。为模拟器和设备构建 - 两者都有问题。
应用程序在所有四个方向上旋转。
有一个典型的情况,你会做这样的事情......
@IBOutlet weak var doorHeightPerScreen: NSLayoutConstraint!
var heightFraction:CGFloat = 0.6
{
didSet
{
if ( heightFraction > maxHeight ) { heightFraction = maxHeight }
if ( heightFraction < minHeight ) { heightFraction = minHeight }
let h = view.bounds.size.height
spaceshipHeightPerScreen.constant = h * heightFraction
self.view.layoutIfNeeded() // holy! read on....
}
}
注意更改约束后的 layoutIfNeeded()
。
继续典型的例子,你会得到类似
的东西
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
heightFraction = (heightFraction)
// use "autolayout power" for perfection every pass.
// now that basic height/position is set,
save/load reactive positions...
position detail stuff...
}
检查一下...我整天都在这样做,只是碰巧使用了 iPhones。
有趣的是,您不需要 layoutIfNeeded 调用。
@IBOutlet weak var doorHeightPerScreen: NSLayoutConstraint!
var heightFraction:CGFloat = 0.6
{
didSet
{
if ( heightFraction > maxHeight ) { heightFraction = maxHeight }
if ( heightFraction < minHeight ) { heightFraction = minHeight }
let h = view.bounds.size.height
spaceshipHeightPerScreen.constant = h * heightFraction
}
}
工作正常。
然而,在一天结束的时候,我把它放在一些 iPad 上......一切都坏了!
每当您旋转 landscape/portrait 时,都会出现问题。
挠头之后,我意识到您确实需要 layoutIfNeeded 调用,在 iPad 上。这是相同的 OS.
确实,无论 OS 版本如何,都会出现这种行为。它展示了所有 iPhones / ALL iPads。卧槽!
@IBOutlet weak var doorHeightPerScreen: NSLayoutConstraint!
var heightFraction:CGFloat = 0.6
{
didSet
{
if ( heightFraction > maxHeight ) { heightFraction = maxHeight }
if ( heightFraction < minHeight ) { heightFraction = minHeight }
let h = view.bounds.size.height
spaceshipHeightPerScreen.constant = h * heightFraction
self.view.layoutIfNeeded() //MUST HAVE, IN IPAD CASE!!!!!!
}
}
对我来说,令人难以置信的麻烦他们会以不同的方式工作。
我想知道的是,是否有某个设置可以使它们工作相同?莫非是我的错?
两者之间是否还有其他已知差异 - 或者确实“已知”有一些这样的错误?
我想不出我在任何地方做过什么奇怪或不寻常的事,除非整个应用程序在第一个视图中有 override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { return .All }
,这是正常的,如果你想把设备倒过来;我怀疑它的相关性。除此之外,它是一个非常“干净”的新鲜应用。
它给我一种矩阵中出现故障的感觉——太可怕了。
什么可能导致这种情况?
根据 RobM 的问题,初始 ViewController 上的 SimulatedMetrics 设置(属性选项卡)是...
应用程序的总体方案:第一个场景“一般”是全屏,设备大小。有一个相同大小的“Live”容器(使用“Trailing”等/约束为零)。在 Live 中,有一个容器视图“Quad”,它的大小确实也完全适合“Live”,所以它也是全屏的。 Quad:UIViewController 展示了我描述的问题。 Quad 包含位于视图周围的各种对象(图像、自定义控件等)。当应用程序启动时,一切正常。
在设备(或类似设备)旋转时:在更改约束之后(我不知道这是否相关):layoutIfNeeded
调用 IS 需要 iPads(所有 iPads),但 NOT 需要 iPhones(所有 iPhones)。该行为在模拟器和设备上是相同的。
另一个例子.....
我发现了另一个令人震惊的例子。
在 UICollectionView 中,自定义单元格(只是简单的静态大小的单元格)。如果您碰巧更改了约束条件(想象一下调整单元格内的图标或产品照片的大小)。
ON IPAD你DO 必须确保在 layoutIfNeeded
中重新调整,否则它 在单元格第一次出现时将无法工作 。
而 ON IPHONE 它的行为肯定不同:它会在第一次出现之前“为你做那个”单元格,如果您碰巧忽略了它。
太变态了!!
我在每个 iPad 和每个 iPhone 上进行了测试。 (此外,不寻常的行为恰好在设备或模拟器上表现出来:模拟器没有区别。)
默认的模拟指标大小是“推断的”,它(如果场景不是 segue 或关系的目标)给你一个 600x600 的视图,这与任何 iOS 设备。您在某个时候将模拟指标尺寸更改为“iPhone 5.5 英寸”,可能是为了匹配您的主要测试设备的尺寸。
从故事板(或 xib)加载视图时,它会以故事板中的大小加载。然后它可以通过它的容器调整大小(如果它是应用程序的根视图,则可以是 UIWindow
,如果它是包含的视图控制器的根视图,则可以通过它的超级视图)。
在您的情况下,听起来您的主测试设备的屏幕与情节提要中的根视图具有相同的大小,因此测试设备不必 运行 像您期望的那样多的布局.
当您使用屏幕尺寸与故事板中的根视图尺寸不同的测试设备时,测试设备必须进行更多布局。
我没有尝试重现你的问题,所以我并不是说这是对你所看到的内容的完整解释。很可能涉及 iOS 错误。尽管如此,这应该可以解释为什么您的应用程序在不同设备上的行为不同。我相信这也是 Apple 选择默认推断尺寸 600x600 的原因:因为没有设备屏幕是那个尺寸,所以所有设备都必须进行相同数量的布局。
我无法重现您所看到的;很高兴看到一个完整的例子。在我的模型中,我配置了一个视图控制器,其中包含一个具有单个子视图的视图,并带有控制子视图高度的约束。我根据视图大小更改了 viewDidLayout
中的子视图高度限制。 iPhone 和 iPad 的行为相同,并且在任何视图上都没有调用 layoutIfNeeded
。
也就是说,我 认为 一旦视图完成其布局,您就会更改子视图约束 - 是吗?我认为更好的方法是通过 viewWillTransitionToSize:withTransitionCoordinator:
.
在此之前布局您的子视图
func viewWillTransitionToSize(_ size: CGSize,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
这样视图层次结构的自动布局可以一次性完成。此方法仅在视图改变大小时调用,因此在首次加载视图时不会调用;您必须在其他地方设置初始约束 - 因为它们取决于视图大小,也许您可以使用 viewWillAppear
.
或者(可能更正确),子类化视图控制器的视图并覆盖 updateConstraints
。这是更改约束常量的最合适位置。
最后,在您的 属性 setter 中,您不应该调用 view.layoutIfNeeded()。如果有的话,您可以设置 view.setNeedsLayout() 以便在下一次运行循环迭代中发生布局,并选择可能需要表示的所有更改。
启动 Xcode 并为清楚起见只构建 9.3,通用应用程序。因此,将 9.3 iPads 与 9.3 iPhones 进行比较。为模拟器和设备构建 - 两者都有问题。
应用程序在所有四个方向上旋转。
有一个典型的情况,你会做这样的事情......
@IBOutlet weak var doorHeightPerScreen: NSLayoutConstraint!
var heightFraction:CGFloat = 0.6
{
didSet
{
if ( heightFraction > maxHeight ) { heightFraction = maxHeight }
if ( heightFraction < minHeight ) { heightFraction = minHeight }
let h = view.bounds.size.height
spaceshipHeightPerScreen.constant = h * heightFraction
self.view.layoutIfNeeded() // holy! read on....
}
}
注意更改约束后的 layoutIfNeeded()
。
继续典型的例子,你会得到类似
的东西override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
heightFraction = (heightFraction)
// use "autolayout power" for perfection every pass.
// now that basic height/position is set,
save/load reactive positions...
position detail stuff...
}
检查一下...我整天都在这样做,只是碰巧使用了 iPhones。
有趣的是,您不需要 layoutIfNeeded 调用。
@IBOutlet weak var doorHeightPerScreen: NSLayoutConstraint!
var heightFraction:CGFloat = 0.6
{
didSet
{
if ( heightFraction > maxHeight ) { heightFraction = maxHeight }
if ( heightFraction < minHeight ) { heightFraction = minHeight }
let h = view.bounds.size.height
spaceshipHeightPerScreen.constant = h * heightFraction
}
}
工作正常。
然而,在一天结束的时候,我把它放在一些 iPad 上......一切都坏了!
每当您旋转 landscape/portrait 时,都会出现问题。
挠头之后,我意识到您确实需要 layoutIfNeeded 调用,在 iPad 上。这是相同的 OS.
确实,无论 OS 版本如何,都会出现这种行为。它展示了所有 iPhones / ALL iPads。卧槽!
@IBOutlet weak var doorHeightPerScreen: NSLayoutConstraint!
var heightFraction:CGFloat = 0.6
{
didSet
{
if ( heightFraction > maxHeight ) { heightFraction = maxHeight }
if ( heightFraction < minHeight ) { heightFraction = minHeight }
let h = view.bounds.size.height
spaceshipHeightPerScreen.constant = h * heightFraction
self.view.layoutIfNeeded() //MUST HAVE, IN IPAD CASE!!!!!!
}
}
对我来说,令人难以置信的麻烦他们会以不同的方式工作。
我想知道的是,是否有某个设置可以使它们工作相同?莫非是我的错?
两者之间是否还有其他已知差异 - 或者确实“已知”有一些这样的错误?
我想不出我在任何地方做过什么奇怪或不寻常的事,除非整个应用程序在第一个视图中有 override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { return .All }
,这是正常的,如果你想把设备倒过来;我怀疑它的相关性。除此之外,它是一个非常“干净”的新鲜应用。
它给我一种矩阵中出现故障的感觉——太可怕了。
什么可能导致这种情况?
根据 RobM 的问题,初始 ViewController 上的 SimulatedMetrics 设置(属性选项卡)是...
应用程序的总体方案:第一个场景“一般”是全屏,设备大小。有一个相同大小的“Live”容器(使用“Trailing”等/约束为零)。在 Live 中,有一个容器视图“Quad”,它的大小确实也完全适合“Live”,所以它也是全屏的。 Quad:UIViewController 展示了我描述的问题。 Quad 包含位于视图周围的各种对象(图像、自定义控件等)。当应用程序启动时,一切正常。
在设备(或类似设备)旋转时:在更改约束之后(我不知道这是否相关):layoutIfNeeded
调用 IS 需要 iPads(所有 iPads),但 NOT 需要 iPhones(所有 iPhones)。该行为在模拟器和设备上是相同的。
另一个例子.....
我发现了另一个令人震惊的例子。
在 UICollectionView 中,自定义单元格(只是简单的静态大小的单元格)。如果您碰巧更改了约束条件(想象一下调整单元格内的图标或产品照片的大小)。
ON IPAD你DO 必须确保在 layoutIfNeeded
中重新调整,否则它 在单元格第一次出现时将无法工作 。
而 ON IPHONE 它的行为肯定不同:它会在第一次出现之前“为你做那个”单元格,如果您碰巧忽略了它。
太变态了!!
我在每个 iPad 和每个 iPhone 上进行了测试。 (此外,不寻常的行为恰好在设备或模拟器上表现出来:模拟器没有区别。)
默认的模拟指标大小是“推断的”,它(如果场景不是 segue 或关系的目标)给你一个 600x600 的视图,这与任何 iOS 设备。您在某个时候将模拟指标尺寸更改为“iPhone 5.5 英寸”,可能是为了匹配您的主要测试设备的尺寸。
从故事板(或 xib)加载视图时,它会以故事板中的大小加载。然后它可以通过它的容器调整大小(如果它是应用程序的根视图,则可以是 UIWindow
,如果它是包含的视图控制器的根视图,则可以通过它的超级视图)。
在您的情况下,听起来您的主测试设备的屏幕与情节提要中的根视图具有相同的大小,因此测试设备不必 运行 像您期望的那样多的布局.
当您使用屏幕尺寸与故事板中的根视图尺寸不同的测试设备时,测试设备必须进行更多布局。
我没有尝试重现你的问题,所以我并不是说这是对你所看到的内容的完整解释。很可能涉及 iOS 错误。尽管如此,这应该可以解释为什么您的应用程序在不同设备上的行为不同。我相信这也是 Apple 选择默认推断尺寸 600x600 的原因:因为没有设备屏幕是那个尺寸,所以所有设备都必须进行相同数量的布局。
我无法重现您所看到的;很高兴看到一个完整的例子。在我的模型中,我配置了一个视图控制器,其中包含一个具有单个子视图的视图,并带有控制子视图高度的约束。我根据视图大小更改了 viewDidLayout
中的子视图高度限制。 iPhone 和 iPad 的行为相同,并且在任何视图上都没有调用 layoutIfNeeded
。
也就是说,我 认为 一旦视图完成其布局,您就会更改子视图约束 - 是吗?我认为更好的方法是通过 viewWillTransitionToSize:withTransitionCoordinator:
.
func viewWillTransitionToSize(_ size: CGSize,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
这样视图层次结构的自动布局可以一次性完成。此方法仅在视图改变大小时调用,因此在首次加载视图时不会调用;您必须在其他地方设置初始约束 - 因为它们取决于视图大小,也许您可以使用 viewWillAppear
.
或者(可能更正确),子类化视图控制器的视图并覆盖 updateConstraints
。这是更改约束常量的最合适位置。
最后,在您的 属性 setter 中,您不应该调用 view.layoutIfNeeded()。如果有的话,您可以设置 view.setNeedsLayout() 以便在下一次运行循环迭代中发生布局,并选择可能需要表示的所有更改。