SwiftUI-Widget 使用及避坑指南

iOS Widget簡(jiǎn)單介紹( 只介紹iOS 14 以后Widget相關(guān)內(nèi)容):

Widget 是 iOS 14 重磅推出的新功能席揽,使得用戶(hù)可以在主屏幕添加小組件叹坦,快速瀏覽 app 提供的重要信息稽穆。
用戶(hù)可以通過(guò) Widget 對(duì)主屏幕進(jìn)行個(gè)性化定制赂苗,但是 iOS 14 的 Widget 跟其他系統(tǒng)上的小組件有很大的區(qū)別屈糊。在 Widget 的設(shè)計(jì)上蘋(píng)果也保持了一貫的克制碧查,定位于輕量化、僅用作關(guān)鍵信息的展示耗帕。比如系統(tǒng)自帶 Widget 中的股票、天氣袱贮、電量仿便、運(yùn)動(dòng)信息,他們的共同特征是更新頻率高攒巍、提供的信息重要嗽仪,讓用戶(hù)不用打開(kāi) app 就可以瀏覽關(guān)心的內(nèi)容。

相關(guān)限制:

蘋(píng)果基于上面的設(shè)計(jì)定位柒莉,同時(shí)也為了節(jié)省系統(tǒng)資源保證續(xù)航闻坚,對(duì) Widget 的做了一些限制:
不支持動(dòng)畫(huà),僅支持靜態(tài)頁(yè)面展示兢孝。
更新頻率由系統(tǒng)通過(guò)機(jī)器學(xué)習(xí)來(lái)動(dòng)態(tài)分配窿凤。
不支持拖拽、滾動(dòng)等復(fù)雜的交互跨蟹,不支持 Switch 等控件雳殊。
用戶(hù)點(diǎn)擊 Widget 一定會(huì)跳轉(zhuǎn)到 App。
支持三種不同大小的樣式

適應(yīng)不同的屏幕尺寸

iOS Widget簡(jiǎn)單介紹( 只介紹iOS 14 以后Widget相關(guān)內(nèi)容):

Widget 是 iOS 14 重磅推出的新功能窗轩,使得用戶(hù)可以在主屏幕添加小組件夯秃,快速瀏覽 app 提供的重要信息。
用戶(hù)可以通過(guò) Widget 對(duì)主屏幕進(jìn)行個(gè)性化定制,但是 iOS 14 的 Widget 跟其他系統(tǒng)上的小組件有很大的區(qū)別仓洼。在 Widget 的設(shè)計(jì)上蘋(píng)果也保持了一貫的克制介陶,定位于輕量化、僅用作關(guān)鍵信息的展示色建。比如系統(tǒng)自帶 Widget 中的股票哺呜、天氣、電量镀岛、運(yùn)動(dòng)信息弦牡,他們的共同特征是更新頻率高、提供的信息重要漂羊,讓用戶(hù)不用打開(kāi) app 就可以瀏覽關(guān)心的內(nèi)容驾锰。

相關(guān)限制:

蘋(píng)果基于上面的設(shè)計(jì)定位,同時(shí)也為了節(jié)省系統(tǒng)資源保證續(xù)航走越,對(duì) Widget 的做了一些限制:
不支持動(dòng)畫(huà)椭豫,僅支持靜態(tài)頁(yè)面展示。
更新頻率由系統(tǒng)通過(guò)機(jī)器學(xué)習(xí)來(lái)動(dòng)態(tài)分配旨指。
不支持拖拽赏酥、滾動(dòng)等復(fù)雜的交互,不支持 Switch 等控件谆构。
用戶(hù)點(diǎn)擊 Widget 一定會(huì)跳轉(zhuǎn)到 App裸扶。
支持三種不同大小的樣式

適應(yīng)不同的屏幕尺寸

屏幕尺寸 - portrait 小部件-systemSmall 中型部件-systemMedium 大部件-systemLarge
414x896 pt (XR/XsMax/11/11ProMax) 169x169pt 360x169pt 360x379pt
375x812 pt (X/Xs/11 Pro) 155x155 pt 329x155 pt 329x345 pt
414x736 pt (6p/6sp/7p) 159x159 pt 348x159 pt 348x357 pt
375x667 pt (6/6s/7/8) 148x148 pt 321x148 pt 321x324 pt
320x568 pt (5/5s/SE) 141x141 pt 292x141 pt 292x311 pt

