我将遵循Apple系列文章中的first tutorial,介绍如何在SwiftUI应用程序中创建和合并视图。
在本教程第6节的第8步中,我们需要插入以下代码:
MapView()
.edgesIgnoringSafeArea(.top)
.frame(height: 300)
产生以下用户界面:
现在,我注意到将代码中的修饰符的顺序切换为以下方式:
MapView()
.frame(height: 300) // height set first
.edgesIgnoringSafeArea(.top)
...
为什么修饰符的顺序在这里很重要,我怎么知道什么时候很重要?
答案 0 :(得分:16)
收到的文字墙
最好不要将修饰符视为修饰MapView
。相反,将MapView().edgesIgnoringSafeArea(.top)
视为返回SafeAreaIgnoringView
为body
的{{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
。在这种情况下,它的View
是Content
,所以这个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),我们可以对这两个修饰符(View
和View
)如何影响布局做出合理的猜测。 / p>
在某些时候,SwiftUI遍历树以计算每个视图的框架。它以屏幕的安全区域作为顶层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
该屏幕截图显示,实际上,两个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
在这里,我们可以看到忽略安全区域的_ModifiedContent
(这次带有蓝色边框)与原始代码中的框架相同:它从安全区域的顶部开始。但是我们也可以看到,框架设置MapView
的框架(这次带有红色边框)开始于屏幕的顶部边缘,而不是安全区域的顶部边缘,以及屏幕底部的边缘。框架也已经向上移动了相同程度。
答案 1 :(得分:8)
答案 2 :(得分:3)
将这些修饰符视为转换视图的函数。在该教程中:
要自定义SwiftUI视图,请调用称为修饰符的方法。修改器包装视图以更改其显示或其他属性。每个修饰符都会返回一个新视图,因此通常将多个修饰符垂直堆叠在一起。
顺序很重要。
以下结果将是什么?
对: