专栏首页韦弦的偶尔分享Hacking with iOS: SwiftUI Edition - Moonshot 项目(二)

Hacking with iOS: SwiftUI Edition - Moonshot 项目(二)

moon

使用ScrollView和GeometryReader显示任务详细信息

当用户从我们的主列表中选择一个阿波罗任务时,我们希望显示有关该任务的信息:其图像,任务徽章以及机组人员中的所有宇航员及其角色。前两个并不太难,但是第二个需要更多的工作,因为我们需要在两个JSON文件中将乘员ID与乘员详细信息进行匹配。

让我们从简单开始并逐步进行:创建一个名为MissionView.swift的新SwiftUI视图。最初,它只具有mission属性,以便我们可以显示任务徽章和说明,但不久之后我们将对其添加更多内容。

就布局而言,此东西需要具有一个滚动的VStack,其中带有可调整大小的任务徽章图像,然后是文本视图,然后是分隔符,以便所有内容都可以推送到屏幕顶部。我们将使用GeometryReader设置任务图像的最大宽度,经过反复试验,尽管经过反复试验,我发现任务徽章在不全宽时效果最好——宽度在50%到75%之间的位置看起来更好,避免它在屏幕上显得很大。

现在将此代码放入MissionView.swift中:

struct MissionView: View {
    let mission: Mission

    var body: some View {
        GeometryReader { geometry in
            ScrollView(.vertical) {
                VStack {
                    Image(self.mission.image)
                        .resizable()
                        .scaledToFit()
                        .frame(maxWidth: geometry.size.width * 0.7)
                        .padding(.top)

                    Text(self.mission.description)
                        .padding()

                    Spacer(minLength: 25)
                }
            }
        }
        .navigationBarTitle(Text(mission.displayName), displayMode: .inline)
    }
}

您是否注意到间隔是使用minLength:25创建的?这不是我们以前使用过的东西,但是它可以确保间隔的最小高度至少为25点。这在滚动视图内部很有用,因为总的可用高度是灵活的:间隔符通常会占用所有可用的剩余空间,但是在滚动视图内部没有意义。

我们可以使用Spacer().frame(minHeight:25)来达到相同的结果,但是使用Spacer(minLength:25)的好处是,如果您改变了堆栈方向——如果从VStack转到HStack,那么它实际上变成了Spacer().frame(minWidth:25)

无论如何,有了我们的新视图后,代码将不再生成,这完全是因为它下面的预览结构——该东西需要传入一个Mission对象,以便它可以渲染。幸运的是,我们的Bundle扩展程序在这里也可用:

struct MissionView_Previews: PreviewProvider {
    static let missions: [Mission] = Bundle.main.decode("missions.json")

    static var previews: some View {
        MissionView(mission: missions.randomElement()!)
    }
}

如果您在预览中查看,将会发现这是一个不错的开始,但是下一部分比较棘手:我们要在说明下方显示参与任务的宇航员列表。接下来解决这个问题...

使用 first(where:) 合并Codable结构体

在我们的任务描述下方,我们想显示每个机组人员的图片,姓名和角色,这说起来容易做起来难。

这里的复杂性在于,我们的JSON分两部分提供:missions.jsonastronauts.json。这消除了我们的数据重复,因为一些宇航员参加了多次任务,但这也意味着我们需要编写一些代码以将我们的数据连接在一起——例如,将“armstrong”解析为“Neil A. Armstrong””。您会看到,一方面,我们执行的任务知道机组人员“armstrong”扮演的角色是“指挥官(Commander)”,但不知道谁是“ armstrong”,而另一方面,我们的任务是“Neil A. Armstrong”及其描述他,但不知道他是阿波罗11号的指挥官。

因此,我们需要做的是使我们的MissionView接受获得的任务以及完整的宇航员阵容,然后确定哪些宇航员实际上参与了发射。由于此合并数据只是暂时的,因此我们可以使用元组而不是结构体,但是老实说并没有太大的区别,因此我们将在这里使用新的结构体。

立即在MissionView中添加此嵌套结构体:

struct CrewMember {
    let role: String
    let astronaut: Astronaut
}

现在到了棘手的部分:我们需要向MissionView添加一个属性,该属性存储一组CrewMember对象——这些是完全解析的角色/宇航员配对。首先,这就像添加另一个属性一样简单:

let astronauts: [CrewMember]

