大多数浏览器和
Developer App 均支持流媒体播放。
-
为 Swift Playgrounds 创作引人入胜的内容
了解如何制作专用于 Swift Playgrounds 的引导式说明内容。和我们一起探索如何为已完成的示例代码项目添加指南。我们将演示如何添加任务到您的学习中心,以展示相关代码和可选的实验任务,鼓励学习者以自己的代码扩展该项目。
资源
相关视频
WWDC22
-
下载
♪ ♪
Stephanie Angulo: 大家好 我是 Stephanie Angulo Marcus Jackson: 我是 Marcus Jackson Stephanie: 我们是 Swift Playgrounds Content 团队的软件工程师 今天我们将向您展示 如何运用工具 为 Swift Playgrounds 创建 引人入胜的内容 Swift Playgrounds 4 在 iPad 和 Mac 上引入了 App 开发 这是了解如何为 App Store 构建 App 的最佳方式 我们的团队发布了 许多教程和示例代码产品 可帮助您了解 App 开发的基础知识 我们涵盖的主题包括 使用可观察数据模型 构建动态 SwiftUI App 使用有趣的 SwiftUI 动画 和形状自定义视图 以及更进阶的主题 如异步数据获取 在今天的讲座中 我们将概述 新的教学系统 使用项目的指南模块编写内容 并通过演练和实验任务 构建沉浸的学习体验 让我们开始吧 想象一个学习者正在完成我们的教程 “Keep Going with Apps” 他们最终会得到 一个名为 Emoji App 的 App 在这个 App 中 他们可以在列表中 跟踪所有他们喜欢的动物 改变它们的颜色和大小 然后点击动物 观看 它们在 Creature Dance 视图中的律动
这种舞蹈视角很有趣 但我希望这种氛围 更像是一场真正的舞会 因此 我们来继续向学习者 呈现一些的额外功能
在这里 我在 Creature Dance 视图中添加了更多代码 每个派对都需要舞池 因此我制作了一个 10 x 10 的网格 并将其设置为视图的背景
并且网格中的每个图块 都使用自定义视图修改器 随机更新其颜色 看起来很赞吧
我还想确保 我最喜欢的动物 可以在没有我们帮助的情况下跳舞 所以我做了一些自定义修改器 来帮助制作动物缩放 位置偏移和旋转的动画
在所有这些自定义修改器中 这些动画都设置为 repeatForever 这意味着我们的动物 可以在全新的舞池里整夜跳舞
最后 为了真正点亮舞池 我在视图顶部添加了 一个带动画的迪斯科球
加上这最后一步润色 好像真的能让它们齐聚一堂 参加最终的舞会 我对这个项目做了相当多的修改 但还未深入到 自定义视图修改器的更多细节 那么我们应该如何 向我们的学习者解释这一点呢 您可以指导一个初学者 阅读 Apple 的文档 但是现在您也可以 选择在 Swift Playgrounds 中 教授这些概念以及您的项目代码 我们的团队构建了这个新的教学系统 旨在帮助像您这样的作者 为您的学习者创造 引人入胜的应用内体验 今天我们将向您介绍如何在 Swift Playgrounds 4 中 为这个 App 构建学习内容 让我先睹为快 看看您将能学到什么 当学习者第一次在 Swift Playgrounds 上打开您的内容时 您可以通过可选的欢迎消息 向他们介绍该项目 如我们的朋友 Byte 所示 欢迎信息位于屏幕左侧 项目源代码编辑器的顶部 而屏幕右侧是学习中心
学习中心是一个指定区域 您可以在其中添加 向学习者描述内容的图像和说明文本
在我们的欢迎信息和学习中心中 我们让学习者知道这个项目 将在 SwiftUI 颜色 形状和动画的帮助下提高学习效率
学习中心还包含一个任务部分 任务是作者可以编写 以帮助指导学习者的编码目标 它们是内容的基本构建块
通过点击学习中心的任务按钮 我们的教学系统 将打开一个 Swift 文件 并在该文件的顶部呈现一张 带有学习材料的卡片 此卡片可以包含一系列带有文本 图像和代码片段的页面 稍后 Marcus 将介绍两种任务类型 演练和实验
在高级别中 这就是我们的教学系统必须提供的 通过正确的提示文本和正确的任务 您可以为学习者 打造引人入胜的教育体验 现在为了开始创建您自己的内容 我们首先需要谈谈指南模块 默认情况下 swiftpm 项目的文件结构 将其所有源代码保留在其根目录下 为了升级您的项目 以利用教学系统 您需要更改其文件结构 我们首先需要创建一个 App 模块 创建完成后 我们需要将项目的 所有源代码和资产 移入其中 Package.swift 文件应该留在 我们项目的根目录下
然后我们需要创建一个指南模块 该模块应与 App 模块 和 Package.swift 文件处于同一层级 在指南模块中 您需要一个指南文件 该文件将包含您学习内容的所有提示文本 我已经开始写我的指南文件了 让我们看看到目前为止我写的内容
指南文件包含 指令和 markdown 的组合 指令是 markdown 的扩展 它可以将原始类型作为属性 例如字符串 以及更复杂的类型 例如 markdown 元素和其他指令
指令可以充当其他指令的容器 但它们也可以代表 我们教学系统中的 UI 元素 首先在指南文件中 我添加了必要的指南指令 该指令包围了整个文件 它充当我们所有指令的主要容器 它的参数包括标题 图标和背景图像 以及打开项目时首先要打开的文件 在指南指令下 我添加了欢迎信息指令 欢迎信息是可选的 如前所述 它们会在学习者 第一次打开项目时呈现给他们 在欢迎信息指令下方 我添加了一个 围绕步骤指令的指南指令 指南指令充当您步骤的容器 并且步骤映射到 您在学习中心和任务中显示的内容 要开始在您的学习中心 添加图像和说明文本 您需要在步骤中 包含一个 ContentAndMedia 指令
所以我通过添加一个舞池 一个吸引人的欢迎信息 和学习中心的提示文本来开始这个派对 Marcus 您想让这个派对继续下去吗 Marcus: 当然 对于我们的这些生物来说 这是多么令人兴奋的舞池啊 虽然这种效果真的很酷 但我认为对于仍在学习的人来说 可能有点太多了 为了帮助解释这段代码 我们可以使用演练任务 让我们从单页(one-page)演练开始 稍后我将向您展示如何填写其余部分 Stephanie 已经向您 展示了指南的开头 以及有用的欢迎信息 我们已经有了创建任务 所需的第一个指令 即 step 指令 step 指令是我们的演练内容 所在的位置 要创建一个步骤 您需要用另外两个指令填充它 在这里 我们已经添加了 内容和媒体指令 该指令包含进入右侧 学习中心的 markdown 该指令的主体可以包含 任何形式的 markdown 文本 这是放置更长的提示文本 和更大图像的地方 可能有助于覆盖您的主题 这是 Playgrounds 中显示的 内容和媒体指令 虽然在此示例中该区域看起来很小 但此视图可以进一步向下延伸 并包含在滚动视图中 这使它成为编写较长的提示文本 和显示复杂内容的好地方 例如图表 编写好内容和媒体后 我们可以添加 第二个必需的指令任务 我们将任务添加到另一个 称为任务组的指令中 任务组是一个可选指令 如果您想将一组任务收集在一起 您可以将它放在步骤中 如果您的内容涵盖多个文件 或不同类型任务的相同主题 您可能会考虑这一点 在任务组中 我们可以添加一小段文字 在学习中心以副标题显示
这是任务组 在 Playgrounds 中的显示方式
现在我有了我的任务组和副标题 可以开始添加任务指令了 任务有几个参数 第一个参数是类型 这让教学系统知道 在显示此任务时要生成什么 UI 接下来 每个任务都需要一个 ID ID 是字符串 可以是任何您想要的东西 但是指南中的每个 ID 都必须是唯一的 title 参数也是一个字符串 这也可以是您想要的任何东西 并且不必是唯一的 此标题将由任务卡 UI 呈现 最后 file 参数告诉学习中心 当学习者开始这个任务时 要打开项目中的哪个文件 这是任务 在 Playgrounds 上的显示方式 标题位于按钮内 演练文件列在它的上方 现在我们编写了演练任务 我们来添加我们的第一页吧 页面指令进入任务主体 并具有以下强制性参数 ID 参数的行为就像任务的 ID 一样 因此它们对于整个指南文件来说 必须是唯一的 title 参数的行为 与任务参数的行为非常相似 但是 当您将页面上的 标题字符串留空时 这会让教学系统知道 在显示此页面时使用任务的标题 在页面内部 您可以添加任何 markdown 文本 类似于 content 和 media 指令 但是任务视图比学习中心小得多 保持文字简短 避免使用图表等复杂图像 因为它们可能让学习者难以阅读 这是我们演练的第一页 由 Swift Playgrounds 呈现 我们几乎完成了第一次演练 但首先我需要向您展示如何突出显示 最后一个屏幕截图中显示的代码 为此 我们需要向 CreatureDance.swift 添加一些标记 显示我的演练时 我想突出显示第一个自定义修改器 animatedScalingEffect 为了让这一行更突出 我将在代码前后的行上 添加一对注释 我们从多行注释语法开始 /* 在注释中 我们写了 #-code 破折号 演练 后面是一对括号 在括号内 我们写下要高亮显示的 页面指令的 ID 在这种情况下就是 1.modifier
现在让我们 在 Playgrounds 中进行测试 让我们打开 Emoji App 项目
当您打开项目时 您会看到左侧的 源代码编辑器和右侧的预览 源代码编辑器上方是我们的欢迎消息 我们的好友 Byte 为您概述了 需要做的学习内容 我将点击 Learn More 按钮
右侧的预览被换成了学习中心 顶部是我们在 ContentAndMedia 指令中编写的提示文本 下面是任务组 以及带有我们演练标题的按钮 在学习中心 演练被表示为按钮 按钮上面有 Byte 的 另一位朋友 Expert 的图片
点击这个按钮可以做一些事情 首先 学习中心再次换成预览版 其次 如果它尚未打开 则在源代码编辑器中 打开任务文件参数中指定的文件 第三 任务视图下拉到 源代码编辑器上方 最后 源代码编辑器突出显示 代码演练注释中标记的代码 如果内容不在屏幕上 那么源代码编辑器将滚动 直到出现需要突出显示的代码行 这就是您在 Swift Playgrounds 中 编写演练的方式 但我可以很公正地说 您可能会对多页演练的样子感到好奇 为此 我将在 Xcode 中打开项目 以填写演练的其余部分
现在在 Xcode 中打开指南文件后 我想在我的演练中 添加更多页面 我已经稍微解释了一下 视图修改器是什么 但我想进一步解释 如何构建自定义视图修改器 我会继续添加这些页面
很好 现在我们有了 自定义视图修改器的演练
我认为现在也是解释 ViewModifier 协议的好时机 这样 学习者可以根据需要 尝试制作自己的 ViewModifiers 为此 我将向我们的任务组 添加另一个演练
我们现在有一对完全充实的演练 我将切换回 iPad 看看它的外观
当打开项目时 学习中心现在有两个演练 我将从点击第一个演练开始
就像以前一样 带有视图修改器的行突出显示 我们的任务视图下拉 以解释这段代码是什么 现在我可以点击下一个按钮
源代码编辑器现在 向下滚动到修改器结构 并解释该结构的用途
再次点击下一个按钮 将移至本演练的最后一页 其中解释了有关修改器结构内的 body 方法的更多信息 在任务视图的底角 有一个标记为 Next Walkthrough 的按钮
点击它会自动开始下一个演练任务 只要有其他任务要完成 教学系统就会免费为您提供此功能 在这里 我将介绍本演练的其余部分
这就是您在 Swift Playgrounds 中 构建演练的方式 接下来 我想向您展示 如何创建一种不同类型的任务 让学习者可以尝试自己添加代码 并查看会发生什么 现在 我们有了一个很好的派对 我们的生物在跳舞 背景中有一些灯光 虽然它基本上看起来像一个夜总会 但我认为我们可以做得更好一点 我觉得给我们的生物 添加一些颜色应该会很棒 这样它们看起来就像 在小聚会的闪光灯下跳舞 但这只是我的想法 您会怎么做呢 这时实验任务就派上用场了 如果学习者感到特别好奇 或者他们想要一个独特的 App 他们可以选择添加实验代码 回到指南文件中 我们可以将我们的实验任务 添加到我们已经在处理的同一步骤中 我创建了一个新的任务组 来进行我们的实验 我将其命名为 Experiments 我给它加了一个副标题 同样还有我们第一个 实验任务的开头
实验任务和演练之间的第一个区别 是类型参数的内容 其他参数遵循与演练任务类似的约定 页面指令的工作方式 与它们在演练中的工作方式相同 但是对于实验 我们添加了一个可选参数 isAddable isAddable 参数允许实验任务 直接将代码 添加到源代码编辑器中 当 isAddable 设置为真时 一个添加按钮 会出现在 代码片段旁边的学习任务卡中 页面指令中的代码必须使用 三重反引号 markdown 语法 包装在代码块中 最好将代码块保持在十行或更少 虽然任务视图可以在需要时 显示更长的代码片段 但如果学习者不需要滚动就更好了 这是代码视图在 Playgrounds 中的显示方式 代码片段的右侧是一个添加按钮 因为 isAddable 参数设置为真 这几乎就是我们编写实验任务 所需要的一切 还记得 isAddable 参数吗 这允许实验任务将代码 添加到源代码编辑器 但我们需要告诉 Playgrounds 在代码中的哪个位置添加代码片段 在这里 我们又回到了 CreatureDance.swift 中 我希望学习者在不透明度修改器的 正下方添加颜色修改器 所以这就是我要添加 实验任务评论的地方 实验任务注释是单行的 这意味着它们要以双斜杠开头 然后我们写磅 #- learning-task 后面是一对括号 里面我们写了实验任务的 ID 现在我们拥有了 测试我们的实验任务所需的一切 再来一次 我已经把这些都写在了 我和 Stephanie 正在做的 swiftpm 项目中 让我们来看看吧 我们又回到了学习中心 这次我想专注于最底层的任务组 也就是我们的第一个实验 Byte 的另一位朋友 Blu 在教学系统中记录了实验 让我们点击实验任务吧
接下来发生的事情看起来应该很熟悉 任务视图下拉 但是这一次 任务视图包含一个代码视图 代码视图的右侧是一个添加按钮 点击它会将代码添加到源代码编辑器
现在添加了代码 我想看看对 CreatureDanceView 所做的更改 让我们开始这个派对吧
太好了 现在我们可以看到灯光照射到 我们的生物上 这真是太棒了 但我认为我们可以通过使用计时器 每隔几秒钟给这些生物 一个随机的颜色来提升一个档次 为此 我们需要添加另一个实验 所以让我们将这个项目带回 Xcode 并添加我们的新任务 在我们添加第二个实验之前 我认为在已经存在的实验中 添加一个页面是个好主意 对于一个初学者来说 添加一段代码却不知道为什么添加 或者用来做什么可能会让人困惑 为了解决这个问题 我将在我们的 代码页之前添加一个包含文本的页面
现在 我们准备添加第二个任务 同样 我希望学习者 在他们的项目中添加一些代码 所以我将添加一个解释代码的页面 然后是一个可添加的代码片段
有了这些 我们制作了一个新的内容 向学习者介绍使用 自定义视图修改器可以做的一些事情 嘿 Stephanie 您准备好 向他们展示我们的成果了吗 Stephanie: 是的 让我们开始吧
我将在我的 iPad 上打开 我们内容的最终版本 并查看我的更改和 Marcus 的更改 是如何结合在一起的 当我第一次打开这个项目时 欢迎信息就出现了 向我们介绍了 Creature Party 当我点击欢迎信息中的 Learn More 按钮时 它会为我打开学习中心 太棒了 我们的学习中心在顶部 确实有我的描述 以及 Marcus 添加的四项任务 让我们点击第一个演练
在这里 Marcus 以我的 AnimatedScalingModifier 为例 来说明如何使用自定义视图修改器
当我点击 Next Walkthrough 按钮时 第二个演练动画进入
Marcus 使用 View Modifier 协议 作为示例来描述协议的工作原理 完成第二个演练后 当我点击 Done 时 第一个实验任务会继续执行
Dancing in the Strobe Light 任务告诉我 我可以通过添加包含 colorMultiply 修改器的代码片段 来为我们的生物添加一些颜色 在添加代码片段之前 我们要提醒自己 这个舞会是什么样子的
好的 很酷 我将通过点击 Add 来添加代码片段 然后再次点击 Start the Party 以查看更改
很好 这些生物改变了颜色 我现在将完成这个实验任务 并过渡到最后一个
Switch It Up 实验任务告诉我 我可以通过 点击手势和计时器 来自定义动物的颜色 我将添加代码片段并再次开始派对
现在当我点击动物时 它们会改变颜色 好的 我将完成最后一项任务 然后返回学习中心
现在所有任务在学习中心 都标记为已完成 这意味着我们已经完成了这个示例
这就是您在 Swift Playgrounds 4 中 利用我们新内容功能的方式 我们希望您喜欢今天的讲座 我们很高兴看到 您将建立什么样的学习体验 不要忘记查看 其他 Swift Playgrounds 讲座哦 在 Swift Playgrounds 中 构建您的第一个 App 吧 请享受余下的 WWDC 之旅吧 Marcus: 如果您不介意的话 我们现在要去参加一个派对啦
-
-
1:27 - Dance Floor
let numOfTiles = 100 let squareLength = 150.0 // Dance floor ForEach(0 ..< numOfTiles, id: \.self) { index in let i: CGFloat = CGFloat(index / 10) let j: CGFloat = CGFloat(index % 10) let x = (squareLength * i) - (squareLength) let y = (squareLength * j) - (squareLength * 2) Rectangle() .frame(width: squareLength, height: squareLength) .border(.black, width: 3) .position(x: x, y: y) .randomizedColorEffect(startAnimation: startParty) } .blur(radius: 15) .opacity(startParty ? 1.0 : 0.0)
-
1:47 - Dance
ForEach(data.creatures) { creature in Text(creature.emoji) .resizableFont() .animatedScalingEffect(startAnimation: startParty) .randomizedOffsetEffect(startAnimation: startParty, x: midX * 0.6, y: midY * 0.6) .animatedRotationEffect(startAnimation: startParty) .opacity(startParty ? 1.0 : 0.0) }
-
2:08 - Disco Ball
Text("🪩") .resizableFont() .animatedRotationEffect(startAnimation: startParty) .opacity(startParty ? 1 : 0)
-
5:12 - Guidebook Directive
@GuideBook(title: "Creature Party!", icon: icon.png, background: background.png, firstFile: CreatureDance.swift) { }
-
5:28 - Welcome Message
@WelcomeMessage(title: "Welcome to Creature Party!") { In Creature Party, you'll take this app of dancing creatures to the next level with the help of colors, shapes, animations, and plenty of emoji! }
-
5:37 - Guide and Step Directives
@Guide { @Step(title: "Pump up the jams") { } } }
-
5:53 - Content and Media Directive
@ContentAndMedia { Tonight, the creatures are gonna party like it's 2022. 🐙💃🦝🕺🦦 }
-
7:15 - Task Group Directive
@TaskGroup(title: "Walkthroughs") { Here are the walkthroughs! These will help explain all of the new code. }
-
7:57 - First Walkthrough Task
@Task(type: walkthrough, id: "partyMode", title: "Setting up the Party", file: CreatureDance.swift) { }
-
8:44 - First Walkthrough Page
@Page(id: "1.modifier", title: "") { This is a [view modifier](https://developer.apple.com/documentation/swiftui/viewmodifier). Modifiers let you create unique versions of a view in SwiftUI. }
-
9:48 - Walkthrough Highlight
ForEach(data.creatures) { creature in Text(creature.emoji) .resizableFont() /*#-code-walkthrough(1.modifier)*/ .animatedScalingEffect(startAnimation: startParty) /*#-code-walkthrough(1.modifier)*/ .randomizedOffsetEffect(startAnimation: startParty, x: midX * 0.6, y: midY * 0.6) .animatedRotationEffect(startAnimation: startParty) .opacity(startParty ? 1.0 : 0.0) }
-
11:56 - First Walkthrough extra pages
@Page(id: "1.struct", title: "") { Custom view modifiers are structures that contain code for explaining how to modify whatever view the given modifier is attached to. } @Page(id: "1.body", title: "") { The body method allows you to add custom view modifications. For example, here you're adding a scaling animation that grows and shrinks the `Creature` over a certain period of time. }
-
12:18 - Second Walkthrough Task
@Task(type: walkthrough, id: "protocol", title: "A Little More on Protocols", file: CreatureDance.swift) { @Page(id: "2.protocol", title: "") { All custom view modifiers implement the `ViewModifier` protocol. } @Page(id: "2.body", title: "") { The `ViewModifier` protocol requires all structures that implement it to write the `body(content:)` method. } @Page(id: "2.usage", title: "") { After you've written content for your `body(content:)` method, you can use it on any view you want. Here you'll use it on each `Creature` to add a rotation animation. } }
-
14:21 - First Experiment Task
@TaskGroup(title: "Experiments") { Time to set this party off! You can use experiments to add some extra pazazz to the dance floor. @Task(type: experiment, id: "colors", title: "Dancing in the Strobe Light", file: CreatureDance.swift) { } }
-
14:48 - First Experiment Page
@Page(id: "3.code", title: "", isAddable: true) { ``` .colorMultiply(creatureColor) ``` }
-
15:55 - Experiment Task Comment
ForEach(data.creatures) { creature in Text(creature.emoji) .resizableFont() /*#-code-walkthrough(1.modifier)*/ .animatedScalingEffect(startAnimation: startParty) /*#-code-walkthrough(1.modifier)*/ .randomizedOffsetEffect(startAnimation: startParty, x: midX * 0.6, y: midY * 0.6) /*#-code-walkthrough(2.usage)*/ .animatedRotationEffect(startAnimation: startParty) /*#-code-walkthrough(2.usage)*/ .opacity(startParty ? 1.0 : 0.0) //#-learning-task(colors) }
-
17:42 - Experiment Text
@Page(id: "3.lights", title: "") { Next, add some colors to the creatures so it looks like they're dancing under the lights! }
-
17:55 - Second Experiment Task
@Task(type: experiment, id: "timer", title: "Switch it Up", file: CreatureDance.swift) { @Page(id: "4.lights", title: "") { Now that you have some colors, you can add some code to change the color of the creatures using a timer. Let's add one! } @Page(id: "4.code", title: "", isAddable: true) { ``` .onTapGesture { if let timer = timer { timer.invalidate() self.timer = nil } else { creatureColor = Color.randomColor timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true, block: { timer in creatureColor = Color.randomColor }) } } ``` } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。