我正在关注Apple系列文章中的第一篇教程,其中介绍了如何在SwiftUI应用程序中创建和合并视图。
在本教程第6节的第8步中,我们需要插入以下代码:
MapView()
.edgesIgnoringSafeArea(.top)
.frame(height: 300)
Run Code Online (Sandbox Code Playgroud)
生成以下UI:

现在,我注意到在将代码中的修饰符顺序切换为以下方式时:
MapView()
.frame(height: 300) // height set first
.edgesIgnoringSafeArea(.top)
Run Code Online (Sandbox Code Playgroud)

... Hello World标签和地图之间有多余的空间。
为什么修饰符的顺序在这里很重要,我怎么知道它何时重要?
rob*_*off 21
文字墙传入
最好不要将修饰符视为对的修改MapView。取而代之的是,MapView().edgesIgnoringSafeArea(.top)将SafeAreaIgnoringView其返回body为MapView,并根据其自身的顶部边缘是否位于安全区域的顶部边缘,将其布局不同。您应该这样想,因为这实际上是它的作用。
你怎么能确定我说的是实话?将此代码放入您的application(_:didFinishLaunchingWithOptions:)方法中:
let mapView = MapView()
let safeAreaIgnoringView = mapView.edgesIgnoringSafeArea(.top)
let framedView = safeAreaIgnoringView.frame(height: 300)
print("framedView = \(framedView)")
Run Code Online (Sandbox Code Playgroud)
现在,单击选项mapView以查看其推断的类型,该类型为plain MapView。
接下来,单击选项safeAreaIgnoringView以查看其推断的类型。其类型为_ModifiedContent<MapView, _SafeAreaIgnoringLayout>。_ModifiedContent是SwiftUI的实现细节,View当其第一个通用参数(命名为Content)符合时,它就符合View。在这种情况下,它Content是MapView,因此这_ModifiedContent也是一个View。
接下来,单击选项framedView以查看其推断的类型。其类型为_ModifiedContent<_ModifiedContent<MapView, _SafeAreaIgnoringLayout>, _FrameLayout>。
因此,您可以看到,在类型级别上,framedView是一个内容类型为的视图safeAreaIgnoringView,并且safeAreaIgnoringView是一个内容类型为的视图mapView。
但是这些仅仅是类型,类型的嵌套结构可能不会在运行时在实际数据中表示出来,对吗?运行该应用程序(在模拟器或设备上),然后查看print语句的输出:
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)
)
)
)
)
Run Code Online (Sandbox Code Playgroud)
我对输出进行了重新格式化,因为Swift将其打印在一行上,这使得它很难理解。
无论如何,我们可以看到实际上framedView显然具有content其值为类型的属性safeAreaIgnoringView,并且该对象具有其自己content的值为a 的属性MapView。
因此,当您将修饰符应用于时View,您实际上并没有在修改视图。您正在创建一个新的 View,其body/ content是原来的View。
现在我们了解了修饰符的作用(它们构造了wrapper View),我们可以对这两个修饰符(edgesIgnoringSafeAreas和frame)如何影响布局做出合理的猜测。
在某个时候,SwiftUI遍历树以计算每个视图的框架。它以屏幕的安全区域作为顶层框架开始ContentView。然后,它访问ContentView的主体(在第一个教程中)为VStack。对于a VStack,SwiftUI VStack在堆栈的子级中将的框架划分为3,_ModifiedContent后跟一个Spacer。SwiftUI会仔细查看这些子级,以算出每个子级有多少空间。第一个_ModifiedChild(最终包含MapView)具有一个300点的_FrameLayout修饰符height,因此这是将VStack'的高度分配给第一个的高度_ModifiedChild。
最终,SwiftUI找出了VStack框架的哪一部分分配给每个孩子。然后,它会拜访每个孩子以分配他们的框架并布置孩子的孩子。因此,它_ModifiedContent使用_FrameLayout修改器进行访问,将其框架设置为与安全区域的上边缘相交且高度为300点的矩形。
由于视图是一个_ModifiedContent带有300 的_FrameLayout修饰符的视图height,因此SwiftUI会检查该修饰符可以接受指定的高度。是的,因此SwiftUI不必进一步更改框架。
然后,它访问该子项的子项_ModifiedContent,到达_ModifiedContent其修饰符为_SafeAreaIgnoringLayout。它将忽略安全区域的视图的框架设置为与父视图(框架设置)相同的框架。
接下来,SwiftUI需要计算忽略安全区域的视图的子框架(MapView)。默认情况下,子级与父级获得相同的帧。但是由于此父项_ModifiedContent的修饰符为_SafeAreaIgnoringLayout,因此SwiftUI知道可能需要调整子项的框架。由于修改器的edges设置为.top,因此SwiftUI将父框架的顶部边缘与安全区域的顶部边缘进行比较。在这种情况下,它们是重合的,因此Swift 会扩展孩子的框架,使其覆盖安全区域顶部上方的屏幕区域。因此,孩子的框架延伸到父母的框架之外。
然后,SwiftUI访问MapView并为其分配上面计算出的框架,该框架从安全区域延伸到屏幕边缘。因此,MapView高度为300加上安全区域顶部边缘以外的范围。
让我们通过在忽略安全区域的视图周围绘制红色边框,在框架设置视图周围绘制蓝色边框来进行检查:
MapView()
.edgesIgnoringSafeArea(.top)
.border(Color.red, width: 2)
.frame(height: 300)
.border(Color.blue, width: 1)
Run Code Online (Sandbox Code Playgroud)
屏幕截图显示,实际上,两个_ModifiedContent视图的框架是重合的,并且没有延伸到安全区域之外。(您可能需要放大内容才能看到两个边框。)
这就是SwiftUI与教程项目中的代码一起工作的方式。现在,如果我们MapView按照您的建议在周围交换修饰符怎么办?
当SwiftUI访问的VStack子级时,就像前面的示例一样ContentView,它需要VStack在堆栈的子级中划分的垂直范围。
这次,第一个_ModifiedContent是带有_SafeAreaIgnoringLayout修饰符的。SwiftUI看到它没有特定的高度,因此它看起来是_ModifiedContent的子级(现在_ModifiedContent带有_FrameLayout修饰符)。该视图的固定高度为300点,因此SwiftUI现在知道忽略安全区域的_ModifiedContent高度应该为300点。因此,SwiftUI将扩展范围的前300个点授予VStack堆栈的第一个孩子(忽略安全区域_ModifiedContent)。
之后,SwiftUI访问第一个孩子,以分配其实际框架并布置其孩子。因此,SwiftUI将忽略安全区域_ModifiedContent的框架设置为恰好位于安全区域的前300个点。
接下来,SwiftUI需要计算忽略安全区域_ModifiedContent的孩子的框架,即frame-setting _ModifiedContent。通常,孩子会得到与父母相同的框架。但由于父母是_ModifiedContent用的改性剂_SafeAreaIgnoringLayout,其edges是.top,SwiftUI比较父框架到安全区域的顶部边缘的顶部边缘。在此示例中,它们重合,因此SwiftUI将子框架扩展到屏幕的顶部边缘。因此,框架为300点加上安全区域顶部上方的范围。
当SwiftUI去设定孩子的框架,它看到的孩子是_ModifiedContent与的改性剂_FrameLayout,其height是300由于框架是高度超过300点,这是不与改性剂兼容,因此SwiftUI被迫调整框架。它将框架高度更改为300,但最终不会以与父框架相同的框架结束。多余的范围(安全区域之外)已添加到框架的顶部,但是更改框架的高度会修改框架的底部边缘。
因此,最终效果是框架移动了而不是扩展了安全区域上方的范围。框架设置_ModifiedContent得到的框架覆盖了屏幕的前300个点,而不是安全区域的前300个点。
然后,SwiftUI访问框架设置视图的子项,即MapView,并为其提供相同的框架。
我们可以使用相同的边框绘制技术进行检查:
if false {
// Original tutorial modifier order
MapView()
.edgesIgnoringSafeArea(.top)
.border(Color.red, width: 2)
.frame(height: 300)
.border(Color.blue, width: 1)
} else {
// LinusGeffarth's reversed modifier order
MapView()
.frame(height: 300)
.border(Color.red, width: 2)
.edgesIgnoringSafeArea(.top)
.border(Color.blue, width: 1)
}
Run Code Online (Sandbox Code Playgroud)
在这里,我们可以看到忽略安全区域_ModifiedContent(这次带有蓝色边框)的框架与原始代码中的框架相同:它始于安全区域的顶部。但是我们也可以看到,现在框架设置的框架_ModifiedContent(这次带有红色边框)从屏幕的顶部边缘开始,而不是安全区域的顶部边缘,并且框架的底部边缘也已经上移幅度相同。
| 归档时间: |
|
| 查看次数: |
2439 次 |
| 最近记录: |