iOS14 Widget 萬字開發(fā)指南,先人一步獲得頂級流量

2020 年 6 月 22 日,蘋果召開了第一次線上的開發(fā)者大會 - WWDC20。這次發(fā)布會上宣布了ARM架構(gòu)Mac芯片(拳打Intel)、iOS 14 ATT(腳踢Facebook),可謂是一次載入史冊(我是爸爸)的發(fā)布會了,當(dāng)然還發(fā)布了被稱為下一個頂級流量入口的Widget

踩著八月的尾巴,本次我們就來探究一下Widget。

本文會從Widget初窺Widget開發(fā)兩個維度和章節(jié)來探究一下Widget,
其中初窺章節(jié)會帶您簡單的了解一下Widget,適合應(yīng)用決策者閱讀;
開發(fā)章節(jié)會帶著您一步一步的完成設(shè)計開發(fā)Widget,適合程序員閱讀。

Widget初窺

一、Widget是什么

In iOS 14, we have a dramatic new Home screen experience, one that is much more dynamic and personalized, with a focus on widgets.
The content is the focus.
This is very important: widgets are not mini-apps.
Think of this as more projecting content from your app onto the Home screen rather than full mini-apps filled with tiny little buttons.

一句話來說:Widget不是迷你應(yīng)用程序。而是一種新的主屏幕體驗,能快速提供用戶關(guān)心的內(nèi)容是重點

image


二、Widget的特點

  • Glanceable 一目了然
  • Relevant 早韭晚菘
  • Personalized 量體裁衣
0.png

要設(shè)計一個優(yōu)秀的Widget,就要先了解Widget的全部特點,了然于胸

針對Apple提出的Glanceable、Relevant、Personalized分別用一個成語來形容就是一目了然、早韭晚菘量體裁衣

簡單來說下這幾個特點


1、Glanceable | 一目了然,一覽無余

1.png

一個優(yōu)秀的Widget要一目了然,一覽無余。

普通人每天進入“主屏幕”的次數(shù)超過90次,但是在主屏幕僅停留幾分鐘,就切換到其他App了。

所以Widget一定要充分利用狹小的屏幕展示最核心的信息,并且要簡潔明了。設(shè)計新穎,便于快速瀏覽,高效是一個優(yōu)秀Widget的核心。

用戶不用思考這個Widget怎么使用,不需要點擊任何按鈕就可以獲得最關(guān)心的信息。


2、Relevant | 早韭晚菘

image

蘋果希望Widget可以和用戶緊密結(jié)合,與用戶的行為所關(guān)聯(lián),比如早上起床,用戶希望看一下天氣;中午恰飯,用戶希望有人推薦下附近的美食;晚高峰的時候,用戶希望了解一下行車路線;晚安的時候,希望記錄下次日的行程。

為此,蘋果系統(tǒng)提供了一個叫Smart Stacks(智能疊放)的功能,Smart Stacks是一個Widgets的集合。系統(tǒng)會根據(jù)每個人的習(xí)慣,自動顯示用戶當(dāng)前時間點最需要的Widget。

<div style="align:center"><img src="https://guojunliu.github.io/images/widget/3.gif"/></div>


3、Personalized 量體裁衣

3.1 大小

Widget要能為用戶提供個性化的服務(wù),比如天氣Widget,需要能為不同的用戶提供不同細節(jié)的天氣情況。

4.png

為此Apple提供三種不同大小的小部件

  • systemSmall
  • systemMedium
  • systemLarge

其中systemSmall大小為2*2 Icon,systemMedium大小為4*2 Icon,systemLarge大小為4*4 Icon,具體的顯示效果如下

13.png

3.2 個性化配置

另一方面Widget需要能為不同城市的用戶提供當(dāng)?shù)氐奶鞖馇闆r。