開(kāi)發(fā)要求:

開(kāi)發(fā)工具 Xcode 12 以上版本
開(kāi)發(fā)語(yǔ)言 Swift和SwiftUI
手機(jī)系統(tǒng)要求 14以上

Widget 創(chuàng)建:

1.Widget作為項(xiàng)目的一個(gè)組件,創(chuàng)建之前需要先創(chuàng)建一個(gè)iOS的項(xiàng)目搬素,項(xiàng)目創(chuàng)建成功后點(diǎn)擊:File->New->Target添加Widget Extension Target 點(diǎn)擊Next呵晨。

2.輸入Widget組件名,取消勾選熬尺,點(diǎn)擊Finish就可以了摸屠。Include Configuration Intent:是否支持用戶(hù)配置。

3.關(guān)于預(yù)覽:
本項(xiàng)目會(huì)提示需要升級(jí)粱哼,新創(chuàng)建項(xiàng)目的沒(méi)有該問(wèn)題
(運(yùn)行widget target 模擬器調(diào)試打日志有時(shí)不顯示)

多個(gè)Widget和小季二、大、中頁(yè)面數(shù)據(jù)布局

如何定義多個(gè)Widget揭措,并且小胯舷、中、大的布局完全不同绊含?

iOS14中Widget是支持通過(guò)創(chuàng)建一個(gè)擴(kuò)展項(xiàng)目返回一個(gè)或多個(gè)小部件的需纳,可以使您的應(yīng)用提供多種小部件選擇。并且在項(xiàng)目中視圖通過(guò)WidgetFamily的枚舉自定義自己想要的組件和布局艺挪。

WidgetFamily枚舉

public enum WidgetFamily : Int, RawRepresentable, CustomDebugStringConvertible, CustomStringConvertible {

    /// A small widget.
    case systemSmall

    /// A medium-sized widget.
    case systemMedium

    /// A large widget.
    case systemLarge
}

默認(rèn)模版代碼不翩,只能支持展示一類(lèi)型種的一種樣式

@main //widget 主入口兵扬,系統(tǒng)從這里加載
struct WidgetTest: Widget {
  //kind的值是widget的唯一標(biāo)識(shí)
    let kind: String = "Widget"
    var body: some WidgetConfiguration {//初始化配置代碼
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
           WidgeEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")//編輯頁(yè)面展示的標(biāo)題
        .description("This is an example widget.")//編輯頁(yè)面展示的描述內(nèi)容
        .supportedFamilies([.systemSmall,.systemMedium,.systemLarge])// 如何實(shí)現(xiàn)預(yù)覽里面small樣式展示不同樣式
    }
}

可以通過(guò)修改原Widget入口文件方法添加更多配置來(lái)支持多個(gè)Widget,相同類(lèi)型不同樣式。

@main
struct SwiftWidgetsBundle: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        Widget1()
        Widget2()
        Widget3()
        Widget4()
        ...
    }
}

與主應(yīng)用交互:

根據(jù)官方文檔的描述口蝠,點(diǎn)擊Widget窗口喚起APP進(jìn)行交互指定跳轉(zhuǎn)支持兩種方式:

widgetURL:點(diǎn)擊區(qū)域是Widget的所有區(qū)域器钟,代碼如下。
if family == .systemSmall {  // 小
  VStack(alignment: .center, spacing: 20, content: {
      Text("\(entry.quotes.date) at \(entry.quotes.place) ")
          .font(.system(size: 9))
          .foregroundColor(.gray)
  })
  .widgetURL(URL(string: "https://www.baidu.com/small"))

}

Link:通過(guò)Link修飾妙蔗,允許讓界面上不同元素產(chǎn)生點(diǎn)擊響應(yīng)傲霸。

if family == .systemMedium { // 中
  VStack(alignment: .center, spacing: 8, content: {
      Link(destination: URL(string: "https://www.baidu.com/medium/1")!) {
          Text(entry.quotes.content[0])
              .font(.system(size: 17))
              .foregroundColor(.black)
              .frame(maxWidth:.infinity, alignment: .leading)
      }
     
      Text("\(entry.quotes.date) at \(entry.quotes.place) ")
          .font(.system(size: 12))
          .foregroundColor(.gray)
          .frame(maxWidth:.infinity, alignment: .trailing)
          .frame(height: 20, alignment: .bottom)
  })
 
}