但是,我们如何设置该属性?好吧,想一想:如果我们将这一视图传递给它的任务以及所有宇航员,我们就可以对任务组进行遍历,然后让每个乘员对我们所有的宇航员进行遍历,以找到具有匹配ID的宇航员。当我们找到一个对象时,可以将其及其角色转换为CrewMember对象,但如果不这样做,则意味着我们以某种方式使用了无效或未知名称的组员角色。

Swift为我们提供了一个名为first(where :)的数组方法,该方法确实可以帮助完成此过程。我们可以给它一个谓词(条件的花哨词),它将发回与该谓词匹配的第一个数组元素,如果没有则返回nil。在我们的案例中,我们可以这样说:“给我第一名ID为 armstrong 的宇航员。”

这是其对应的代码:

init(mission: Mission, astronauts: [Astronaut]) {
    self.mission = mission

    var matches = [CrewMember]()

    for member in mission.crew {
        if let match = astronauts.first(where: { $0.id == member.name }) {
            matches.append(CrewMember(role: member.role, astronaut: match))
        } else {
            fatalError("Missing \(member)")
        }
    }

    self.astronauts = matches
}

输入该代码后,我们的预览结构将再次停止工作,因为它需要更多信息。因此,在该处添加第二个调用,以decode()加载所有宇航员,然后再将它们传递进来:

struct MissionView_Previews: PreviewProvider {
    static let missions: [Mission] = Bundle.main.decode("missions.json")
    static let astronauts: [Astronaut] = Bundle.main.decode("astronauts.json")

    static var previews: some View {
        MissionView(mission: missions[0], astronauts: astronauts)
    }
}

现在我们有了所有宇航员数据,我们可以使用ForEach在任务说明的正下方显示此数据。这将使用与ContentView中使用的相同的HStack / VStack组合,除了现在我们需要在HStack的末尾使用一个空格将视图向左推——以前我们是免费获得的,因为我们在List中,但是现在不是这样。我们还将使用胶囊的形状和覆盖层,为宇航员的图片添加一些额外的样式,以使其看起来更好。

MissionView中的Spacer(minLength:25)之前添加以下代码:

ForEach(self.astronauts, id: \.role) { crewMember in
    HStack {
        Image(crewMember.astronaut.id)
            .resizable()
            .frame(width: 83, height: 60)
            .clipShape(Capsule())
            .overlay(Capsule().stroke(Color.primary, lineWidth: 1))

        VStack(alignment: .leading) {
            Text(crewMember.astronaut.name)
                .font(.headline)
            Text(crewMember.role)
                .foregroundColor(.secondary)
        }

        Spacer()
    }
    .padding(.horizontal)
}

您应该在预览中看到看起来不错,但是要在模拟器中看到它,我们需要在ContentView中修改NavigationLink:现在将其Text(“ Detail View”),替换为:

