大多数浏览器和
Developer App 均支持流媒体播放。
-
使用 TipKit 以提升功能的可发现性
使用 TipKit 教用户如何使用你的 App!了解如何通过提示创建有效的教导时机。我们将与你分享如何建立资格规则以触达理想受众、控制提示频率以及测试策略以确保成功互动。
章节
- 0:00 - Intro
- 1:27 - Create a tip
- 5:23 - Eligibility rules
- 9:19 - Display and dismissal
- 12:35 - Test tips
- 13:56 - Wrap-up
资源
相关视频
WWDC24
-
下载
♪ ♪
Ellie:大家好 我是 Ellie Gattozzi 是 Apple 的一名教学设计经理 今天我将介绍如何利用 TipKit 使功能更容易被发现 你将学习如何创建你的第一个提示 添加规则以指定谁应该看到提示 管理何时显示和解除提示 以控制你的 App 中 教导信息的发送频率 并测试你的提示 但在开始之前 先来 了解一些 TipKit 的背景
你了解并正在开发 用户会喜欢的功能 但首先 用户需要发现这些功能 TipKit 是一个新的框架 可轻松在你的 App 中显示提示
作为负责 Apple 所有用户教导的 Instructional Products 团队 我们在设计 TipKit 时 特别考虑到了教导方面 比如 TipKit 可以教授某人全新的功能 帮助发现隐藏功能 或展示完成任务的更快方法 其适用于 iPhone、iPad、 Mac、Apple Watch 和 Apple TV 使用 Tipkit 我将开始创建一个提示 在 Backyard Birds App 中 有一个功能叫收藏后院 收藏功能可使用户 更轻松地找到其最关心的后院 这使该功能成为提示的极佳选择 一旦我确定了要提示的功能 我就准备好转到 Xcode 了 我将从定义 收藏后院功能的新提示开始 提示由标题和消息组成 因此我将添加标题和消息 有用的提示用 直接的操作短语作为标题 说明该功能是什么 其消息中包含易于 记住的福利信息或指示说明 以便用户了解为何要使用该功能 且以后能够自己完成该任务
以下是一些有效提示的示例 所有这些都具有 可操作性、教学性、易记性 以下是几个不适合 使用 TipKit 的示例 第一个是促销 第二个是错误消息 两者都不具有教导性 第三个示例中 功能更好固然很好 但用户不需要做任何事情 最后一个示例很有用 但过于复杂 难以在瞬间阅读和记忆
现在回到我的提示 我已经有了标题和消息 接着 我将放入提示视图的 基本结构并配置 TipsCenter TipsCenter 可实现 TipKit 的主要功能 包括在 App 启动之间 让提示及相关事件持续存在 并让提示测试起来更为容易 TipsCenter 提供了一个默认的 共享实例 我在此添加了该实例
配置好 TipsCenter 后 我现在可以看到提示了 这看起来不错 但还可以更好 我将更改文本颜色为靛蓝色 使其与 App 匹配 并放置一个图标 以吸引用户注意力 并在视觉上将提示与功能相关联 我选择了星号符号 因为该提示是关于收藏的 如果你的功能具有可自定义的设置 你也可添加一个操作按钮 操作按钮可将 用户直接导航到这些设置 以便用户快速进行调整 或者如果你觉得用户 会发现其他资源特别有用 另一个选择是直接 将其链接到其他资源 比如一个入门流程 好了 我有我的 教学提示了 这看起来很出色 现在我将确定提示的 最佳处理方式和放置位置 有两种类型的提示视图: 第一种是弹出视图 允许提示 出现在你 App 的 UI 上 可直接指向按钮或其他元素 特别适用于指导用户 而无需改变当前 App 的屏幕 在 tvOS 上 仅支持使用弹出视图 第二种是内联视图 该视图下可调整 App 的 UI 以暂时适应该视图大小 而不会阻塞任何 UI 使用任一视图时 让提示出现在相关 按钮或你调用的元素附近 是很有帮助的 针对收藏后院功能 我将选择弹出视图 并将其放置 并指向右上角的星形按钮 这是我的 App 的第一个提示 这看来很棒 且合情合理 但要真正利用 TipKit 在最佳时机 触达理想受众的能力 我们将要添加一些规则 我将交给 Charlie 由他来谈谈 如何将提示提升到新的水平 Charlie:谢谢 Ellie 大家好 我是 Charlie Parks 来自 Instructional Products 团队的 一名软件工程经理 发现某个新功能 可能会引发惊喜和愉悦感 但前提是用户对该功能感兴趣 且其任意相关教导 都不会被视为垃圾或无关紧要 收藏后院的相关提示很有用 但可能并不适用于所有人 特别是那些已经发现该功能的人 对于很少使用该 App 的人 这可能也不那么有趣 在 Apple 我们认为 App 内的教导 应集中在那些会从中受益的人身上 我们的目标是避免 妨碍个人在 App 中完成某些事 为了确保只在最理想的时间 向最相关的受众显示提示 TipKit 提供了许多资格规则 你可单独使用或组合使用 以确定何时应显示提示 主要的规则类型有两种 第一种是基于参数的规则 基于参数的规则是持久的 最适合根据你想要编写的 表达式的 Swift 值类型显示提示 第二种是基于事件的规则 基于事件的规则 允许你定义一个操作 在用户有资格获得 提示之前必须执行该操作
针对收藏后院提示 我想要做的第一件事 是确保用户已登录其帐户 我将用一个基于 参数的规则来完成该任务 首先 我将参数的 初始值设置为 false 然后 我将其添加到提示的规则中 很好 我已缩小了提示的受众范围 但现在我想进一步缩小范围 我希望用户能够在提示之前 使用 App 并自然地发现该功能 我将创建基于事件的 规则来实现这一点 以确保提示仅在 用户进入后院 细节视图至少三次后显示
所以 首先 我要创建事件 然后 我将在规则值为 true 之前 让规则计算我希望事件触发的次数 在本例中 我希望用户 在我呈现提示之前 进入后院详细视图三次 我需要做的最后 一件事是捐赠该事件 因此在 BackyardDetailView 中 每次视图出现时 我都会捐赠该事件 这些规则正在逐渐形成 但如果我想进一步收紧规则呢? 到目前为止 我关注 那些频繁进入后院细节视图 但从未收藏过后院的用户 现在 我还想仅向那些 经常使用 该 App 的用户显示提示
我将在基于事件的 规则中添加日期查询修饰符 以确保该规则只会在 过去五天内用户进入 后院详细视图三次时判断为 true TipKit 另一个非常强大的 功能是通过为每个事件捐赠 添加关联类型来创建自定义捐赠 并根据这些类型查询事件 使用关联类型 我可以 进一步细化基于事件的规则 因此只有当用户进入 特定的后院细节视图时才会匹配
首先 我将创建 一个 DetailViewDonation 并提供特定后院视图的 ID 然后 在我的捐赠中 我将包括用户当前 所在后院视图的 ID 这些一旦建好 我将 根据唯一的后院 ID 查询事件 更新我的规则 在定义这些自定义捐赠时 请记住存储的数据大小 存储的数据越大 查询 执行的速度越慢、性能越低 TipKit 中的规则易于组合 其提供了一种强大而简单的方式 以确保将提示 显示给最能从中受益的人 规则可以是一般性的 或根据需要具体制定 也可组合规则 在最佳时间锁定理想受众 一旦提示出现在我的 App 中 我不希望其永远停留在屏幕上 如果用户使用了提示中描述的功能 我也不希望提示继续存在 如果我有多个提示 我不希望这些提示同时出现 因为那可能会令人不知所措 并妨碍用户想要做的事情 TipKit 提供了 几种显示和解除行为 因此这些提示可按节奏 流畅出现 且只在有用时出现 假设我已在 我的 App 中添加了几个提示 现在我在 Backyard Birds App 中有五个提示 这些提示都很有用 但如果 它们同时显示则效果会降低 使用 TipKit 我可设置提示的显示频率 这样这些提示 就能以更理想的节奏出现
在 TipsCenter 中 我可指定必须经过多长时间 才能再次出现提示 如果我想每隔 24 小时 显示一个提示 我可使用 .daily 如果我想每隔 60 分钟 显示一个提示 我可使用 .hourly 或者我可以提供任意有效的 TimeInterval 值指定自定义时长 如果我真的觉得需要立即进行教导 我还可使用 .immediate 修饰符 这样 即使刚出现了另一条提示 或有其他提示正在屏幕上显示 用户都将在其有资格 获得提示的那一瞬间 看到该提示 与其在 TipsCenter 级别忽略显示频率 更有用的做法可能是 根据每个提示的情况单独忽略之 为此 我可向特定提示添加 .ignoresDisplayFrequency 选项 现在 只有该特定提示 才会在用户符合条件时显示 而我的 App 中的其他提示将继续 按照我在 TipsCenter 级别设置的显示节奏进行显示 一旦提示出现 我希望其 只在有用时停留在屏幕上 如果用户使用了提示中描述的功能 这意味着用户已经执行了操作 或者如果用户有资格 获得提示但仍然不感兴趣 则应解除提示 假设使用 App 的用户已满足了 显示提示的所有规则:其已登录 过去五天内 已三次打开后院细节视图 但从未在任何 后院上点击过收藏按钮 一旦用户看到提示并点击收藏按钮 我将调用 invalidate 方法 理由是“.userPerformedAction” 表示所需的操作已执行 提示将被解除 另一种解除提示的方法是 如果提示显示次数 超过定义的 .maxDisplayCount 在这本例中 一旦提示显示了五次 且用户未采取任何行动 则下一次用户进入 后院细节视图时不应再显示提示 这些是使用 TipKit 提供的内置功能 解除提示的一些方法 但你才最了解你的 App 你可使用 .invalidate() 方法 根据任意交互或你认为 合适的标准来解除提示 TipKit 还可通过 iCloud 同步提示状态 以确保在一台设备上看到的 提示不会在另一台设备上看到 比如 如果使用该 App 的用户 在 iPad 和 iPhone 上都安装了该 App 并且这些设备上的功能都相同 则最好不要在这两个设备上 都教导用户使用该功能 现在我将交回给 Ellie 由她来 介绍 TipKit 提供的测试 API 和下一步的一些操作 Ellie:为了易于测试 TipKit 提供了一些方便的 API 你可使用这些 API 绕过资格规则 以便根据需要显示或隐藏提示 你可检查 你的 App 中的所有提示 而无需满足为其设置的规则 为此 请将 .showAllTips 添加到 TipsCenter 的配置中 如你只想在测试时显示某些提示 请使用 .showTips 并传递特定的提示 ID 作为参数 或使用 .hideTips 防止显示特定提示 如你想确保不显示任何提示 以便你专注于 App 中的其他功能 请使用 .hideAllTips 你还可使用 .resetDatastore 清除 TipKit 数据存储中的所有信息 在每次编译 App 时 设置一个原始纯净状态 你可通过 API 调用所有相同的测试选项 也可将其作为启动参数 添加到你的项目方案中 你可使用其中任意选项 进行快速检查或测试完整功能 有了这些 一切就绪 你已创建了你的提示 添加了规则 设置了 App 中提示的频率 现在正进行测试 以便一切按预期运行 综上所述 提示有利于 帮助用户发现你 App 中的功能 记得保持提示 简短性、教学性和可操作性 使用易于定义但功能强大的 规则以锁定理想受众 TipKit 的示例代码将在 developer.apple.com/ 上提供 请留意 TipKit 人机界面指南 以获得创建出色提示的更多信息 现在请检查你的 App 寻找发现性机会 并在开发新功能时记住 TipKit 我谨代表 Instructional Products 团队 感谢你的观看
-
-
1:55 - Create a tip
struct FavoriteBackyardTip: Tip { var title: Text { Text("Save as a Favorite") } var message: Text { Text("Your favorite backyards always appear at the top of the list.") } }
-
2:48 - Configure TipsCenter
@main struct BackyardBirdsApp: App { var body: some Scene { WindowGroup { ContentView() } } // ... init() { TipsCenter.shared.configure() } }
-
3:18 - Add actions and an asset to a tip
struct FavoriteBackyardTip: Tip { var title: Text { Text("Save as a Favorite").foregroundColor(.indigo) } var message: Text { Text("Your favorite backyards always appear at the top of the list.") } var asset: Image { Image(systemName: "star") } var actions: [Action] { [ Tip.Action( id: "learn-more", title: "Learn More" ) ] } }
-
4:53 - Create a popover view
private let favoriteBackyardTip = FavoriteBackyardTip() // ... .toolbar { ToolbarItem { Button { backyard.isFavorite.toggle() } label: { Label("Favorite", systemImage: "star") .symbolVariant( backyard.isFavorite ? .fill : .none ) } .popoverMiniTip(tip: favoriteBackyardTip) } }
-
6:38 - Add a parameter based rule
struct FavoriteBackyardTip: Tip { @Parameter static var isLoggedIn: Bool = false // ... var rules: Predicate<RuleInput...> { // User is logged in #Rule(Self.$isLoggedIn) { $0 == true } } }
-
7:16 - Add an event based rule
struct FavoriteBackyardTip: Tip { @Parameter static var isLoggedIn: Bool = false static let enteredBackyardDetailView: Event = Event<DetailViewDonation>( id: "entered-backyard-detail-view" ) // ... var rules: Predicate<RuleInput...> { // User is logged in #Rule(Self.$isLoggedIn) { $0 == true } // User has entered any backyard detail view at least 3 times #Rule(Self.enteredBackyardDetailView) { $0.count >= 3 } } }
-
7:34 - Donate the event when a view appears
.onAppear { FavoriteBackyardTip.enteredBackyardDetailView.donate() }
-
7:59 - Filter event donations in an event based rule
// User has entered any backyard detail view at least 3 times in the past 5 days #Rule(Self.enteredBackyardDetailView) { $0.donations.filter { $0.date > Date.now.addingTimeInterval(-5 * 60 * 60 * 24) } .count >= 3 }
-
8:34 - Create a custom donation
// Create the associated type extension BackyardDetailTip { struct DetailViewDonation: DonationValue { let backyardID: Int } } // Donate the unique id of the backyard detail being viewed .onAppear { BackyardFavoriteTip.enteredBackyardDetailView.donate( with: .init(backyardID: backyard.id) ) } struct FavoriteBackyardTip: Tip { // ... var rules: Predicate<RuleInput...> { // Update the rule to specify a backyardID #Rule(Self.enteredBackyardDetailView) { $0.donations.filter { $0.date > Date.now.addingTimeInterval(-5 * 60 * 60 * 24) } .largestSubset(by: \.backyardID) .count >= 3 } } }
-
9:57 - Configure display frequency
// One tip per day. TipsCenter.shared.configure { DisplayFrequency(.daily) } // One tip per hour. TipsCenter.shared.configure { DisplayFrequency(.hourly) } // Custom configuration. Only show one tip every five days. let fiveDays: TimeInterval = 5 * 24 * 60 * 60 TipsCenter.shared.configure { DisplayFrequency(fiveDays) } // No frequency control. Show all tips as soon as eligible. TipsCenter.shared.configure { DisplayFrequency(.immediate) }
-
10:34 - Turn off display frequency controls for a tip
struct FavoriteBackyardTip: Tip { // ... var options: [Option] { [.ignoresDisplayFrequency(true)] } }
-
11:27 - Invalidate a tip
Button { backyard.isFavorite.toggle() // When user taps the favorite button, dismiss the tip favoriteBackyardTip.invalidate(reason: .userPerformedAction) } label: { Label("Favorite", systemImage: "star") .symbolVariant(backyard.isFavorite ? .fill : .none) } .popoverMiniTip(tip: favoriteBackyardTip)
-
11:41 - Configure max display count on a tip
struct FavoriteBackyardTip: Tip { // ... var options: [Option] { [.maxDisplayCount(5)] } }
-
12:46 - Programmatically call testing API
// Show all defined tips in the app TipsCenter.showAllTips() // Show some tips, but not all TipsCenter.showTips([tip1, tip2, tip3]) // Hide some tips, but not all TipsCenter.hideTips([tip1, tip2, tip3]) // Hide all tips defined in the app TipsCenter.hideAllTips() // Purge all TipKit related data TipsCenter.resetDatastore()
-
13:31 - Configure launch arguments in your scheme
// Show all defined tips in the app com.apple.TipKit.ShowAllTips 1 // Show some tips, but not all com.apple.TipKit.ShowTips tipID,otherTipID // Hide some tips, but not all com.apple.TipKit.HideAllTips 1 // Hide all tips defined in the app com.apple.TipKit.HideTips tipID,otherTipID // Purge all TipKit related data com.apple.TipKit.ResetDatastore 1
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。