注!:systemSmall(小組件)只支持widgetURL眉反,而systemMedium(中組件)和 systemLarge(大組件)則都支持昙啄。Link:更希望的是不同元素的點(diǎn)擊響應(yīng)。

在主項(xiàng)目的SceneDelegate代理方法中接收回調(diào)

- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
    /// 根據(jù)不同的URL回調(diào)做出響應(yīng)
    NSLog(@"%@",URLContexts);
}

或 AppDelegate 中的

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*,id> *)options{
//處理
    return YES;
}

數(shù)據(jù)更新時(shí)機(jī)

這里關(guān)于刷新策略寸五,根據(jù)官方文檔來(lái)看梳凛,Timeline的刷新策略是會(huì)延遲的,并不一定根據(jù)你設(shè)定的時(shí)間來(lái)梳杏。同時(shí)官方規(guī)定每個(gè)配置的窗口小部件每天都接收有限數(shù)量的刷新韧拒,
(官方文檔:https://developer.apple.com/documentation/widgetkit/timelineprovider

導(dǎo)致無(wú)法預(yù)測(cè)何時(shí)更新Widget。即使設(shè)置了某個(gè)時(shí)間再次獲取時(shí)間軸本身進(jìn)行更新十性,也無(wú)法保證iOS會(huì)同時(shí)更新視圖叛溢。從而造成一定的Widget頁(yè)面更新延遲。

蘋(píng)果因?yàn)樘峁┝艘粋€(gè)單獨(dú)的方法劲适,調(diào)用來(lái)重新加載所有窗口小部件
/// 控件的所有已配置小部件重新加載時(shí)間線(xiàn)
/// 包含應(yīng)用程序楷掉。

WidgetCenter.shared.reloadAllTimelines()

刷新次數(shù)限制

Refreshing Widgets Efficiently
Each configured widget receives a limited number of refreshes every day. Several factors affect how many refreshes a widget receives, such as whether the containing app is running in the foreground or background, how frequently the widget is shown onscreen, and what types of activities the containing app engages in.

每個(gè)配置的小部件每天都會(huì)收到有限的刷新次數(shù)。有幾個(gè)因素會(huì)影響小部件接收的刷新次數(shù)霞势,例如包含的應(yīng)用程序是在前臺(tái)還是后臺(tái)運(yùn)行烹植,小部件在屏幕上顯示的頻率,以及包含的應(yīng)用程序參與的活動(dòng)類(lèi)型支示。

在Xcode中調(diào)試小部件時(shí)刊橘,WidgetKit不會(huì)施加此限制鄙才。要驗(yàn)證小部件的行為是否正確颂鸿,請(qǐng)?jiān)赬code的調(diào)試器之外測(cè)試應(yīng)用程序和小部件的行為。

當(dāng)你的應(yīng)用程序位于前臺(tái)攒庵、有活動(dòng)媒體會(huì)話(huà)或使用標(biāo)準(zhǔn)位置服務(wù)時(shí)嘴纺,刷新不計(jì)入小部件的每日限制。有關(guān)媒體會(huì)話(huà)和定位服務(wù)的更多信息浓冒,請(qǐng)參閱doc://com.apple.documentation/documentation/avfoundation/avaudiosession使用標(biāo)準(zhǔn)定位服務(wù)栽渴。

數(shù)據(jù)共享:

主要是使用App Group來(lái)實(shí)現(xiàn)。
如登錄態(tài)同步等

Swift 與OC 相互調(diào)用:

Widget 中的Swift 調(diào)用主項(xiàng)目的OC 調(diào)用
使用橋接方法稳懒,且引入的文件必須 在Target Membership 關(guān)聯(lián)對(duì)應(yīng)的 Widget Target闲擦。

注意:

  • 用戶(hù)初次安裝未啟動(dòng),無(wú)法添加Widget組件。
  • 圖片加載不支持異步墅冷,只能同步加載好后進(jìn)行顯示
  • 在創(chuàng)建文件時(shí)一般都會(huì)在Target Membership 進(jìn)行勾選相應(yīng)的Target纯路,默認(rèn)生產(chǎn)的模版的入口不需要勾選主target ,否則會(huì)報(bào)錯(cuò)
