iOS14Widget簡介

簡介

新的 WidgetKit 框架和 SwiftUI 關(guān)于 widget 新的 api,創(chuàng)建的 widget能滿足 iOS绣张,iPadOS答渔,和 macOS 平臺(tái),能放在 Home screen 的任意位置侥涵。而且還擁有智能堆棧 Smart Stack 沼撕,集成siri的智能化推薦能力宋雏,能根據(jù)你使用時(shí)間,位置等因素端朵,來智能顯示組件好芭。

  • Widget使用SwiftUI視圖顯示其內(nèi)容,可視化能力強(qiáng)
  • 一個(gè)App 可以提供多種樣式的 Widget,可更具不同情況來顯示不同類型的Widget,用戶可以選擇將其最關(guān)心的放置在最重要的位置上冲呢,以便最方便的獲取信息舍败。
  • Widget和之前的TodayWidget是一個(gè)獨(dú)立運(yùn)行的程序,需要在項(xiàng)目中進(jìn)行App Groups的設(shè)置才能使其與主程序互通數(shù)據(jù)敬拓。
  • 目前存在的問題邻薯,widget不支持連續(xù)的實(shí)時(shí)更新,只能通過timeline來設(shè)置時(shí)間軸進(jìn)行更新乘凸。
  • Widget 只有大中小3種尺寸(systemLarge厕诡、systemMedium、systemSmall)营勤。
image

項(xiàng)目構(gòu)建

添加Widget到原工程

打開你的 Xcode 工程, 并且選擇 File > New > Target灵嫌,在 Application Extension 中選擇 Widget Extension,輸入 Widget 的名字葛作,如果 Widget 提供了用戶可配置的屬性寿羞,請(qǐng)選中“ Include Configuration Intent ”復(fù)選框。

image

在這里簡單介紹下Widget的配置信息

小部件擴(kuò)展模板提供了一個(gè)符合小部件協(xié)議的初始小部件實(shí)現(xiàn)赂蠢。Widget的body屬性決定了該Widget是否具有用戶可配置的屬性绪穆。

創(chuàng)建widget時(shí),包含配置意圖「Include Configuration Intent」復(fù)選框決定了Xcode使用哪種配置虱岂。當(dāng)選擇這個(gè)復(fù)選框時(shí)玖院,Xcode使用將使用默認(rèn)設(shè)置進(jìn)行配置。

Configuration有兩種配置可供選擇:

1.StaticConfiguration: 對(duì)于一個(gè)沒有用戶可配置屬性的Widget第岖∧丫「例如,顯示一般市場信息的股票市場Widget蔑滓,或顯示趨勢標(biāo)題的新聞Widget郊酒。」

2.IntentConfiguration烫饼。對(duì)于一個(gè)具有用戶可配置屬性的Widget來說,你可以使用SiriKit自定義意圖來定義屬性试读。您使用 SiriKit 自定義意圖來定義屬性杠纵。「例如钩骇,一個(gè)天氣Widget需要一個(gè)城市的郵政編碼或郵政編碼比藻,或者一個(gè)包裹跟蹤Widget需要一個(gè)跟蹤號(hào)碼铝量。」

以下代碼創(chuàng)建一個(gè)常規(guī)的StaticConfiguration银亲,不可配置的狀態(tài)的 Widget:

