[本文翻译自hackingwithswift,点击链接阅读原文]
当点击菜单项时,我们希望引入一个显示更多信息的详细信息视图。我们已经将ContentView
放在导航堆栈中,所以现在我们可以使用一种名为NavigationLink
的新视图类型。我们需要给它一个目的地——它应该显示什么样的东西——以及在屏幕上显示的链接。
在实践中,这看起来就像我们迄今为止使用过的所有其他容器一样,所以让我们用一个整洁的快捷方式来试试:虽然我们将在一分钟内显示一个详细视图,但我们可以使用常规文本视图作为占位符。
因此,将此直接放在ContentView.swift中的ItemRow
代码周围:
NavigationLink {
Text(item.name)
} label: {
// existing contents…
}
这意味着整行是一个导航链接,其中包含项目名称的目的地。
如果您现在运行该应用程序,您将看到两个重要的区别:
我们所有的行现在在右侧边缘都有一个灰色的披露指示器,因为SwiftUI默认为我们提供了正确的行为。
当您点击任何项目时,一个新的屏幕将滑入,说出您选择的任何项目的名称。
能够像这样呈现文本视图是构建用户界面时的绝佳节省时间!
当然,我们想要更多——我们想要一个漂亮的大局,一些关于食物的细节,等等。因此,按Cmd+N来制作另一个新的SwiftUI视图,这次称为ItemDetail.swift。
与ItemRow
一样,这需要将菜单项传递并存储为属性,因此现在将此添加到ItemDetail
:
let item: MenuItem
我们还需要更新其预览代码来使用我们的示例项目,这样我们就可以看到我们正在做什么:
static var previews: some View {
ItemDetail(item: MenuItem.example)
}
与我们的列表行一样,我们将从简单和迭代开始,直到我们得到一些效果好的东西。
首先,我们的ItemDetail
视图的简单版本,其中包含项目的图像和描述,以及顶部的标题:
struct ItemDetail : View {
let item: MenuItem
var body: some View {
VStack {
Image(item.mainImage)
Text(item.description)
}
.navigationTitle(item.name)
}
}
为了让您能够立即开始看到操作,让我们在ContentView.swift中更新我们的NavigationLink
,以便它显示包含所选项目的ItemDetail
视图。有两种方法可以这样做,最简单的方法就是将ItemDetail
代码放在我们有Text(item.name)
的位置,像这样:
NavigationLink {
ItemDetail(item: item)
} label: {
这有用,但在幕后,它导致SwiftUI做比你想象的更多的工作——每次它在我们的List
创建一行时,它也会创建NavigationLink
,作为其中的一部分,它还将为每个可见行创建ItemDetail
。
这远非理想,所以SwiftUI为我们提供了一个更快、更简单的替代方案:我们可以将任何Hashable
对象直接附加到NavigationLink
作为其值,然后使用navigationDestination()
修饰符告诉SwiftUI“当您被要求导航到MenuItem
时,加载具有该值的ItemDetail
视图。
这需要两个步骤。首先,我们需要将NavigationLink
代码更改为:
NavigationLink(value: item) {
ItemRow(item: item)
}
现在我们需要将此修饰符添加到List
——在navigationTitle()
它很好,但这其实并不重要:
.navigationDestination(for: MenuItem.self) { item in
ItemDetail(item: item)
}
现在,您可以随着进度运行代码,看到操作中的详细屏幕。
您不会在顶部看到标题,因为预览不知道它在导航堆栈中。为了解决这个问题,我们可以像这样更改预览:
struct ItemDetail_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
ItemDetail(item: MenuItem.example)
}
}
}
这实际上并没有改变我们的代码在运行时的作用——只是预览发生了变化。
你可以看到我们的详细视图有一些布局问题,所以让我们纠正它们。
首先,导航栏标题不应该很大,因为苹果建议仅将该样式用于用户界面中的顶级屏幕。我们可以通过添加另一个修饰符belownavigationTitlenavigationTitle()
来解决这个问题,如下所示:
.navigationBarTitleDisplayMode(.inline)
其次,虽然图像非常宽,但描述文本从边缘到边缘看起来就不太好了。我们可以通过添加像这样的padding()
修饰符来解决这个问题:
Text(item.description)
.padding()
padding()
修饰符允许我们指定想要填充的边以及使用多少,但如果没有任何参数,它将对所有边缘应用填充。它的适用量取决于上下文——正在使用什么设备等——但总体上看起来不错。
第三,我们的内容垂直居中看起来很奇怪,因为我们的眼睛习惯于信息对齐到顶部。为了解决这个问题,我们可以直接在项目描述之后使用另一个Spacer()
:
Image(item.mainImage)
Text(item.description)
.padding()
Spacer()
这开始看起来不错了,但我们也需要想办法展示给我们食物拍照的人的名字。我们可以把它放在图片下方或警报内,但更好的主意是放在图像的右下角。
您已经遇到了水平和垂直堆栈(HStack
和VStack
),但SwiftUI为我们提供了第三个名为ZStack
的选项来处理重叠视图。要在这里使用一个,请用这个替换我们现有的图像:
ZStack {
Image(item.mainImage)
Text("Photo: \(item.photoCredit)")
}
这创建了图像,然后在上面叠加了一些文本。很有可能你很难看到那个文本,所以让我们应用一些修饰符来让它更清晰:
Text("Photo: \(item.photoCredit)")
.padding(4)
.background(.black)
.font(.caption)
.foregroundStyle(.white)
提示:如果您交换padding()
和background()
修饰符的顺序,结果会有所不同。秩序很重要!
它现在更明显了,但这只是意味着我们可以看到它看起来不是很好——它不应该真的在我们的食物上!
为了解决这个问题,我们可以在我们的ZStack
中添加一些对齐方式,以便标签在右下角:
ZStack(alignment: .bottomTrailing) {
我们甚至可以对文本应用一些自定义偏移量,将其拉起,从边缘稍微离开:
Text("Photo: \(item.photoCredit)")
.padding(4)
.background(.black)
.font(.caption)
.foregroundStyle(.white)
.offset(x: -5, y: -5)
不错!
还有另一个布局问题,但根据您的Xcode配置,您可能还没有注意到它:我们用户界面的某些部分挂在屏幕上!
到目前为止,我一直在用iPhone 14 Pro Max设备做画布,效果很好,因为它有一个巨大的屏幕。然而,如果我换成小型设备——例如iPhone SE(转到产品>目的地>iPhone SE(第3代))——屏幕要小得多,现在您应该会看到照片信用区域现在从屏幕的右边缘运行。
发生这种情况是因为SwiftUI默认以自然尺寸显示图像,这意味着它们在屏幕上的宽度和高度与像素相同。我们的主要图像对于iPhone SE屏幕来说太大了,因此SwiftUI不是把它挤在屏幕外溢出,而是让它溢出——图像会挂起来,这样做允许其他一切都在增长。
为了解决这个问题,我们需要为我们的图像添加两个新的修饰符:一个是使图像调整大小,一个是使其缩放以适应可用空间。
因此,将您的图像修改为:
Image(item.mainImage)
.resizable()
.scaledToFit()
有了这个小小的改变,我们的图像将在所有iPhone屏幕尺寸上从边缘到边缘运行,这要好得多。除了scaledToFit()
还有一个scaledToFill()
修饰符——前者将确保整个图像可见,即使这意味着留出一点空的空间,而后者永远不会留下任何空位,即使这意味着裁剪一些图片。两者都会自动保留它们所应用的图像的自然宽高比。
评论区