為此Apple在創(chuàng)建Widget時為開發(fā)者提供了兩種類型:

  • StaticConfiguration :對于沒有用戶可配置屬性的窗口小部件,也就是用戶無需配置,展示的內(nèi)容只和用戶信息有關(guān)系。例如,顯示一般市場信息的股市窗口小部件,或顯示趨勢頭條的新聞窗口小部件。

  • IntentConfiguration :對于具有用戶可配置屬性的窗口小部件,也就是支持用戶配置及用戶意圖的推測。您使用SiriKit自定義意圖來定義屬性。例如,需要一個城市的郵政編碼的天氣小部件,或者需要一個跟蹤號的包裹跟蹤小部件。

image

需要說明的是,IntentConfiguration并不需要編寫代碼,只需要簡單的配置,Xcode 會自動幫你生成對應(yīng)的代碼和類型。

3.3 黑暗模式

此外Widget還支持系統(tǒng)的黑暗模式

image


三、Widget的本質(zhì)

Widget的本質(zhì)是一系列靜態(tài)視圖堆疊而成的集合,不同的時間點展示不同的視圖

這里要引入Widget的核心Timeline

顧名思義,Timeline就是一條時間線,在對應(yīng)的時間點發(fā)生對應(yīng)的事件

許多Widget具有可預(yù)測的時間點,在這些時間點更新其內(nèi)容是有意義的。例如,顯示天氣信息的小部件可能會在一整天內(nèi)每小時更新一次溫度。股市窗口小部件可以在公開市場時間頻繁更新其內(nèi)容,但周末則不用完全更新。通過提前計劃這些時間,生成不同的視圖放入時間線中,WidgetKit會在適當(dāng)?shù)臅r間到來時自動刷新您的窗口小部件。

這也決定了Widget基本上不能實時更新

另外值得一提的是,WidgetKit會把 Timelines 所定義的Views 結(jié)構(gòu)信息緩存到磁盤,然后在刷新的時候才通過 JIT 的方式來渲染。這使得系統(tǒng)可以在極低電量開銷下為眾多 Widgets 處理 Timelines 信息。

image


四、用戶交互

不好意思,沒有交互!??!

為了實現(xiàn)以上的特點,Apple也移除限制了Widget的一些功能

  • 不能交互
  • 不能播放動畫
  • 不能播放視頻
  • 不支持滾動
  • 不支持主動刷新視圖

唯一支持的只有用戶點擊Widget喚起主App

5.png

其中點擊喚起主App有兩種方案,分別是:

  • widgetURL
  • Link

widgetURL喚起App的點擊區(qū)域是Widget的所有區(qū)域,這種方案適合簡單元素,單一邏輯的小部件

image

對于systemSmall類型的小部件,只支持widgetURL喚起方式

針對systemMediumsystemLarge還可以使用更細分的Link喚起方式,這種喚起方式能讓小部件通過不同元素的點擊喚起App的不同頁面,讓開發(fā)者有更多的施展空間

9.png

舉個簡單的例子,widgetURL可應(yīng)用于天氣小部件,博客小部件,點擊直達App;

Link可用于備忘錄和日歷小部件,點擊不同的備忘錄和日期直接跳轉(zhuǎn)到對應(yīng)的備忘錄詳情和待辦詳情頁面

10.png


Widget初窺總結(jié)

Widget的出現(xiàn)猶如在一潭死水的iOS桌面上泛起了一片漣漪,一定會有很多App來爭奪這塊肥肉一般的流量入口。

但是仔細研究一下會發(fā)現(xiàn),Apple這次推出的Widget非常克制,并沒有非常激進,

俗話說:喜歡是放肆,但愛就是克制。這里不得不再次引用Apple在Widget介紹中出現(xiàn)頻率最高的話widgets are not mini-apps,因為Widget在設(shè)計之初就是為了能使用最少的成本,向用戶提供最核心的信息。為了盡可能的減少用戶成本(電量,網(wǎng)絡(luò)等)和提高用戶體驗,Apple在技術(shù)層面上做了很多限制,限制了非常多的功能,大大削弱了Widget的地位和重要程度,也降低了開發(fā)者實現(xiàn)的熱情和積極性