struct TestWidget: Widget {
    let kind: String = "TestWidget"
    var body: some WidgetConfiguration { 
     StaticConfiguration(kind: kind,provider: Provider()) { entry in
            TestWidget EntryView(entry: entry)
        }
        .configurationDisplayName("name")
        .description("des")
        .supportedFamilies([.systemMedium])
    }
}
  • kind:識(shí)別Widget的字符串慢叨。這是您選擇的標(biāo)識(shí)符,應(yīng)描述Widget所代表的內(nèi)容务蝠。

  • Provider:符合TimelineProvider的對(duì)象拍谐。一個(gè)符合TimelineProvider的對(duì)象,它能產(chǎn)生一個(gè)時(shí)間線馏段,告訴WidgetKit何時(shí)渲染W(wǎng)idget轩拨。時(shí)間線包含一個(gè)你定義的自定義TimelineEntry類型。時(shí)間線條目標(biāo)識(shí)了你希望WidgetKit更新Widget內(nèi)容的日期院喜。在自定義類型中包含你的Widget的視圖需要渲染的屬性亡蓉。

  • Placeholder:一個(gè) SwiftUI 視圖,WidgetKit 用來在第一次渲染W(wǎng)idget喷舀。占位符是您的Widget的通用表示砍濒,沒有特定的配置或數(shù)據(jù)。

  • Content Closure(內(nèi)容閉合):一個(gè)包含SwiftUI視圖的封閉硫麻。WidgetKit調(diào)用它來渲染W(wǎng)idget的內(nèi)容爸邢,從提供者那里傳遞一個(gè)TimelineEntry參數(shù)。

  • Custom Intent(自定義配置)庶香。一個(gè)定義用戶可配置屬性的自定義意圖甲棍。

  • 包括顯示名稱(name)、描述(description)和Widget支持的系列(families)赶掖。

Provider 時(shí)間線配置(Provide Timeline Entries)

struct Provider: TimelineProvider {
  
    public typealias Entry = SimpleEntry
  
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date())
    }

    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date())
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []

        // Generate a timeline consisting of five entries an hour apart, starting from the current date.
        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate)
            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

Provider會(huì)生成由時(shí)間線條目組成的時(shí)間線感猛,每個(gè)條目都指定了更新小組件內(nèi)容的日期和時(shí)間。

  • placeholder :系統(tǒng)讓你的 view 自動(dòng)渲染一個(gè)占位圖奢赂。
  • getSnapshot:快照預(yù)覽為了在小部件庫中顯示小部件陪白,在你添加widget時(shí)候,預(yù)覽的樣式膳灶。當(dāng)部件還沒有從服務(wù)器獲取狀態(tài)時(shí)咱士,Provider通過顯示一個(gè)空狀態(tài)來實(shí)現(xiàn)getSnapshot方法。
  • getTimeline:在請(qǐng)求初始快照后轧钓,WidgetKit調(diào)用getTimeline(for:with:completion:)向提供者請(qǐng)求一個(gè)常規(guī)的時(shí)間線序厉。時(shí)間線由一個(gè)或多個(gè)時(shí)間線條目和一個(gè)重載策略組成,告知WidgetKit何時(shí)請(qǐng)求后續(xù)時(shí)間線毕箍。

Widget的刷新策略

Timeline里面有三種方式:atEnd弛房,after(date),never

  • atEnd: timeline 中最后一個(gè) entry 顯示后更新而柑。timelines 方法會(huì)重新調(diào)用文捶。
  • after(date): 指定日期荷逞,重新更新timeline。
  • never:系統(tǒng)不會(huì)自動(dòng)更新粹排,除非我們主動(dòng)通過 Widget Center Api 來更新种远。

保持組件狀態(tài)為最新(Keeping a Widget Up To Date)

Widget使用SwiftUI視圖來顯示它們的內(nèi)容。WidgetKit在一個(gè)單獨(dú)的過程中代表您渲染視圖顽耳。因此坠敷,即使小組件在屏幕上,小組件擴(kuò)展也不會(huì)持續(xù)活躍斧抱。盡管widget并不總是處于活動(dòng)狀態(tài)常拓,但有幾種方法可以使其內(nèi)容保持最新。

為可預(yù)測的事件生成一個(gè)時(shí)間軸
定義widget時(shí)辉浦,實(shí)現(xiàn)一個(gè)自定義的TimelineProvider弄抬。WidgetKit從你的provider那里獲取一個(gè)時(shí)間線,并使用它來跟蹤何時(shí)更新widget宪郊。時(shí)間線是一個(gè)TimelineEntry對(duì)象的數(shù)組掂恕。時(shí)間線中的每個(gè)條目都有日期和時(shí)間,以及小組件顯示其視圖所需的附加信息弛槐。除了時(shí)間線條目懊亡,時(shí)間線還指定了一個(gè)刷新策略,該策略告訴WidgetKit何時(shí)請(qǐng)求新的時(shí)間線.