NavigationLink(destination: MissionView(mission: mission, astronauts: self.astronauts)) {

现在继续在模拟器中运行该应用——它开始变得有用!

在继续之前,请尝试花费几分钟来定制宇航员的显示方式——我使用了胶囊夹的形状和覆盖层,但是您可以尝试使用圆形或圆角矩形,可以使用不同的字体或更大的图像,甚至可以添加标记任务指挥官是谁的某种方式。

解决 buttonStyle() 和 layoutPriority() 的问题

要完成此程序,我们将制作第三个也是最后一个视图以显示宇航员的详细信息,这可以通过在任务视图中点击一位宇航员来实现。这大多数情况下只是您的实践,但我确实想强调一个有趣的地方,以及如何使用名为 layoutPriority()的新修饰符解决它。

首先创建一个名为AstronautView的新SwiftUI视图。这将具有一个宇航员属性,因此它知道要显示的内容,然后使用与MissionView中类似的GeometryReader / ScrollView / VStack组合进行布局。输入以下代码:

struct AstronautView: View {
    let astronaut: Astronaut

    var body: some View {
        GeometryReader { geometry in
            ScrollView(.vertical) {
                VStack {
                    Image(self.astronaut.id)
                        .resizable()
                        .scaledToFit()
                        .frame(width: geometry.size.width)

                    Text(self.astronaut.description)
                        .padding()
                }
            }
        }
        .navigationBarTitle(Text(astronaut.name), displayMode: .inline)
    }
}

我们再次需要更新预览,以便它使用一些数据创建其视图:

struct AstronautView_Previews: PreviewProvider {
    static let astronauts: [Astronaut] = Bundle.main.decode("astronauts.json")

    static var previews: some View {
        AstronautView(astronaut: astronauts[0])
    }
}

现在,我们可以使用另一个NavigationLinkMissionView中进行演示。这需要放在ForEach内部,以便包装现有的HStack

NavigationLink(destination: AstronautView(astronaut: crewMember.astronaut)) {
    HStack {
        // current code
    }
    .padding(.horizontal)
}

立即运行该应用程序,并进行全面尝试——您应该至少看到一个错误,或者取决于SwiftUI,可能会看到两个错误。

第一个错误非常明显:在任务视图中,我们所有的宇航员图片均显示为纯蓝色胶囊,而不是其图片。您可能还会注意到,每个人的名字都用相同的蓝色阴影书写,这可能为您提供了线索–现在,这是一个导航链接,SwiftUI通过将视图涂成蓝色来使整个外观看起来很活跃。

要解决此问题,我们需要让SwiftUI将导航链接的内容呈现为一个普通按钮,这意味着它不会对图像或文本应用颜色。因此,将其作为修改器添加到MissionView中的宇航员NavigationLink中:

.buttonStyle(PlainButtonStyle())

至于第二个错误,您甚至可能根本没有看到它-在我看来这是SwiftUI本身的一个错误,因此它可能会在将来的版本中修复,或者有可能仅影响特定设备配置。因此,如果在使用与我相同的iPhone模拟器时不存在此错误,则可能已解决!

(当前版本已经修复)Bug是这样的:如果您选择某些宇航员,例如阿波罗1号的爱德华·怀特二世,您可能会看到其说明文字被裁剪到底部。因此,您只看到了一些文本,而不是看到所有文本,后面是省略号。而且,如果您仔细观察图像的顶部,您会发现它不再直接靠在顶部的导航栏上。

我们看到的是SwiftUI的布局算法,很难得出关于我们内容的正确结论。在我看来,这是一个SwiftUI错误,有可能到您自己尝试时它甚至不存在。但是它就在这里,因此,我将向您展示如何使用layoutPriority()修饰符对其进行修复。

为此,只需在AstronautView中的描述文本视图中添加layoutPriority(1),如下所示:

Text(self.astronaut.description)
    .padding()
    .layoutPriority(1)

解决了这两个错误之后,我们的程序就完成了——上一次运行它,然后尝试一下!

译自 Showing mission details with ScrollView and GeometryReader Merging Codable structs using first(where:) Fixing problems with buttonStyle() and layoutPriority()

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Swift 有效的数独 - LeetCode

    判断一个数独是否有效,根据:Sudoku Puzzles - The Rules。 (数独规则: 每一行不能有重复的数字;每一列不能有重复的数字;将数独框划分...

    韦弦zhy
  • Swift 验证回文字符串 - LeetCode

    韦弦zhy
  • SwiftUI导航栏完全指南

    NavigationView是SwiftUI应用程序最重要的组件之一,它使我们能够轻松推入和弹出屏幕,以清晰,分层的方式为用户呈现信息。在本文中,我想演示在应用...

    韦弦zhy
  • Android的Nexus搭建Maven私有仓库

    Anonymous_95975_565
  • 牛客挑战赛49 D筱玛爱线段树 线段树+树状数组计数

     存下操作, 从后往前 用树状数组维护操作的次数, 编号从大到小,防止重复调用递归。

    用户2965768
  • golang(GO语言)http详解简单基础

    因为好像长时间的写PHP可能大家感觉烦躁了,所以写一点golang的东西大家可以拿去玩玩,golang在web开发中让你得心应手,其实也是很好的东西,只要你玩进...

    李海彬
  • 如何在Ubuntu 18.04服务器上安装Python 3并设置编程环境

    Python是一种灵活的多功能编程语言,可用于许多用例,具有脚本,自动化,数据分析,机器学习和后端开发的优势。1991年首次出版,其名称灵感来自英国喜剧组织Mo...

    心语花束
  • 行进中换轮胎——万字长文解析美团和大众点评两大数据平台是怎么融合的

    背景 互联网格局复杂多变,大规模的企业合并重组不时发生。原来完全独立甚至相互竞争的两家公司,有着独立的技术体系、平台和团队,如何整合,技术和管理上的难度都很大。...

    美团技术团队
  • 案例:AWR手工创建快照失败,SYSAUX表空间剩余不足处理

    版本:Oracle 11.2.0.4 RAC 问题现象:AWR手工创建快照失败,SYSAUX表空间剩余不足。

    Alfred Zhao
  • 深度学习解决手写数字的图片识别

    本篇使用TensorFlow框架,利用MNIST手写数字数据集来演示深度学习的入门概念。其训练集共有60000个样本(图片和标签),测试集有10000个样本。手...

    用户6021899

扫码关注云+社区

领取腾讯云代金券