其實每年Apple更新的新技術(shù)只有很少的一部分能應(yīng)用到App上,希望這次的Widget能有動力讓大家結(jié)合自己的App,給自己的App帶來更多的流量,也能給用戶帶來更好的體驗。




Widget開發(fā)

重頭戲來啦,接下來讓我們一步一步設(shè)計編寫出優(yōu)秀的小部件吧

開始之前,首先我們要介紹下Widget的開發(fā)語言,Apple特別指定了小部件只能使用SwiftUI來開發(fā)

一、SwiftUI

image

現(xiàn)在iOS主流的開發(fā)語言還是Objective-C,那Apple為什么要選擇2019 WWDC發(fā)布迄今為止只有一年的SwiftUI呢?

首先,從一開始就將小部件實現(xiàn)多平臺化是Apple的一個目標(biāo),SwiftUI在跨設(shè)備展示的能力上是一把大殺器;

其次SwiftUI還使自動布局和暗模式等功能變得非常容易,降低了適配等開發(fā)成本,對不需要太多元素的小部件來說,SwiftUI重點關(guān)注布局的特點無疑是最合適的;

從另一方面來講,只有使用 SwiftUI 才能達到我們上邊說的對于 Widget 的限制。如果可以使用 Objective-C UIKit 的話,我們強大的開發(fā)者可能會想出無數(shù)的黑科技來忽略Apple真的小部件的限制。比如開發(fā)無法使用 UIViewRepresentable 來橋接 UIKit;

最后Apple也夾帶了自己的私心,Apple今年已經(jīng)將 Swift 語言和 SwiftUI 的重要程度提升到了一個新的高度,Swift已經(jīng)可以獨立于Foundtion框架,那么對應(yīng)的SwiftUI也應(yīng)該不依賴于UIKit框架了,強行使用SwiftUI可以使開發(fā)人員盡可能容易地將其學(xué)習(xí)其內(nèi)容并應(yīng)用于iOS,iPadOS和macOS,

畢竟5月份卡位第20位Objective-C在6月份已經(jīng)跌出了前20

image

這里要重點說明一下,Widget只要使用任何 UIKit 的元素就會直接 Crash


二、將小部件目標(biāo)添加到您的應(yīng)用

窗口小部件擴展模板提供了創(chuàng)建窗口小部件的起點。單個小部件擴展可以包含多種小部件。例如,一個體育應(yīng)用程序可能有一個顯示團隊信息的小部件,另一個顯示游戲時間表的小部件。一個小部件擴展可以包含兩個小部件。盡管建議將所有窗口小部件包含在一個窗口小部件擴展中,但如有必要,可以添加多個擴展。

  1. 在Xcode中打開您的應(yīng)用程序項目,然后選擇“文件”>“新建”>“目標(biāo)”。

  2. 從“應(yīng)用程序擴展”組中,選擇“窗口小部件擴展”,然后單擊“下一步”。

  3. 輸入您的包名。

  4. 如果窗口小部件提供了用戶可配置的屬性,請選中Include Configuration Intent復(fù)選框。

單擊完成。

image

重點:小部件不僅支持Swift項目,同樣也支持Objective-C項目,OC小伙伴不用擔(dān)心啦

創(chuàng)建完小部件之后,我們會多出一個SmileEverydayWidget.swift文件,這已經(jīng)是一個可以run起來的小部件了,因為我們接下來要逐個方法來分析,所以先將文件全文展示如下

//
//  SmileEverydayWidget.swift
//  SmileEverydayWidget
//
//  Created by steve on 2020/8/28.
//

import WidgetKit
import SwiftUI

struct Provider: TimelineProvider {
    public typealias Entry = SimpleEntry

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

    public func timeline(with 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)
    }
}

struct SimpleEntry: TimelineEntry {
    public let date: Date
}

struct PlaceholderView : View {
    var body: some View {
        Text("Placeholder View")
    }
}

struct SmileEverydayWidgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        Text(entry.date, style: .time)
    }
}

@main
struct SmileEverydayWidget: Widget {
    private let kind: String = "SmileEverydayWidget"

    public var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider(), placeholder: PlaceholderView()) { entry in
            SmileEverydayWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}

struct SmileEverydayWidget_Previews: PreviewProvider {
    static var previews: some View {
        /*@START_MENU_TOKEN@*/Text("Hello, World!")/*@END_MENU_TOKEN@*/
    }
}

這里的概念和代碼比較多,接下來我們一個一個來解釋


三、Widget API

首先我們從帶有main字段的方法來說起,

@main
struct SmileEverydayWidget: Widget {
    private let kind: String = "com.steve.liu.smileEverydayWidget"

    public var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider(), placeholder: PlaceholderView()) { entry in
            SmileEverydayWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

大家都知道帶有main標(biāo)識的方法都是程序的入口

這段代碼使用SwiftUI聲明了一個名為SmileEverydayWidget的小部件,其中StaticConfiguration是小部件的初始化方法,它有幾個參數(shù):

  1. kind
  2. provider
  3. placeholder
image

其中

kind標(biāo)識小部件的字符串,并且應(yīng)描述小部件所代表的內(nèi)容。即小部件的包名

provider時間線提供者

PlaceholderView占位視圖

同時也提供了一些方法,例如

  • configurationDisplayName()設(shè)置小部件顯示的名稱
  • description()設(shè)置小部件的描述
  • supportedFamilies()設(shè)置小部件支持的尺寸

這里有一個重點,為了使某個應(yīng)用程序的窗口小部件出現(xiàn)在窗口小部件庫中,用戶必須在安裝該應(yīng)用程序后至少啟動一次包含該窗口小部件的應(yīng)用程序。


四、WidgetEntryView

WidgetEntryView就是使用SwiftUI布局的小部件視圖

struct SmileEverydayWidgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        Text(entry.date, style: .time)
    }
}

例如這個小部件視圖就簡單的展示了當(dāng)前的時間

接下來我們可以將默認的布局更改為我們自己想要的布局,例如我在設(shè)置了顯示文本的字體和小部件的背景圖

struct SmileEverydaWidgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        
        return Text(entry.message)
            .background(Image(entry.backgroundImageStr))
            .font(.callout)
    }
}


五、PlaceholderView

每種小部件都需要提供占位符UI。

占位符UI是窗口小部件的默認內(nèi)容。

它應(yīng)該代表您的小部件類型,但僅此而已。

此用戶界面中不應(yīng)有任何用戶數(shù)據(jù)。

17.png

想象一下

如果在用戶的主屏幕上出現(xiàn)如下的場景,那么你的小部件離被移除可能已經(jīng)不遠了

image


六、TimelineEntry

我們知道小部件是按照時間線來展示的,TimelineEntry時間線上的一個個條目

struct SimpleEntry: TimelineEntry {
    public let date: Date
}

TimelineEntry有一個必須有的屬性就是date,也就是這個條目在時間線上的具體時間

另外開發(fā)者可以在TimelineEntry里自定義各種屬性,用來給小部件視圖提供數(shù)據(jù)

例如我在TimelineEntry里自定義了messagebackgroundImageStr屬性,用來顯示小部件上的文字和背景圖片

struct SimpleEntry: TimelineEntry {
    public let date: Date
    public let message: String
    public let backgroundImageStr : String
}

七、TimelineProvider

TimelineProvider 是一個提供了上述我們所說的TimelineEntry集合的對象

image

我們來看下具體的代碼:

struct Provider: TimelineProvider {
    public typealias Entry = SimpleEntry

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

    public func timeline(with 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)
    }
}

其中包含兩個方法

  • snapshot()
  • timeline()

1、snapshot 快照

為了在小部件庫中顯示小部件,WidgetKit 要求提供者提供預(yù)覽快照, 即snapshot(),這個方法里主要提供了一些示例數(shù)據(jù),最好是真實數(shù)據(jù),用于時間線不能展示的時候展示給用戶

需要說明的是,快照是系統(tǒng)需要快速顯示單個條目的位置。

因此,您的擴展程序必須盡快返回視圖,因為這樣做時,用戶會在iOS上漂亮的Widget Gallery中看到真正的Widget。

這不是我們在設(shè)計時必須提供的屏幕截圖或圖像。這是用戶在iOS,iPadOS和macOS上真正的小部件體驗。

在大多數(shù)情況下,時間軸的第一個條目和快照可以作為同一條目返回,因此,在“小工具庫”中看到的就是用戶將其添加到設(shè)備中時得到的內(nèi)容。

image

例如我們在Widget Gallery中添加電池小部件時,小部件此時在Widget Gallery中展示的就是當(dāng)前設(shè)備電池信息的實時數(shù)據(jù)的快照,而不是一些虛假的數(shù)據(jù),這個時候小部件的數(shù)據(jù)是什么樣子,用戶添加到主屏幕上之后小部件的數(shù)據(jù)就是什么樣子,從而提高用戶的體驗。

對比

小部件里有兩個比較類似的概念,PlaceholderViewsnapshot,都是一種占位解決方案,不同的是PlaceholderView是在主屏幕上無法快速獲取數(shù)據(jù)時的一種占位視圖,不至于顯示loading或者白屏給用戶看;而snapshot主要用于Widget Gallery中,用來提高用戶體驗的,一般來說,snapshot就是時間線的第一幀

2、timeline

在請求初始快照后,WidgetKit調(diào)用timeline以請求提供者的常規(guī)時間軸。時間軸由一個或多個時間軸條目TimelineEntry以及一個重載策略ReloadPolicy組成,該重載策略通知WidgetKit何時請求后續(xù)時間軸。

關(guān)于重載策略,提供了以下幾種策略

  • atEnd: 是指 Timeline 執(zhí)行到最后一個時間片的時候再刷新。
  • atAfter: 是指在某個時間以后有規(guī)律的刷新
  • never:是指以后不需要刷新了。什么時候需要重新刷新需要 App 重新告知 Widget

根據(jù)上邊的分析,我們可以將TimelineProvider改造如下

struct Provider: TimelineProvider {
    public typealias Entry = SimpleEntry

    public func snapshot(with context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let date = Date()
        let message = "蒹葭蒼蒼,白露為霜。所謂伊人,在水一方。"
        let backgroundImageStr = "bg7"
        
        
        let entry = SimpleEntry(date: date, message: message, backgroundImageStr: backgroundImageStr)
        completion(entry)
    }

