SwiftUI视图中修改器的顺序会影响视图外观

时间:2019-06-04 20:19:25

标签: swift swiftui

我将遵循Apple系列文章中的first tutorial,介绍如何在SwiftUI应用程序中创建和合并视图。
在本教程第6节的第8步中,我们需要插入以下代码:

MapView()
    .edgesIgnoringSafeArea(.top)
    .frame(height: 300)

产生以下用户界面:

现在,我注意到将代码中的修饰符的顺序切换为以下方式:

MapView()
    .frame(height: 300) // height set first
    .edgesIgnoringSafeArea(.top)

... Hello World 标签和地图之间有多余的空间。

问题

为什么修饰符的顺序在这里很重要,我怎么知道什么时候很重要?

3 个答案:

答案 0 :(得分:16)

收到的文字墙

最好不要将修饰符视为修饰MapView。相反,将MapView().edgesIgnoringSafeArea(.top)视为返回SafeAreaIgnoringViewbody的{​​{1}},并且根据其自身的顶部边缘是否在顶部边缘,其布局会有所不同安全区域。您应该这样想,因为这就是它的实际作用。

如何确定我说的是实话?将此代码放入您的MapView方法中:

application(_:didFinishLaunchingWithOptions:)

现在,选择并单击let mapView = MapView() let safeAreaIgnoringView = mapView.edgesIgnoringSafeArea(.top) let framedView = safeAreaIgnoringView.frame(height: 300) print("framedView = \(framedView)") ,以查看其推断的类型,即普通的mapView

接下来,选择并单击MapView以查看其推断的类型。其类型为safeAreaIgnoringView_ModifiedContent<MapView, _SafeAreaIgnoringLayout>是SwiftUI的实现细节,当其第一个通用参数(名为_ModifiedContent)符合View时,它符合Content。在这种情况下,它的ViewContent,所以这个MapView也是_ModifiedContent

接下来,选择并单击View以查看其推断的类型。其类型为framedView

因此您可以看到,在类型级别上,_ModifiedContent<_ModifiedContent<MapView, _SafeAreaIgnoringLayout>, _FrameLayout>是内容类型为framedView的视图,而safeAreaIgnoringView是内容类型为{ {1}}。

但是这些仅仅是类型,类型的嵌套结构可能不会在运行时在实际数据中表示出来,对吗?运行该应用程序(在模拟器或设备上),然后查看print语句的输出:

safeAreaIgnoringView

我对输出进行了重新格式化,因为Swift将其打印在一行上,这使得它很难理解。

无论如何,我们可以看到实际上mapView显然具有framedView = _ModifiedContent< _ModifiedContent< MapView, _SafeAreaIgnoringLayout >, _FrameLayout >( content: SwiftUI._ModifiedContent< Landmarks.MapView, SwiftUI._SafeAreaIgnoringLayout >( content: Landmarks.MapView(), modifier: SwiftUI._SafeAreaIgnoringLayout( edges: SwiftUI.Edge.Set(rawValue: 1) ) ), modifier: SwiftUI._FrameLayout( width: nil, height: Optional(300.0), alignment: SwiftUI.Alignment( horizontal: SwiftUI.HorizontalAlignment( key: SwiftUI.AlignmentKey(bits: 4484726064) ), vertical: SwiftUI.VerticalAlignment( key: SwiftUI.AlignmentKey(bits: 4484726041) ) ) ) ) 属性,其值是framedView的类型,并且该对象具有自己的content属性,其属性值是safeAreaIgnoringView