下面是蘋果官網(wǎng)給的一個(gè)顯示角色健康水平的游戲小部件的例子乎串。
當(dāng)健康水平低于100%時(shí)店枣,角色以每小時(shí)25%的速度恢復(fù)。例如叹誉,當(dāng)角色的健康水平為25%時(shí)鸯两,需要3小時(shí)才能完全恢復(fù)到100%。下圖顯示了WidgetKit如何從provider那里請(qǐng)求時(shí)間線长豁,在時(shí)間線條目中指定的每個(gè)時(shí)間渲染小部件钧唐。

func getTimeline(for configuration: CharacterSelectionIntent, with context: Self.Context, completion: @escaping (Timeline<Self.Entry>) -> ()) {
    let selectedCharacter = characher(for: configuration)
    let endDate = selectedCharacter.fullHealthDate
    let oneHour: TimeInterval = 60 * 60
    var currentDate = Date()
    var entries: [SimpleEntry] = []

    while currentDate < endDate {
        let relevance = TimelineEntryRelevance(score: Float(selectedCharacter.healthLevel))
        let entry = SimpleEntry(date: currentDate, character: selectedCharacter, relevance: relevance)
        currentDate += oneHour
        entries.append(entry)
    }
    let timeline = Timeline(entries: entries, policy: .atEnd)
    completion(timeline)
}

當(dāng)WidgetKit最初請(qǐng)求時(shí)間軸時(shí),provider會(huì)創(chuàng)建一個(gè)有四個(gè)條目的時(shí)間軸匠襟。第一個(gè)條目代表當(dāng)前的時(shí)間(Now)钝侠,之后是每小時(shí)一次的三個(gè)條目。在刷新策略設(shè)置為默認(rèn)的atEnd的情況下酸舍,WidgetKit會(huì)在時(shí)間線條目中的最后一個(gè)日期之后請(qǐng)求一個(gè)新的時(shí)間線帅韧。當(dāng)時(shí)間線中的每個(gè)日期到達(dá)時(shí),WidgetKit會(huì)調(diào)用小組件的內(nèi)容閉包顯示結(jié)果啃勉。在最后一個(gè)時(shí)間線條目過后忽舟,WidgetKit會(huì)重復(fù)這個(gè)過程,要求提供者提供一個(gè)新的時(shí)間線。由于角色的健康度已經(jīng)達(dá)到了100%萧诫,提供者會(huì)以當(dāng)前時(shí)間的單一條目和刷新策略設(shè)置為never來回應(yīng)。在這種設(shè)置下枝嘶,WidgetKit不會(huì)要求另一條時(shí)間線帘饶,直到應(yīng)用程序使用WidgetCenter告訴WidgetKit請(qǐng)求新的時(shí)間線。

當(dāng)時(shí)間線改變時(shí)通知WidgetKit
當(dāng)某件事情影響到小組件的當(dāng)前時(shí)間線時(shí)群扶,或者說當(dāng)你要刷新Widget的時(shí)候及刻,App可以告訴WidgetKit請(qǐng)求新的時(shí)間線。App可以告訴WidgetKit重新加載時(shí)間線并更新小組件的內(nèi)容竞阐。要重載特定類型的widget,使用WidgetCenter

WidgetCenter.shared.reloadTimelines(ofKind: "com.testWidget")

kind參數(shù)包含與用于創(chuàng)建widget的WidgetConfiguration的值相同的字符串。當(dāng)然也只有一個(gè)widget的話也可以用reloadAllTimelines舅桩。

如果widget具有用戶可配置的屬性靶草,那么通過使用WidgetCenter來驗(yàn)證是否存在具有適當(dāng)設(shè)置的widget,從而避免不必要的重新加載幕垦。

WidgetCenter.shared.getCurrentConfigurations { result in
    guard case .success(let widgets) = result else { return }
    if let widget = widgets.first(
        where: { widget in
            let intent = widget.configuration as? SelectCharacterIntent
            return intent?.character == characterThatReceivedHealingPotion
        }
    ) {
        WidgetCenter.shared.reloadTimelines(ofKind: widget.kind)
    }
}