    public func timeline(with context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []
        
        var currentDate = Date()
        var nextUpdateDate = Calendar.current.date(byAdding: .second, value: 3, to: currentDate)!
        
        let message = "蒹葭蒼蒼,白露為霜。所謂伊人,在水一方。\n溯洄從之,道阻且長;溯游從之,宛在水中央。\n蒹葭凄凄,白露未晞。所謂伊人,在水之湄。"
        let backgroundImageStr = "bg"
        
        
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        var currentDateStr = ""
        var nextUpdateDateStr = ""

        let longUuid = UUID().uuidString
        let range: Range = longUuid.range(of: "-")!
        let location: Int = longUuid.distance(from: longUuid.startIndex, to: range.lowerBound)
        let uuid = longUuid.prefix(location)
        
        for i in 1 ..< 10 {
            
            var msg = ""
            
            currentDate = nextUpdateDate;
            nextUpdateDate = Calendar.current.date(byAdding: .second, value: 3, to: currentDate)!
            
            currentDateStr = formatter.string(from: currentDate)
            nextUpdateDateStr = formatter.string(from: nextUpdateDate)
            
            msg.append(message)
            msg.append("\n時間軸ID " + uuid);
            msg.append("\n時間軸第" + String(i+1) + "個視圖")
            msg.append("\n本次視圖開始時間 " + currentDateStr)
            msg.append("\n下次視圖開始時間 " + nextUpdateDateStr)
            
            let entry = SimpleEntry(date: currentDate, message: msg, backgroundImageStr: backgroundImageStr+String(i))
            
            entries.append(entry)
            print(String(i))
        }
        
        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

這段代碼里我們提供了一個古詩詞的快照

并生成了一個時間線,其中時間線里包含了10個entry,且每個entry 間隔10s

entry文本拼接顯示了

  • 展示文本(詩經(jīng))
  • 時間軸ID
  • 本次視圖開始時間
  • 下次視圖開始時間

下邊我們來看一下真實run出來的效果

21.gif

到此為止我們就有了一個可以在主屏幕上展示的小部件了

并且能根據(jù)時間線展示不同的視圖了。


七、Reload Timeline 使小部件保持最新

為了使我們的小部件能隨時提供最新的,而不是過期的信息,我們需要不時的對小部件進行更新。

我們已經(jīng)知道了小部件的本質(zhì)是一系列的視圖堆疊,那么更新小部件就是更新這些視圖。

28.png

比如一個有三個視圖的小部件,預(yù)測了現(xiàn)在和未來3小時的天氣預(yù)報,這個小部件顯示步驟如下:

image

但是有一個很重要的問題就是時間線是我們預(yù)測出來的,是預(yù)測就會有偏差。比如天氣預(yù)報,預(yù)報2小時后有雨,但是隨著天氣的變化,2小時后變成晴天了,這個時候我們?nèi)绻桓滦〔考系臅r間線,就會在2小時后給用戶提供錯誤的信息。

為此我們需要在有信息變化的時候重新顯示新的視圖,如下圖:

image

為了是我們的小部件信息準(zhǔn)確無誤,首先我們需要了解下小部件是如何刷新的

很不幸,Widget 的刷新完全由 WidgetCenter控制。開發(fā)者無法通過任何 API 去主動刷新 Widget 的頁面,只能告知 WidgetCenter,Timeline 需要刷新了

所以我們不能直接刷新小部件的視圖,而是要通過生成一個新的時間線來替換舊的時間線,Reload Timeline 并不是直接刷新 Widget,而是 WidgetCenter 重新向 Widget 請求下一階段的數(shù)據(jù)。

其中Reload Timeline分為兩種方式

  • System Reloads
  • App Reloads

1、System Reloads

這個行為由系統(tǒng)主動發(fā)起,會調(diào)用一次 Reload Timeline 向 Widget 請求下一階段刷新的數(shù)據(jù)。系統(tǒng)除了會按時發(fā)起 System Reloads 之外,還會動態(tài)決策每個不同的 TimeLine 的 System Reloads 的頻次。比如被點擊次數(shù)很大程度上直接決定了 System Reloads 的頻率,點擊率越高,更新頻次越快,當(dāng)然還有一些由于設(shè)備環(huán)境變化觸發(fā)的行為也會觸發(fā) System Reloads,比如設(shè)備時間進行了變更。

42.png

很顯然這種方案不能很好的解決我們上邊的問題

2、App Reloads

這種行為指的是App主動通知小部件,你需要更新信息了。這里邊根據(jù)App的當(dāng)前的前后臺狀態(tài)又分為兩種方式

