-
ClassKit 简介
全新的 ClassKit 框架让您能在 app 中整理教育内容,方便老师在新的“课业”app 中探索并分配特定的活动,以及查看学生的进度。了解如何将 ClassKit 应用到您的内容结构中、声明可分配的内容,以及分享教师在“课业”中所分配活动的学生进度。
资源
相关视频
WWDC21
-
搜索此视频…
(ClassKit简介 演讲215)
大家早上好 我叫Pavel Marin与我今天和大家 谈谈ClassKit
我们的演讲分为三部分 在第一部分中 我们将对ClassKit进行概述 我们将向你展示数据 如何在系统中流动 演讲的第二部分 我们将列出系统中所有的类 以及介绍每个类的一些有趣特性 最后 Marin将向我们展示如何在现有 app中使用ClassKit (使用ClassKit) 让我们从概述开始 (总体概述) ClassKit 是一个基于教育的框架 这意味着它被设计于学校环境中使用
它允许你在app中定义可布置的内容 以便教师可以将其作为家庭作业 布置给学生
它可以汇报 这些由老师布置的内容的进度
而且这是在考虑到 隐私的情况下完成的 我的意思是你可以在你的app中 报告任何所布置内容的进度 但老师只会看到那些 他们明确布置给学生的任务的进度
你为何应采用ClassKit呢? 首先 这将能够提供更好的学生体验 以及更优的教师工作流程 因为他们将能够确切地知道 你的app中有哪些可布置的内容 并将其布置给学生
它能让教师了解在app中 每个学生的表现如何 这使个性化学习成为可能 如果老师能够知道 学生在你的app中的表现 他们将能够更好地调整 以后的家庭作业 最后 它也给你一个竞争优势 这是因为如果教师…
当这类信息反馈给他们时 他们很可能会提倡在学校内广泛使用 你的app
说到学校 有一个叫Apple School Manager的相关技术 学校管理员和IT部门 使用它为该组织中的每个人 创建托管Apple ID 每个老师都会得到一个 托管Apple ID 所有学生也将得到 托管的Apple ID 管理员也可以使用它 在学校内创建课程 例如 在数学课上我们有一位老师 他自己有一个托管Apple ID 而学生也有各自的 托管Apple ID
这也是学校管理 所有内容和设备的地方
正如我们提到的竞争优势一样 这是学校在ClassKit功能中 启用学生进度报告的地方
此外 这也是学校批量购买 app的地方 任何启用了 ClassKit支持的app 都将在其名字旁边有一个 “Works with Schoolwork”选中标记
Schoolwork是一个 即将推出的新教育app 学生使用此app查看 分发给他们的讲义 教师用它来布置家庭作业 我们在系统中称之为讲义 讲义只是一组任务 例如 一段内容可能是 讲义中的一项任务
这也是教师查看学生 在某一份讲义中的表现 进度报告的地方
让我们来看看一个讲义的 生命周期是什么样的 (讲义的生命周期) 但在我们制作讲义之前 你的app需要能够声明 哪些内容是可布置的
在我们的框架中 这是通过使用 交叉代码CLS上下文来实现的 CLS上下文能够允许你 将你的内容表示为树结构 你的app中已经预定义了
一个主app上下文 这充当上下文树中的根上下文 而你所有的内容都只是 主app上下文的后代
所以…
我们请求你尽早…
定义你的内容 因为你越早定义你的内容 Schoolwork中的教师 越早可以使用它 并将其布置给学生
假设你已经创建了你的上下文树 一位教师将能够打开 Schoolwork 然后点击“Create New Handout”按钮
这将创建一个新的讲义实例 然后他们会将讲义 指向你app内的某个特定内容 一旦他们完成这个步骤 就可以把这份讲义分发给学生 一旦学生收到这份讲义 他们会点击讲义 这会打开你的app 这时你的app将会创建必要的上下文
为了更好的学生体验 你最好将学生导航到相应的内容
Marin一会儿将会告诉我们 这是如何在代码中实现的 现在假设 学生开始做这份家庭作业 你的app开始向学生报告进度 所有这些进度将被发送到教师的设备 并捆绑在我们称之为 进度报告或活动报告中 当这些完成以后 教师可以从分发的讲义中获得该报告
这整个流程包括 你有一个教师托管ID 一个学生托管ID 也可能有一些设备 甚至只是一台你可以登录登出的设备 以便你可以进行测试
但尽管如此 我们实际上还可以做得比这更好 我们创建了一个我们称之为 开发者模式的东西 开发者模式的作用是 能够让你充当老师 以便你可以创建讲义 分发给学生 并查看这些学生的进度报告 它也可以让你充当学生 使用这些讲义 并且你的app可以将进度报告给教师
还有一种重置开发数据的方法 说实话我个人经常需要使用这个按钮
有了这些作为前提 我们来看看框架中有哪些类 (框架类) 在最顶端 我们有一个Data Store
它用于在我们的app中管理上下文树
这也是主app上下文作为属性 暴露出来的地方 Data Store跟踪系统中的 所有的修改对象
所以如果你想保存这些对象 你可以调用 CLSDataStore.save(completion:)
我还想提一下 一个Data Store共享实例 你可以与之交互
接下来我们讨论一下上下文
我们看到上下文是一个树层次结构 Marin稍后会讨论上下文的细节 如何使用它们 并提及一些关于它们的有趣属性 但我想专注于上下文身份
ClassKit中上下文的身份 包含两样东西 第一个是identifier属性
identifier属性允许你 在兄弟节点中唯一识别上下文
我的意思是 你可以拥有具有相同 identifier的上下文 或具有相同identifier 的多个上下文 只要他们没有相同的父节点 那就没问题
另一个东西我们称之为 上下文标识符路径
上下文标识符路径是我们实际用来 在上下文树中唯一的识别 特定上下文的东西 上下文标识符路径是什么呢? 其实它只是一个 上下文标识符的数组 我们使用它的方式是 从根开始遍历上下文树 沿着这条路径 直到我们到达一个节点 即路径中的最后一个节点
我们来看看这在实践中看起来如何
在这个例子中 我们有一个指向 某本书中某一章的 “section-1”上下文的 上下文路径 右边是我们的上下文树 要查找“section-1” 我们将首先访问“App”上下文 然后我们访问“Book”上下文 然后找到“Book”上下文的 孩子中 标识符为“chapter-2”的 上下文 我们将访问这个节点 然后以同样的 方式访问“section-1” 由于“section-1” 是我们路径上的最后一个节点 这个节点就是该路径所代表的节点
我们有几种方法来查找 系统中的上下文 一种是使用绝对上下文路径 为此你可以调用 CLSDataStore的 contexts (matchingIdentifierPath)方法
此方法将会返回路径中的所有上下文 所以在这个例子中 它应该是一个包含“App”上下文 “Book”上下文、“chapter-2”上下文 以及“section-1”上下文的数组
如果由于某种原因 “section-1”中的 “chapter-2”丢失了 那么完成块中的数组 将继续包含“App”上下文 和“Book”上下文
这使你有机会为遗失的上下文 创建完全不同的内容
还有一种使用相对路径 查找上下文的方法
如果你已经持有对某个上下文的引用 但你想找到其后代 这时它就很有用 实现它的方法是 CLSContext的 descendent (matchingIdentifierPath)函数 这里的区别在于 你要么获取到该上下文 要么获取到null 如果它不匹配任何路径
还有一种查找上下文的方式 这是通过CLSDataStore的 contexts (matching:predicate)方法实现的 在幻灯片的例子中 你可以看到如何找到 某个上下文的所有孩子
此外 还有一个 DataStoreDelegate 它与我们刚才看到的路径查找方法 一同使用
屏幕上是委托声明
这当你的上下文是 按需下载的时候通常很有用 这种情况下你的所有上下文都不可用 因此你想按需创建这些上下文 它的工作原理是 当我们开始查询路径中的上下文时 如果你没有在该路径上找到上下文 我们将在你定义的委托上调用 createContext(forIdentifier)方法 这给你一个创建所遗失上下文的机会
当你创建好了上下文 我们将把这个上下文 添加到树中的正确位置
正如我所提到的 这对具有动态内容的app非常有用 让我们来看看实践中这是如何运作的 我们还使用相同的标识符路径 但这次 我们的树并不完整 而且我们有一个委托对象 (委托实战) 我们首先访问“App”上下文 然后我们将访问“Book”上下文 接着我们将尝试访问 “chapter-2”上下文
但它并不存在 因此我们要求委托创建它 如果委托创建了它 我们会将它添加到树上的正确位置 我们可以访问这个上下文了 我们将为“section-1” 做同样的事情 由于“section-1” 是路径上最后一节点 这意味着它就是这条路径所指的对象
接下来我们谈谈活动对象 活动对象实际上是用来 向教师报告进度的对象 事实上他们应被理解为“活动报告”
活动对象始终与上下文相关联 你可以自己分配一个 你可以通过调用 CLSContext. createNewActivity()方法 来创建一个新的活动对象 这将返回一个与方法的接收者 相关联的 新的活动对象
如果你想查看 当前是否有一个活动 与你的上下文相关联 你可以通过查询CLSContext的 currentActivity属性 来完成此操作
我想提醒一下 每当你协同创建一个新活动时 这与重新启动该活动是一回事 这意味着教师将收到 一份新的进度报告
我们来看看如何将进度添加到活动中 一种方法是直接设置 progress属性 这行代码是要添加一个进度范围 从开始到结束
另外直接设置progress属性 与将进度范围的开始设为0 具有相同效果
我还想提一下 添加互相重叠的进度范围 甚至将同一个进度范围添加多次 都是完全没有问题的 我们会在幕后处理好所有细节 以确保最终向教师提交正确的报告
我们还需要讨论另外一个对象 那就是活动项目 什么是活动项目
它是你希望作为你的报告的一部分 展示给教师的数量
每个活动可以有一个主要活动项目 这将是除了进度以外 你想展示给教师的 主要信息
例如 这对于展示测验的 最终分数很有用
你还可以拥有多个其他活动项目 你可以使用它添加额外的信息 并展示给教师 比如 给学生提供了多少提示 每个问题的答案 等等
活动分项和抽象类 在我们的系统中 我们已经定义了三个子类
其中之一是 CLSQuantityItem 这对于简单的标量 如提示 经验点和类似的事情非常有用 (CLSActivityItem) 我们还有一个ScoreItem 这对于表示"Y分之X"之类的 数值很有用 例如 对于测验的最终分数 这可能很有用 我们也有一个BinaryItem 这个是用来回答 “是或否”一类的问题
我们来看看如何将主活动项目 添加到活动中 你首先需要创建一个活动项目 在幻灯片中 我们向你展示了如何创建名为 “Total Score”的ScoreItem 然后将其作为主活动项目 与一个活动进行关联 你需要设置 一个活动的 primaryActivityItem属性
要添加其他活动项目 你将再次创建一个新的活动项目 在幻灯片中 我们展示了 如何创建QuantityItem 其类型为“hints” 然后是它的数量
为了将它与一项活动联系起来 你需要调用CLSActivity的 addAdditionalActivityItem方法 将你刚刚创建的项目作为参数传给它 此时 该活动项目将与活动相关联
在处理活动项目时有一些最佳实践
其中之一是始终为主活动项目 设置相同的子类
我的意思是 想象一下 教师给两个学生 分配相同的上下文 你知道 在学生A的设备上 你将主活动项目设置为 ScoreItem 在学生B的设备上 你将主活动项目设置为 BinaryItem 如果你这样做 我们实际上无法产生 一个汇总报告给老师 因为没有明确的启发式方法 将ScoreItem转换为 BinaryItem 反之亦然 正因为如此 最好始终使用 活动项目的相同子类作为主活动项目
另外 请为你的活动项目提供清晰 简单但描述性强的标题 这是因为你在活动项目上设置的标题 将在其报告中显示给教师
并请好好利用附加活动项目 它们是提供额外信息的好方法 教师实际上需要这些信息来真正了解 学生在你的app中的表现
因此 我想邀请Marin上台 她将在实践中向我们展示 所有这些如何运作
(使用ClassKit) 大家好 我的名字是Marin 我是ClassKit团队的工程师 我想告诉你 如何将ClassKitapp到 现有的app中 为了做到这一点 Pavel 和我预先构建了一个演示app 该演示app叫Quizzler 这是一个简单的数学测验app 你看到的第一个屏幕 要求你选择你想要测试的数学类型 让我们继续并选择加法测验
选择好之后 我们就会进入另一个界面 问我们是否想查看 所有用户的高分记分牌 或者我们只是想直接开始测验 让我们直接点击开始测验
然后 我们会看到每一个问题 我们只要回答这些问题 最后我们会得到一个总分
现在我们了解了这个 演示app的功能 让我们来谈谈 使用ClassKit的步骤
首先我们将讨论 什么类型的上下文数据 对我们的演示app有意义 讨论完这些之后 我们还会讨论什么类型的 由学生产生的活动数据 也可能有意义
我们将确保我们支持深度链接 现有两种方法可做到这一点 第一种方法是使用全局链接 如果你已经在app中 采用了全局链接 你可以简单地设置 CLSContext中的 全局链接属性
而我们的演示app不支全局链接 所以我会向你展示第二种方法 即使用NS User Activities Continue Restoration Handler
然后我们将确保使用开发者模式 在Schoolwork app上 进行我们的测试
现在我们来谈谈什么类型的 上下文结构 可能对这个app有意义 作为第一步 我们可能会做一个 我们的用户界面与上下文树的 一对一映射 如果我们这样做 最终会得到这样一个结构 其中最顶层是我们的主app上下文 即我们的Quizzler app 在它的下面是“Addition”上下文 和“Multiplication”上下文 在它们各自的下面 还有“Scoreboard”上下文 以及真正的“Quiz”上下文
Pavel告诉我们 上下文是我们app的一部分 教师将它们布置给学生 现在我们牢记这一点 我们先来谈谈 “Scoreboard”上下文
“Scoreboard”上下文 是什么? 这是所有用户的最高分 一位老师给他的学生分发这些 真的有意义吗?
好像并没有 所以我们将删除它 现在 剩下的结构看起来像这样 接下来让我们看看 “Addition”上下文 我们app中的“Addition” 上下文是什么? 难道它不就只是加法测验吗? 这实际上就是一件事 而不是两件 所以这些应该被合并为一个 所以我们继续这样做 对于“Multiplication”上下文也是如此
现在我们剩下的结构看起来像这样
Pavel和我讨论了一下 我们决定在将来可能会 添加一个减法测验 以及一个除法测验 如果我们决定这样做 我们可以 很方便地将它们添加为兄弟节点 现在 当你们都在想 哪种结构对你的app有意义 不要只考虑你当前的功能集 同时也要考虑你将来可能做什么 并确保你的结构将随着 你的需求变化轻松扩展
现在让我们来谈谈上下文本身 我们希望确保我们有明确的标题 那是因为标题是一个重要信息 学生和老师都必须通过这个信息 了解这个上下文代表什么 (CLSContext) 我们也希望尽早定义我们的上下文 对于我们的演示app 我们使用的是静态内容 因此一旦app启动 我们就知道要编写哪些上下文 所以这就是我们定义 我们的上下文的时候
我们可能也想要始终向教师 以特定顺序展示我们的上下文 对于我们的演示app 意味着“Addition”上下文 应该始终显示在 “Multiplication”上下文之上 为了做到这一点 我们将利用CLSContext中 displayOrder属性
现在我们已经确定了 我们要写什么类型的上下文数据 让我们来谈谈由学生生成的活动
在这里 我有一个 Schoolwork app的截图 这是一些活动数据可能的样子
对于我们的测验 我认为记录学生进行这项测验 所花费的总时间绝对有意义 为了做到这一点 我们可以调用 CLSActivity的 start()和stop()方法
现在 如果能够显示学生得到的总分 可能也很不错 如果我们仔细想想 总分可能是这整个测验中 最重要的信息 因此我们可能想使用 Schoolwork UI突出显示它 就像我们在这里做的一样 为了做到这一点 我们可以创建 一个CLSScoreItem 然后我们将其设置为主活动项目 这样 它就会在 Schoolwork UI中突显
现在 如果能显示每个问题 是否回答正确可能也很好
要做到这一点 我们可以创建 CLSBinaryItem 并将其中每一个 都添加为活动的附加活动项目
现在我们已经确定了 要写什么类型的 ClassKit代码 让我们看看这实际上看起来如何 (演示)
现在屏幕上是我的显示器镜像 我打开了Xcode 我首先要做的是 选择我的项目的目标 然后我将选择 Capabilities窗格 在这里找到 ClassKit API
然后 我们要确保将这个选项打开 现在我们就准备好开始 写一些ClassKit代码了 首先我们要写我们的上下文 我们说过可以在app 启动后立即这样做 现在让我们打开 AppDelegate 我们在这里创建一个函数 它将为我们发布我们的上下文
这是我们的发布上下文函数 而我们现在要做的是 实例化一个 CLSContext实例 传递该上下文的类型
给它一个唯一的标识符 然后是一个清晰简洁的标题 确保设置了 displayOrder属性 因为我们希望加法测验 显示在乘法测验之上 然后 我们将为乘法测验 做同样的事情 实例化一个上下文 并确保设置显示顺序 接着 我们创建一个 必须要创建的上下文字典
然后我们要获取我们希望 将这些添加到其中的父上下文 由于我们的上下文结构比较简单 我们知道我们的父上下文 永远是主app上下文 现在让我们运行一个查询来查看 这些上下文是否已经存在 所以我们需要创建 一个predicate 寻找所有父节点是 我们刚刚定义的父节点的上下文 然后 我们将向CLSDataStore的 共享实例发出该查询 以找出所有与我们刚定义的 predicate相匹配的上下文
这将返回一个上下文数组 我们只要遍历所有 我们知道其存在的上下文
然后 对于已经存在的每个上下文 我们将从需要创建的 上下文字典中删除它
接着我们将遍历剩余的 尚不存在的上下文 并且对于这些上下文中的每一个 我们将它作为一个子上下文 添加到我们的父上下文中
然后我们调用save函数保存更改 现在我们需要做的就是在app启动时 调用这个函数
让我们运行此代码 以测试我们的上下文 是否真的被创建了
我这有一个设备 我将其投影到屏幕上 完美 我们的Quizzler app 已启动 但一切看起来都一样 我们如何知道这些上下文 已经被创建了呢? 这就是我们使用到开发者模式 和Schoolwork的地方 我现在点击主页按钮 并打开设置
现在我向下滚动 我需要寻找开发人员设置
当我选择开发人员设置时 我将看到 一些ClassKit API 如果我选择 ClassKit API 我们可以看到 我们可以切换并扮演老师的角色 我要确保我选择了它 现在 我们可以点击主页按钮 打开Schoolwork app
当Schoolwork app启动时 我首先会看到欢迎屏幕 我将关掉它 如果我们看右上角 我可以看到那有一个加号按钮 这时我就知道 我是以老师的身份登录的 如果我点击那个加号按钮 我将打开“创建新讲义”视图 我可以添加一个活动 然后点击“Apps” 可以看到 Quizzler app显示在这里 如果我选择它 真棒 我们的上下文就在那里 所以现在我们已经能够验证 我们的上下文实际上已经创建 我还想指出加法测验 在乘法测验之上显示 我们现在也能够验证 我们的显示顺序已经设置正确 现在让我们选择加法测验 这将会为这份讲义添加该上下文 现在让我们将这份讲义 发送给我们的班级
我要点击收件人字段 选择我的班级
让我们给这个讲义添加一个标题
现在我继续点击“Post”按钮 它所做的是将这份讲义 发送给我的班级 并为这份讲义中的上下文授权 现在该上下文可以开始 记录进度数据了
我们可以看到 我的讲义成功发布 我可以点击它 在这里我们看到了我添加的上下文 并且我的漂亮图标 就在上下文名称旁边 如果我点击图标 它应该直接将我 导航到加法数学测验 我们点击它
Schoolwork启动了app 但这不是加法数学测验 哦 对了 这是对的 我们忘了添加深度链接 让我们回到Xcode并添加它
打开AppDelegate 让我们加入Continue Restoration Handler
在这里 Continue Restoration Handler将处理一些用户活动 我们需要使用该用户活动 并获取它的 contextIdentifierPath
一旦我们有了上下文路径 我们将实例化我们自己的 关联到该上下文的内部测验模型
然后我们要确定 我们调用的是同一个发布上下文函数 如果该上下文还没有被创建 我们需要确保创建它们 这里将返回一个可选错误 我们需要确保能够处理任何错误
然后 我们将与主线程进行同步 实例化我们的Storyboard 并且实例化此测验的视图控制器 然后我们将在视图控制器上 设置我们的测验 并压入适当的视图 现在 让我们运行此代码 并测试我们的深度链接是否正常工作 现在切换回我的设备 然后导航回 Schoolwork app
在这里 我们再次点击图标
真棒 它直接启动了加法数学测验 现在我们准备开始编写 用户生成的活动数据 让我们回到Xcode 并找到当测验第一次启动时 被调用的那部分代码
这里 我们想要为我们的测验 启动计时器 我们要做的是 向CLSDataStore实例 发起一个查询 然后我们要在主app上下文中查询 所有与标识符路径相匹配的后代 这些标识符路径与此测验相关联
这将返回一个可选上下文 我们使用该上下文 确保调用它的 becomeActive方法 然后我们将实例化 一个新的CLS活动实例 这是因为当这部分代码运行时 我们就知道学生正对该测验 进行一次新的尝试 接着我们要缓存该活动 以及我们定义的属性
然后我们调用活动的start方法 来启动我们的计时器 我们还说过我们想报告 每个问题的回答是否正确 所以我要找到当学生选择答案时 所调用的代码
在这里我们要做的是 获取当前运行的活动 这正是我们刚刚创建的 接着 我们将实例化一个 CLSBinaryItem 给它一个唯一的标识符 以及一个清晰明确的标题 然后记得传入 此binaryItem的类型
现在我们要设置 学生的回答是否正确的值
然后我们要在当前运行活动中 将这个binaryItem 作为附加活动项添加进去
我们最不想做的是 停止计时器并设置总分 我们找到当学生停止答题时 所调用的那部分代码
这里我们获得同样的当前运行活动 接着 我们将创建一个 CLSScoreItem 传递一个唯一的标识符、标题 我们还要传入 可能的最高分数中学生所得到的分数 然后 我们将该分数项 作为主活动项添加到我们的活动中 注意每次我这样做时 我总是设置CLS活动项目的 相同子类 它总是一个ScoreItem
然后我们调用stop方法 来停止我们的计时器 并调用save 以保存我们所有的更改 现在让我们重新运行此代码 以测试我们的学生生成的活动数据 是否已被设置 我切换回我的设备 现在我们要切换并充当学生的角色 我将返回到开发人员设置界面 设置在这里 我们点击“Student” 现在我转而扮演学生的角色 现在我们打开 Schoolwork app
这时候右上角 不再有一个加号按钮 我就知道我是以学生身份登录的 我们可以看到 我这里有老师分发给我的讲义 如果我点击它 我们可以看到我需要完成的上下文 我可以点击图标 它直接将我导航到测验界面 现在我看到一条提醒 它向我展示我的进度数据 将以我的名义进行记录 并发送给老师 我来关掉它 然后回答我们所有的问题
最后得到总分 可以看到 我得了100% 我们回到 Schoolwork app 在这里我们可以看到活动数据 我们可以看到时间和总分 注意总分在用户界面的主要部分 突出显示 现在我们能够验证 我们的活动项目设置正确 我也可以点击单元格 这会显示一个弹出框 上面有我写的所有活动数据 我可以看到浅灰色的总得分 还有我设定的总分的标题
在底部 我们可以看到所用时间 然后是每个问题的回答结果 我们还可以看到我为每个问题 设置的标题 它们也是浅灰色的 现在我们已经能够验证 我们的活动数据已经被记录下来了 现在我想重新将Pavel请上台 以继续他的演讲 并总结我们看到的这些东西
谢谢Marin 我想提一下关于ClassKit的 一些最佳实践
首先 尽早声明你的上下文 以便教师可以进行分发 并且Marin向我们 展示了你的app中 不是所有东西都非得是一个上下文
如果这样做有意义 就请使用 CLS Data Store Delegate 另外 请好好利用附加活动项目 它们的确是提供额外信息的好方法 而教师可能需要这些信息来了解 学生在你的app中的表现
还有一些关于教育app的 一般性最佳实践 其中之一是 删除StoreKit依赖 这是因为在学校环境中 使用app内清除并不是个好主意 这个库是用来支持可清除存储的
总的来说 这是一个好主意 但在学校环境中非常重要的是 在那里共享iPod是很常见的 而空间在这种情况下很重要 最后 通过托管app配置 实现设置访问 这样做可以使学校管理员 在配置设备时更轻松
你可以在 developer.apple.com/education 找到 关于所有这些内容的链接 和正在进行的细节
愿大家在剩余的WWDC中 度过愉快时光 谢谢大家的参与
-