如果只有一個(gè)Widget丢氢,你可以使用WidgetCenter來重新加載所有widget的時(shí)間線。

WidgetCenter.shared.reloadAllTimelines()

Link WidgetUrl

從 Widget 跳轉(zhuǎn)到 App 指定界面先改,只需要用 SwiftUI Link 的方式疚察,View 的外層包上一個(gè) Link,destination 是設(shè)定好的 url仇奶,就能實(shí)現(xiàn)跳轉(zhuǎn)了貌嫡。

Link(destination: URL(string: topJump.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)!) {
  HStack() {
    Spacer()
      .frame(width: Len(15))
    // titile
    Text(topDesc)
      .fontWeight(.regular)
      .font(.system(size: Len(14)))
      .foregroundColor(Color(red: 51/255, green: 51/255, blue: 51/255, opacity: 1.0))
    Image(triangleIcon)
      .padding(.trailing, 4.0)
      .frame(width: Len(8), height: Len(8))
    Spacer()
  }
}

widgetURL:在View上加一個(gè)widgetURL ,URL是設(shè)定好的 url该溯,就能實(shí)現(xiàn)跳轉(zhuǎn)了

HStack() {
  Spacer()
    .frame(width: UMEWLen(15))
  // titile
  Text(newBean.topDesc)
    .fontWeight(.regular)
    .font(.system(size: Len(14)))
    .foregroundColor(Color(red: 51/255, green: 51/255, blue: 51/255, opacity: 1.0))
  Image(triangleIcon)
    .padding(.trailing, 4.0)
    .frame(width: Len(8), height: Len(8))
  Spacer()
}.widgetURL(URL(string: topJump.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)!)

Widget bundles

有時(shí)候我們會(huì)有多個(gè)樣式不同種類的 Widget岛抄,就需要用 @WidgetBundleBuilder 把多個(gè) Widget 放在一起

@main
struct MainBundle: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        oneWidget()
        twoWidget()
        threeWidget()
    }
}

Intent

在你的Xcode項(xiàng)目中,選擇File > New File并選擇SiriKit Intent Definition File朗伶。點(diǎn)擊 "下一步 "并在提示時(shí)保存文件弦撩。Xcode創(chuàng)建一個(gè)新的.intenttdefinition文件,并將其添加到項(xiàng)目中论皆。
Xcode從意圖定義「intent definition file」文件中生成代碼益楼。要在一個(gè)目標(biāo)「target」中使用這些代碼。
-將intent定義文件作為目標(biāo)的一個(gè)成員点晴。
-通過添加 intent 的類名到 target 屬性的 Supported Intents 部分來指定要包含在 target 中的特定 intents感凤。

稍后會(huì)上傳demo和自己遇到的一些坑
[NewWidget]https://github.com/moneyYouCai/NewWidget-iOS14

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市粒督,隨后出現(xiàn)的幾起案子陪竿,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件族跛,死亡現(xiàn)場離奇詭異闰挡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)礁哄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門长酗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人桐绒,你說我怎么就攤上這事夺脾。” “怎么了茉继?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵咧叭,是天一觀的道長。 經(jīng)常有香客問我烁竭,道長菲茬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任派撕,我火速辦了婚禮生均,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘腥刹。我一直安慰自己马胧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布衔峰。 她就那樣靜靜地躺著佩脊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪垫卤。 梳的紋絲不亂的頭發(fā)上威彰,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音穴肘,去河邊找鬼歇盼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛评抚,可吹牛的內(nèi)容都是我干的豹缀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼慨代,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼邢笙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起侍匙,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤氮惯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妇汗,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡帘不,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了杨箭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厌均。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖告唆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晶密,我是刑警寧澤擒悬,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站稻艰,受9級(jí)特大地震影響懂牧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜尊勿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一僧凤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧元扔,春花似錦躯保、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至擅羞,卻和暖如春尸变,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背减俏。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國打工召烂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人娃承。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓奏夫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親历筝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子桶蛔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353