  • 應(yīng)用在前臺運行
  • 應(yīng)用在后臺運行

當(dāng)應(yīng)用在前臺運行的時候,App 可以直接使用WidgetCenter的 API 來 Reload Timeline;而當(dāng)應(yīng)用處于后臺時,可以使用后臺推送(Background Notification)來 Reload Timeline。

image

除了這些,給Timeline設(shè)定合適的刷新策略也是很重要的手段

合理的組合使用這些刷新機制,能夠極大的提高Widget信息的準(zhǔn)確性

46.png


八、交互

前邊我們說過,widget和app交互有兩種方式SwiftUI widgetURL APISwiftUI Link API

這兩種方式的本質(zhì)都是URL Schemes,只要監(jiān)聽SceneDelegatescene:openURLContexts:就可以了

由于Schemes大家都太熟悉了,關(guān)于如何高效快速準(zhǔn)確的傳遞參數(shù),這里就不展開講了。

image


九、設(shè)計漂亮的小部件

如果你已經(jīng)看到了這里,并且已經(jīng)理解了上述的講解,你已經(jīng)具備了開發(fā)小部件的能力。

那么有哪些關(guān)鍵點能給自己的小部件錦上添花呢?

去除額外的App信息:系統(tǒng)會在小部件下方自動顯示你的應(yīng)用名稱,因此你無需在內(nèi)容中重復(fù)App的名稱,Icon,而是要通過顏色,布局和圖像來聯(lián)系您的App

簡潔的描述。小部件庫中顯示的描述可以幫助人們理解每個小部件的功能。從動作動詞開始描述通常效果很好;例如,“查看當(dāng)前天氣狀況和位置預(yù)測”或“跟蹤即將舉行的活動和會議”。避免包含不必要的短語來引用窗口小部件本身,例如“此窗口小部件顯示...”,“使用此窗口小部件...”或“添加此窗口小部件”。

舒適的信息密度:一覽無余。當(dāng)內(nèi)容顯得稀疏時,小部件可能看起來是多余的;當(dāng)內(nèi)容太密集時,小部件將無法瀏覽。如果要包含很多信息,請避免讓小部件成為難以解析的項的拼貼。尋求整理內(nèi)容的方法,以便人們可以立即掌握關(guān)鍵部分,并以更長的時間查看相關(guān)細節(jié)。您可能還考慮創(chuàng)建一個較大的小部件,并尋找可以用圖形替換文本而又不會失去清晰度的位置。

明智地使用顏色:豐富,美麗的色彩吸引眼球,但它們絕不能阻止人們一眼就吸收小部件的信息。使用顏色可以增強小部件的外觀,而不會與小部件的內(nèi)容競爭。

使用系統(tǒng)字體,支持系統(tǒng)功能:例如 支持黑暗模式;使用SF Pro和使用系統(tǒng)字體;文本可縮放。

image

設(shè)計一個真實的預(yù)覽以顯示在小部件庫中:突出顯示小部件的外觀和功能可幫助人們做出明智的決定,并鼓勵他們添加小部件。您可以在小部件預(yù)覽中顯示真實數(shù)據(jù),但是如果數(shù)據(jù)生成或加載所需的時間太長,請顯示真實的模擬數(shù)據(jù)。

設(shè)計占位符內(nèi)容,以幫助人們識別您的小部件。小部件在加載數(shù)據(jù)時顯示占位符內(nèi)容。通過將UI的靜態(tài)部分與代表實際內(nèi)容的半透明形狀結(jié)合起來,可以創(chuàng)建有效的預(yù)覽。例如,您可以使用不同寬度的矩形來建議文本行,并使用圓環(huán)或正方形代替字形和圖像。

image

圖片適配屏幕尺寸:確保圖片在大部件和小部件下都不會壓縮

image


十圍之木,始生如蘗

簡單的總結(jié)一下

一個優(yōu)秀的小部件是完全可以提高用戶體驗,成為很好的流量入口,給App帶來巨大的商業(yè)價值。

但是要設(shè)計一個優(yōu)秀的小部件也并非易事。

本文拋磚引玉,希望大家能設(shè)計出更多優(yōu)秀的小部件。

本次的Widget指北到這里就結(jié)束了,萬字不易,多多傳播。

喜歡我你就關(guān)注我,

有話說你就評論我,

都不干你就點個贊

image


Demo


參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容