因此,当您将{修饰符“应用于content时,实际上并没有修饰视图。您正在创建一个 MapView,其View / View是原始的body


现在我们了解了修饰符的作用(它们构造了包装器content s),我们可以对这两个修饰符(ViewView)如何影响布局做出合理的猜测。 / p>

在某些时候,Sw​​iftUI遍历树以计算每个视图的框架。它以屏幕的安全区域作为顶层edgesIgnoringSafeAreas的框架开始。然后,它访问frame的身体,(在第一个教程中)它是ContentView。对于ContentView,SwiftUI将VStack的帧划分为堆栈的子级,这是三个VStack,后跟一个VStack。 SwiftUI会仔细查看这些子级,以找出每个子级有多少空间。第一个_ModifiedContent(最终包含Spacer)具有_ModifiedChild修饰符,其MapView为300点,所以_FrameLayout的高度是多少被分配给第一个height

最终,SwiftUI找出VStack框架的哪一部分分配给每个子代。然后,它会拜访每个孩子以分配他们的框架并布置孩子的孩子。因此,它使用_ModifiedChild修饰符访问了VStack,将其框架设置为一个与安全区域的上边缘相交且高度为300点的矩形。

由于视图是一个_ModifiedContent,其_FrameLayout修饰符的_ModifiedContent为300,因此SwiftUI会检查该修饰符可以接受指定的高度。是的,因此SwiftUI不必进一步更改框架。

然后,它访问那个_FrameLayout的孩子,到达height,其修饰符为`_SafeAreaIgnoringLayout。它将忽略安全区域的视图的框架设置为与父(框架设置)视图相同的框架。

接下来,SwiftUI需要计算忽略安全区域的视图的子框架(_ModifiedContent)。默认情况下,子级与父级获得相同的帧。但是由于该父对象是_ModifiedContent,其修饰符为MapView,因此SwiftUI知道它可能需要调整子帧。由于修饰符的_ModifiedContent设置为_SafeAreaIgnoringLayout,SwiftUI会将父框架的上边缘与安全区域的上边缘进行比较。在这种情况下,它们是重合的,因此Swift 扩展了孩子的边框,使其覆盖了安全区域顶部上方的屏幕区域。因此,孩子的框架延伸到父母的框架之外。

然后,SwiftUI访问edges并将其分配给上面计算的框架,该框架从安全区域延伸到屏幕边缘。因此,.top的高度为300加上安全区域顶部边缘以外的范围。

我们通过在忽略安全区域的视图周围绘制红色边框,在框架设置视图周围绘制蓝​​色边框来对此进行检查:

MapView

screen shot of original tutorial code with added borders

该屏幕截图显示,实际上,两个MapView视图的框架是重合的,并且没有延伸到安全区域之外。 (您可能需要放大内容才能看到两个边框。)


这就是SwiftUI与教程项目中的代码一起工作的方式。现在,如果我们按照您的建议在MapView() .edgesIgnoringSafeArea(.top) .border(Color.red, width: 2) .frame(height: 300) .border(Color.blue, width: 1) 上交换修饰符怎么办?

SwiftUI访问_ModifiedContent的{​​{1}}子级时,它需要在堆栈的子级中划分MapView的垂直范围,就像前面的示例一样。

这次,第一个VStack是带有ContentView修饰符的那个。 SwiftUI看到它没有特定的高度,因此它看起来是VStack的子级,现在是带有_ModifiedContent修饰符的_SafeAreaIgnoringLayout。该视图的固定高度为300点,因此SwiftUI现在知道忽略安全区域的_ModifiedContent应该为300点高。因此,SwiftUI将_ModifiedContent范围的前300个点授予堆栈的第一个孩子(忽略安全区域的_FrameLayout)。

稍后,SwiftUI会访问第一个孩子,以分配其实际框架并布置其孩子。因此,SwiftUI将忽略安全区域的_ModifiedContent的框架设置为恰好位于安全区域的前300个点。

接下来,SwiftUI需要计算忽略安全区域VStack的孩子的框架,即框架设置_ModifiedContent。通常,孩子会得到与父母相同的框架。但是由于父级是_ModifiedContent,修饰符为_ModifiedContent,其_ModifiedContent_ModifiedContent,因此SwiftUI会将父级框架的顶部边缘与安全区域的顶部边缘进行比较。在此示例中,它们重合,因此SwiftUI将子框架扩展到屏幕的顶部边缘。因此,框架为300点加上安全区域顶部上方的范围。

当SwiftUI设置子框架时,它会看到子是_SafeAreaIgnoringLayout,其修饰符为edges,其.top为300。由于框架大于300点高,它与修改器不兼容,因此SwiftUI被迫调整框架。它将帧高度更改为300,但是它最终没有与父帧相同的帧。额外的范围(安全区域之外)已添加到框架的顶部,但是更改框架的高度会修改框架的底部边缘。

因此,最终效果是将框架移动 而不是扩展到安全区域上方的程度。框架设置_ModifiedContent获得的框架覆盖了屏幕的前300个点,而不是安全区域的前300个点。

SwiftUI然后访问框架设置视图的子级_FrameLayout,并为其赋予相同的框架。

我们可以使用相同的边框绘制技术进行检查:

height

screen shot of modified tutorial code with added borders

在这里,我们可以看到忽略安全区域的_ModifiedContent(这次带有蓝色边框)与原始代码中的框架相同:它从安全区域的顶部开始。但是我们也可以看到,框架设置MapView的框架(这次带有红色边框)开始于屏幕的顶部边缘,而不是安全区域的顶部边缘,以及屏幕底部的边缘。框架也已经向上移动了相同程度。

答案 1 :(得分:8)

是的。是的在SwiftUI Essentials会话中,Apple试图尽可能简单地解释这一点。

enter image description here

更改顺序后-

enter image description here

答案 2 :(得分:3)

将这些修饰符视为转换视图的函数。在该教程中:

  

要自定义SwiftUI视图,请调用称为修饰符的方法。修改器包装视图以更改其显示或其他属性。每个修饰符都会返回一个新视图,因此通常将多个修饰符垂直堆叠在一起。

顺序很重要。

以下结果将是什么?

  1. 拿张纸
  2. 在边缘周围绘制边框
  3. 切出一个圆圈

对:

  1. 拿张纸
  2. 切出一个圆圈
  3. 在边缘周围绘制边框