版本记录
版本号 | 时间 |
---|---|
V1.0 | 2020.11.20 星期五 |
前言
WidgetKit
是iOS14的新的SDK,接下来几篇我们就一起看一下这个专题。感兴趣的可以看下面几篇文章。
1. WidgetKit框架详细解析(一) —— 基本概览(一)
开始
首先看下主要内容:
在本教程中,您将向一个大型
SwiftUI
应用添加小部件(widget)
,重用其视图以显示该应用存储库中的条目。内容来自翻译。
下面就看下写作环境
Swift 5, iOS 14, Xcode 12
接着就是正文了
在今年的WWDC Platforms State of the Union
中看到新的主屏幕小部件(widgets)
后,我就知道必须为自己喜欢的应用制作一个!在主屏幕上看到它真是太好了。我并不孤单。每个人都在做!苹果公司知道自己是赢家,并提供了一个由三部分组成的代码,以使所有人开始使用。
已经发布了几本指导手册,那么本教程有什么不同?好吧,我决定将一个小部件添加到由一组开发人员编写的相当大的SwiftUI
应用程序中,但没人是我。有大量的代码可供筛选,以查找构建窗口小部件所需的内容。而且所有这些都没有考虑到小部件的编写。因此,请跟随我,向我展示如何做到。
注意:您将需要
Xcode 12 beta
。您还需要运行iOS 14
的iOS设备。Catalina
可以。如果您有运行Big Sur Beta
的Mac [partition]
,则可以尝试在其中运行代码,以防它无法在Catalina
上运行。最重要的是,目前这是一个真正的
bleeding-edge API
。WWDC
演示中出现的内容不是Xcode 12 beta 1
的一部分。您可能会遇到一些不稳定的情况。就是说,Widgets
很酷,很有趣!
在打开启动程序项目之前,请打开Terminal
,cd
到starter / emitron-iOS-development
文件夹,然后运行以下命令:
scripts/generate_secrets.sh
您正在生成运行项目所需的一些机密文件。
现在,在starter / emitron-iOS-development
文件夹中打开Emitron
项目。这需要一些时间才能获取一些软件包,因此,这里有一些有关项目的信息,您可以在等待的同时进行。
Emitron
是raywenderlich.com
应用程序。如果您是订阅者(subscriber)
,则一定已将其安装在iPhone
和iPad
上。它可以让您流式传输视频,并且,如果您具有专业订阅,则可以下载视频以进行离线播放。
该项目是开源的。您可以在其GitHub存储库GitHub repository,中阅读有关它的信息,当然,欢迎您为它的改进做出贡献。
您下载的starter
版进行了一些修改:
- 设置是最新的,
iOS Deployment Target
是14.0
。 - 在
Downloads / DownloadService.swift
中,注释了两个在Xcode beta 1
中引起错误的promise
语句。下载服务是针对专业订阅的,本教程不需要它。 - 在
Guardpost / Guardpost.swift
中,将authSession?.prefersEphemeralWebBrowserSession
设置为false
,从而避免每次构建和运行应用程序时都需要输入登录详细信息。您仍然必须点击Sign in
,提示您使用raywenderlich.com
登录。点击Continue
。首次构建和运行时,您可能仍必须输入电子邮件和密码,但是在随后的构建和运行中,点击Continue
会跳过登录表单。
到目前为止,Xcode
已经安装了所有软件包。在模拟器中构建并运行。
忽略有关Hashable
和SwiftyJSON
的警告。 滚动和播放在Xcode beta 1
中效果不佳,但是您不会在本教程中对此进行修复。 如果滚动“太多”,则该应用程序将崩溃。 也不是你的问题。
WidgetKit
本教程全部关于向Emitron
添加闪亮的新小部件。
Adding a Widget Extension
首先添加带有File ▸ New ▸ Target…
的widget extension
。
搜索widget
,选择Widget Extension
并点击Next
:
将其命名为EmitronWidget
,并确保未选中Include Configuration Intent
:
有两种窗口小部件配置(widget configurations)
:Static
和 Intent
。 具有IntentConfiguration
的小部件使用Siri Intents
来使用户自定义小部件参数。
单击Finish
并同意激活方案(activate-scheme)
对话框:
1. Running Your Widget
小部件模板(widget template)
提供了许多您只需自定义的样板代码。它可以直接使用,因此,您可以立即设置所有内容,以确保在准备测试代码时一切都能顺利运行。
注意:
Xcode 12 beta 1
模拟器不会在widget gallery
中显示您的widget
。因此,在将来的某个Beta
版本之前,您必须在iOS 14
设备上构建并运行。如果您没有可用的设备,则可以运行Widget scheme
而不是主Emitron scheme
,该Widget
将出现在模拟器的主屏幕上。
在项目导航器中,选择顶级Emitron
文件夹对targets
进行签名。更改bundle identifier
,并为每个target
的每个版本设置team
。
注意:对于
widget
,您可能会遇到一个明显的Xcode bug
,该bug
将三个版本中的两个的签名标识设置为Distribution
。如果看到此错误,请打开Build Settings
,搜索distribution
并将签名标识更改为Apple Development
。
最后一个陷阱:确保小部件的bundle ID
前缀与应用程序的ID
匹配。这意味着您将需要在“ ios”
和“ EmitronWidget”之
间插入dev
以获取your.prefix.emitron.ios.dev.EmitronWidget
。
OK,现在连接您的iOS设备,选择Emitron scheme
和您的设备,然后构建并运行。登录,然后关闭应用程序,然后在主窗口的空白区域上按,直到图标开始抖动。
点击右上角的+
按钮,然后向下滚动以找到raywenderlich
:
选择它可以查看三种尺寸的快照:
点击Add Widget
以在屏幕上查看您的小部件:
点击widget
以重新打开Emitron
。
您的小部件widget
起作用了! 现在,您只需要使其显示来自Emitron
的信息即可。
Defining Your Widget
使您的小部件显示应用程序为每个教程显示的一些信息是很有意义的。
此视图在UI / Shared / Content List / CardView.swift
中定义。 我的第一个想法是将窗口widget target
添加到此文件中。 但这需要添加越来越多的文件,以容纳Emitron
中所有复杂的连接。
您真正需要的只是Text
视图。 这些图片很可爱,但是您需要包括持久性基础结构以防止它们消失。
您将复制相关Text
视图的布局。 它们使用几个实用程序扩展,因此找到这些文件并将EmitronWidgetExtension
target
添加到其中:
注意:确保注意到图像顶部
Assets
。
CardView
显示ContentListDisplayable
对象的属性。 这是Displayable / ContentDisplayable.swift
中定义的协议:
protocol ContentListDisplayable: Ownable {
var id: Int { get }
var name: String { get }
var cardViewSubtitle: String { get }
var descriptionPlainText: String { get }
var releasedAt: Date { get }
var duration: Int { get }
var releasedAtDateTimeString: String { get }
var parentName: String? { get }
var contentType: ContentType { get }
var cardArtworkUrl: URL? { get }
var ordinal: Int? { get }
var technologyTripleString: String { get }
var contentSummaryMetadataString: String { get }
var contributorString: String { get }
// Probably only populated for screencasts
var videoIdentifier: Int? { get }
}
您的widget
仅需要name
,cardViewSubtitle
,descriptionPlainText
和releasedAtDateTimeString
。 因此,您将为这些属性创建一个结构。
1. Creating a TimelineEntry
创建一个新的名为WidgetContent.swift
的Swift
文件,并确保其targets
是emitron
和EmitronWidgetExtension
:
它应该在EmitronWidget
组中。
现在,将此代码添加到新文件中:
import WidgetKit
struct WidgetContent: TimelineEntry {
var date = Date()
let name: String
let cardViewSubtitle: String
let descriptionPlainText: String
let releasedAtDateTimeString: String
}
要在窗口小部件中使用WidgetContent
,它必须符合TimelineEntry
。 唯一必需的属性是date
,您可以将其初始化为当前日期。
2. Creating an Entry View
接下来,创建一个视图以显示四个String
属性。 创建一个新的SwiftUI View
文件,并将其命名为EntryView.swift
。 确保其target
仅是EmitronWidgetExtension
,并且也应位于EmitronWidget
组中:
现在,用以下代码替换struct EntryView
的内容:
let model: WidgetContent
var body: some View {
VStack(alignment: .leading) {
Text(model.name)
.font(.uiTitle4)
.lineLimit(2)
.fixedSize(horizontal: false, vertical: true)
.padding([.trailing], 15)
.foregroundColor(.titleText)
Text(model.cardViewSubtitle)
.font(.uiCaption)
.lineLimit(nil)
.foregroundColor(.contentText)
Text(model.descriptionPlainText)
.font(.uiCaption)
.fixedSize(horizontal: false, vertical: true)
.lineLimit(2)
.lineSpacing(3)
.foregroundColor(.contentText)
Text(model.releasedAtDateTimeString)
.font(.uiCaption)
.lineLimit(1)
.foregroundColor(.contentText)
}
.background(Color.cardBackground)
.padding()
.cornerRadius(6)
}
您实质上是从CardView
复制Text
视图并添加填充间距。
完全删除EntryView_Previews
。
3. Creating Your Widget
现在开始定义窗口widget
。 打开EmitronWidget.swift
并在该行中双击SimpleEntry
:
public typealias Entry = SimpleEntry
选择Editor ▸ Edit All in Scope
,并将名称更改为WidgetContent
。 这将导致一些错误,您将在接下来的几个步骤中进行修复。 首先删除声明:
struct WidgetContent: TimelineEntry {
public let date: Date
}
现在,此声明是多余的,并且与WidgetContent.swift
中的声明冲突。
4. Creating a Snapshot Entry
provider
的一种方法提供了一个快照条目,以显示在widget gallery
中。 为此,您将使用特定的WidgetContent
对象。
在import
语句的下面,添加此全局对象:
let snapshotEntry = WidgetContent(
name: "iOS Concurrency with GCD and Operations",
cardViewSubtitle: "iOS & Swift",
descriptionPlainText: """
Learn how to add concurrency to your apps! \
Keep your app's UI responsive to give your \
users a great user experience.
""",
releasedAtDateTimeString: "Jun 23 2020 • Video Course (3 hrs, 21 mins)")
这是我们的并发视频课程的更新,该课程在WWDC
第2天发布。
现在,将snapshot(with:completion:)
的第一行替换为:
let entry = snapshotEntry
当您在gallery
中查看此小部件时,它将显示此条目。
5. Creating a Temporary Timeline
小部件需要一个TimelineProvider
才能为其提供TimelineEntry
类型的条目。 它会在条目的date
属性指定的时间显示每个条目。
最重要的provider
方法是timeline(with:completion:)
。 它已经有一些代码来构造时间轴,但是您没有足够的条目。 因此,注释掉最后两行以外的所有内容,并添加以下行:
let entries = [snapshotEntry]
您正在创建一个仅包含snapshotEntry
的entries
数组。
6. Creating a Placeholder View
小部件在等待实际时间轴条目时显示其PlaceholderView
。 您还将为此使用snapshotEntry
。
以此替换Text
视图:
EntryView(model: snapshotEntry)
WWDC
代码中还显示了一个特殊的修饰符,该修饰符使视图的内容模糊不清,以表明这是一个占位符,而不是真实的东西。 是这样的:
.isPlaceholder(true)
在WWDC
视频中看起来很酷,但是在Xcode 12 beta 1
中没有编译。有关更多信息,请参阅Apple开发者论坛中的此项 this entry。
7. Defining Your Widget
最后,您可以将所有这些部分放在一起。
首先,删除EmitronWidgetEntryView
。 您将改用EntryView
。
现在,将EmitronWidget
的内部替换为以下内容:
private let kind: String = "EmitronWidget"
public var body: some WidgetConfiguration {
StaticConfiguration(
kind: kind,
provider: Provider(),
placeholder: PlaceholderView()
) { entry in
EntryView(model: entry)
}
.configurationDisplayName("RW Tutorials")
.description("See the latest video tutorials.")
}
这三个字符串是您想要的:kind
描述您的窗口小部件,最后两个字符串显示在库中每个窗口小部件上方的尺寸。
在您的设备上构建并运行,登录,然后关闭该应用以查看您的小部件。
如果仍然显示时间,请将其删除并重新添加。
这是中等大小的小部件现在的样子:
只有中等大小的小部件看起来不错,因此请修改您的小部件以仅提供该大小。 在.description
下面添加此修饰符:
.supportedFamilies([.systemMedium])
接下来,您将直接从应用程序的存储库中为时间线提供真实的条目!
Providing Timeline Entries
该应用程序将在Data / ContentRepositories / ContentRepository.swift
中创建的contents
中显示ContentListDisplayable
对象的数组。 要与您的小部件widget
共享此信息,您将创建一个应用程序组。 然后,在ContentRepository.swift
中,将文件写入此应用程序组,并在EmitronWidget.swift
中读取该文件。
1. Creating an App Group
在项目页面上,选择emitron target
。 在Signing & Capabilities
选项卡中,单击+ Capability
,然后将App Group
拖到窗口中。 将其命名为group.your.prefix.emitron.contents
;确保适当替换your.prefix
。
现在,选择EmitronWidgetExtension target
并添加App Group
功能。 滚动浏览App Group
以查找并选择group.your.prefix.emitron.contents
。
2. Writing the Contents File
在ContentRepository.swift
的顶部,在import Combine
语句的下面,添加以下代码:
import Foundation
extension FileManager {
static func sharedContainerURL() -> URL {
return FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.your.prefix.emitron.contents"
)!
}
}
这只是获取应用程序组容器的URL
的一些标准代码。 确保替换您的应用标识符前缀。
现在,在var contents
下面,添加此辅助方法:
func writeContents() {
let widgetContents = contents.map {
WidgetContent(name: $0.name, cardViewSubtitle: $0.cardViewSubtitle,
descriptionPlainText: $0.descriptionPlainText,
releasedAtDateTimeString: $0.releasedAtDateTimeString)
}
let archiveURL = FileManager.sharedContainerURL()
.appendingPathComponent("contents.json")
print(">>> \(archiveURL)")
let encoder = JSONEncoder()
if let dataToSave = try? encoder.encode(widgetContents) {
do {
try dataToSave.write(to: archiveURL)
} catch {
print("Error: Can't write contents")
return
}
}
}
在这里,您将创建一个WidgetContent
对象的数组,每个对象用于存储库中的每个项目。 您将它们分别转换为JSON
并将其保存到app group
的容器中。
在let archiveURL
行设置一个断点。
设置contents
后,您将调用此方法。 将此didSet
闭包添加到contents
中:
didSet {
writeContents()
}
如果Xcode
在警告WidgetContent
。 跳转到WidgetContent
的定义,使其符合Codable
:
struct WidgetContent: Codable, TimelineEntry {
现在,在模拟器中构建并运行该应用程序。 在断点处,widgetContents
具有20
个值。
继续执行程序并在应用程序中向下滚动。 在断点处,widgetContents
现在具有40
个值。 因此,您可以控制与小部件共享多少项。
停止应用程序,禁用断点,然后从调试控制台复制URL文件夹路径并在Finder
中定位。 看一下contents.json
。
接下来,转到并设置widget
以读取此文件。
3. Reading the Contents File
在EmitronWidget.swift
中,添加相同的FileManager
代码:
extension FileManager {
static func sharedContainerURL() -> URL {
return FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.your.prefix.emitron.contents"
)!
}
}
确保更新您的前缀。
将此帮助程序方法添加到Provider
:
func readContents() -> [Entry] {
var contents: [WidgetContent] = []
let archiveURL =
FileManager.sharedContainerURL()
.appendingPathComponent("contents.json")
print(">>> \(archiveURL)")
let decoder = JSONDecoder()
if let codeData = try? Data(contentsOf: archiveURL) {
do {
contents = try decoder.decode([WidgetContent].self, from: codeData)
} catch {
print("Error: Can't decode contents")
}
}
return contents
}
这将读取您保存到应用程序组容器中的文件。
取消注释timeline(with:completion:)
中的代码,然后替换此行:
var entries: [WidgetContent] = []
使用下面
var entries = readContents()
接下来,修改注释和for
循环以将日期添加到条目中:
// Generate a timeline by setting entry dates interval seconds apart,
// starting from the current date.
let currentDate = Date()
let interval = 5
for index in 0 ..< entries.count {
entries[index].date = Calendar.current.date(byAdding: .second,
value: index * interval, to: currentDate)!
}
删除for
循环下面的let entry
行。
之后的那一行设置时间轴运行并指定刷新策略。 在这种情况下,时间轴将在用完所有当前条目后刷新。
在您的设备上构建并运行,登录并加载列表。 然后关闭该应用程序,添加您的小部件并观看它每5秒更新一次。
我可以整天看这个。
如果您没有滚动列表,则该widget
将在20
个项目后用完所有条目。如果等待那么长时间,您会在刷新时看到它暂停。
注意:这是
Beta
版软件。如果未获得预期的结果,请尝试从设备中删除该应用,然后重新启动设备。另外,请记住,小部件并不是要以秒为单位测量时间间隔。在教程设置中,非常短的间隔只是更加方便。但是结果是,时间轴刷新的等待时间感觉很长!最后一条警告:不要让5秒小部件在设备上运行,因为它会耗尽电池电量。
Enabling User Customization
我为时间轴间隔选择了5秒,因此无需等待很长时间即可看到更新。如果您想要更短或更长的间隔,只需更改代码中的值即可。或者...
创建一个intent
,让您可以通过在主屏幕上直接编辑小部件来设置时间间隔!
注意:使用
intent
更改时间间隔时,直到widget
刷新其时间轴,您才会看到效果。
1. Adding an Intent
首先,添加您的intent
:创建一个新文件(Command-N)
,搜索intent
,选择SiriKit Intent Definition File
并将其命名为TimelineInterval
。确保其target
同时是emitron
和EmitronWidgetExtension
。
在intent
侧边栏的左下角,单击+
,然后选择New Intent
。
将intent
命名为TimelineInterval
。 如图所示,使用Category View
设置Custom Intent
:
并添加一个名为Integer
类型的interval
的参数,其默认值,最小值和最大值(如所示)和Type Field
。 或设置您自己的值和/或使用步进器。
2. Reconfiguring Your Widget
在EmitronWidget.swift
中,将小部件重新配置为IntentConfiguration
。
将Provider
协议更改为IntentTimelineProvider
。
struct Provider: IntentTimelineProvider {
将snapshot(with:completion:)
定义为:
public func snapshot(
for configuration: TimelineIntervalIntent,
with context: Context,
completion: @escaping (Entry) -> Void
) {
现在,将timeline(with:completion:)
的定义更改为:
public func timeline(
for configuration: TimelineIntervalIntent,
with context: Context,
completion: @escaping (Timeline<Entry>) -> Void
) {
在timeline(for:with:completion)
中,更改interval
以使用配置参数:
let interval = configuration.interval as! Int
最后,在EmitronWidget
中,将StaticConfiguration(kind:provider:placeholder :)
更改为此:
IntentConfiguration(
kind: kind,
intent: TimelineIntervalIntent.self,
provider: Provider(),
placeholder: PlaceholderView()
) { entry in
在您的设备上构建并运行,登录并加载列表。 关闭应用程序,添加小部件,然后长按该小部件。 它会翻转以显示Edit Widget
按钮。
点击此按钮更改间隔值
本教程向您展示了如何利用大型应用程序中的代码来创建一个小部件,以显示该应用程序自己的存储库中的项目。 以下是一些您可以添加到Emitron
小部件中的想法:
- 为大型窗口小部件设计一个视图,该视图显示两个或更多条目。 查看
Apple
的EmojiRangers
示例应用程序,以了解如何为小部件系列修改EntryView
。 - 将一个
widgetURL
添加到EntryView
,以便点按该小部件可在该项目的详细信息视图中打开Emitron
。 - 添加
intent
以使用户可以从小部件设置应用程序的过滤器。
后记
本篇主要讲述了基于
WidgetKit
和SwiftUI
的简单示例,感兴趣的给个赞或者关注~~~