版本記錄
版本號 | 時間 |
---|---|
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秒,因此無需等待很長時間即可看到更新。如果您想要更短或更長的間隔,只需更改代碼中的值即可?;蛘?code>...創建一個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
的簡單示例,感興趣的給個贊或者關注~~~