duplicate symbol '_main' in:
    /Users/XXX/Library/Developer/Xcode/DerivedData/XXX-hghyirqieliknyckkefqaeomgvms/Build/Intermediates.noindex/XXX.build/Debug-iphoneos/XXX.build/Objects-normal/arm64/main.o
    /Users/XXX/Library/Developer/Xcode/DerivedData/XXX-hghyirqieliknyckkefqaeomgvms/Build/Intermediates.noindex/XXX.build/Debug-iphoneos/XXX.build/Objects-normal/arm64/XXXWidget.o
ld: 1 duplicate symbol for architecture arm64
clang_bk: error: linker command failed with exit code 1 (use -v to see invocation)
[$] waitpid = 64263 
[$] run clang_bk fail,not exist 
nagain clang exit 
Command /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang failed with exit code 255
  • Widget中支持的字體和主工程支持的字體不一樣寞忿,如果UI中設(shè)置的字體沒(méi)有達(dá)到預(yù)期效果建議打印一下支持的字體驰唬。
    swift:
var familNames:[String] = []
        familNames = UIFont.familyNames
        for fami in familNames {
            print("---familNames",fami)
            let namesArr = UIFont.fontNames(forFamilyName: fami)
            for name in namesArr {
                print("---famil fontname",name)
            }
        }
  print(familNames)
  • 埋點(diǎn)
    Widget 的曝光事件我們是無(wú)法感知的,由于點(diǎn)擊 Widget 會(huì)直接跳轉(zhuǎn)到主 app腔彰,所以我們?cè)谔D(zhuǎn)到主 app 的 URL 上增加了埋點(diǎn)參數(shù)叫编,主 app 解析 URL 中的參數(shù)調(diào)用 UT 來(lái)埋點(diǎn)。
  • 包大小
    主工程是使用OC霹抛,Widget開(kāi)發(fā)會(huì)使用Swift及SwiftUI 會(huì)引入新的庫(kù)搓逾,會(huì)導(dǎo)致包有一定的增加。
  • swiftUI布局的坑點(diǎn):
    比如
    Image("imageName")
    .resizable()
    .frame(width: fitWidth(22), height: fitWidth(22))
    圖片設(shè)置大小要先設(shè)置resizable上炎,否則不生效,
    SwiftUI 設(shè)置方法先后順序不同會(huì)導(dǎo)致不同的UI效果

參考:
如何用iOS14 Widget小組件自定義玩法

iOS14 Widget初體驗(yàn)

如何進(jìn)行 iOS Widget 開(kāi)發(fā)?
iOS小組件Widget踩坑

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恃逻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子藕施,更是在濱河造成了極大的恐慌寇损,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裳食,死亡現(xiàn)場(chǎng)離奇詭異矛市,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)诲祸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)浊吏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人救氯,你說(shuō)我怎么就攤上這事找田。” “怎么了着憨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵墩衙,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我甲抖,道長(zhǎng)漆改,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任准谚,我火速辦了婚禮挫剑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘柱衔。我一直安慰自己樊破,他們只是感情好愉棱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著哲戚,像睡著了一般羽氮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惫恼,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天档押,我揣著相機(jī)與錄音,去河邊找鬼祈纯。 笑死令宿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的腕窥。 我是一名探鬼主播粒没,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼簇爆!你這毒婦竟也來(lái)了癞松?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤入蛆,失蹤者是張志新(化名)和其女友劉穎响蓉,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體哨毁,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枫甲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扼褪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片想幻。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖话浇,靈堂內(nèi)的尸體忽然破棺而出脏毯,到底是詐尸還是另有隱情,我是刑警寧澤幔崖,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布食店,位于F島的核電站,受9級(jí)特大地震影響岖瑰,放射性物質(zhì)發(fā)生泄漏叛买。R本人自食惡果不足惜砂代,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一蹋订、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刻伊,春花似錦露戒、人聲如沸椒功。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)动漾。三九已至,卻和暖如春荠锭,著一層夾襖步出監(jiān)牢的瞬間旱眯,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工证九, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留删豺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓愧怜,卻偏偏與公主長(zhǎng)得像呀页,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拥坛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容