iOS14 Widget初體驗(yàn)

前言

iOS14發(fā)布已經(jīng)有一段時(shí)間了,更新完之后最讓我好奇的就是這個(gè)Widget功能。從用戶(hù)使用到開(kāi)發(fā)者開(kāi)發(fā)上與iOS13有了很大的區(qū)別,用戶(hù)可以更加個(gè)性化的去構(gòu)建自己的桌面。

看完這篇博客,你可以大概的了解一些關(guān)于iOS14 Widget信息,和如何嘗試編寫(xiě)一個(gè)簡(jiǎn)單的Widget小組件。

Widget代碼演示部分可以直接跳至第三部分:iOS14 Widget的代碼實(shí)現(xiàn)。

1:與iOS13 Widget區(qū)別

不同點(diǎn)

最直觀的區(qū)別就是像官方演示視頻展示的那樣,可以直接在iPhone主屏幕進(jìn)行組件的布局 如下圖

image
  • iOS13上的Widget是一個(gè)整體,在負(fù)1屏幕上的一個(gè)顯示區(qū)域內(nèi)的布局顯示它們有專(zhuān)門(mén)的頁(yè)面進(jìn)行管理。而在14上每一個(gè)Widget都是一個(gè)單獨(dú)的組件,他們可以自己管理自己通過(guò)簡(jiǎn)單的長(zhǎng)按下壓手勢(shì)進(jìn)行編輯和刪除操作。
  • iOS13上的Widget想要改變大小,只能通過(guò)一個(gè)右上角的下標(biāo)箭頭進(jìn)行編輯擴(kuò)展。一般就兩種尺寸:NCWidgetDisplayMode的expanded和Compact屬性。而在14改變成了三種尺寸:Widget只支持3種尺寸systemSmall (2x2)、 systemMedium (4x2)、 systemLarge(4x4) (以單個(gè)APP-icon大小為單元)。下圖對(duì)比以13-Widget小歷和14-Widget自帶日歷為對(duì)比。

iOS13-Widget


image

iOS14-Widget


image
  • iOS13上的Widget不支持部件堆疊,通過(guò)上下滑動(dòng)切換。而在14上支持。14還支持在任意主界面上進(jìn)行Widget頁(yè)面的編輯。

除了顯示上的區(qū)別外,代碼的實(shí)現(xiàn)上也有不同

  • iOS13上的Widget默認(rèn)是根據(jù)開(kāi)發(fā)語(yǔ)言的選擇上為基礎(chǔ),生成組件。如OC的項(xiàng)目生成OC的Widget組件,Swift的項(xiàng)目生成Swift的Widget組件。而且14上則是默認(rèn)里Swift和SwiftUI為語(yǔ)言基礎(chǔ)進(jìn)行開(kāi)發(fā)。

  • 在14上使用的是新的WidgetKit框架而非13的Today Extension。UI部分只能夠使用SwiftUI來(lái)開(kāi)發(fā),所以需要一定的SwiftUI和Swift基礎(chǔ)。

  • 14的Widget需要依賴(lài)Xcode12.0版本。

相同點(diǎn)

  • 同一個(gè)APP都可以開(kāi)發(fā)多個(gè)組件,也可以同時(shí)使用多個(gè)組件。

  • 可以和主程序APP進(jìn)行交互。

2:iOS14 Widget的特點(diǎn)

  • 14的Widget可自定義的更強(qiáng)了,可以和主屏幕的APP一起排布,可以堆疊節(jié)省空間,集成siri的智能化推薦。

  • 蘋(píng)果在開(kāi)發(fā)上更加的建議去如何設(shè)計(jì)一個(gè)漂亮的小部件(Widget),如通過(guò)顏色,版式和圖像傳達(dá)您的品牌,舒適的信息密度等,詳情可查看蘋(píng)果開(kāi)發(fā)文檔

  • 適應(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
  • 支持Widget配置和交互

3:iOS14 Widget的代碼實(shí)現(xiàn)

1:項(xiàng)目創(chuàng)建

1:Widget作為項(xiàng)目的一個(gè)組件,創(chuàng)建之前需要先創(chuàng)建一個(gè)iOS的項(xiàng)目。

  • Create a new Xcode Project
  • 語(yǔ)言選擇:Swift/OC

2:項(xiàng)目創(chuàng)建成功后點(diǎn)擊:File->New->Target添加Widget Extension Target 點(diǎn)擊Next。


image

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

image

4:如圖可以看到默認(rèn)生成的模板,默認(rèn)預(yù)覽組件的尺寸systemSmall。但是在真機(jī)上編譯完可以看到Widget三個(gè)尺寸。你可以在預(yù)覽方法中,添加systemMedium和systemLarge的預(yù)覽。在Xcode12上最右邊可視化界面直接模擬器運(yùn)行和真機(jī)編譯。

image
  • 添加各種尺寸的組件預(yù)覽
// MARK: - 預(yù)覽
struct AdopterWidget_Previews: PreviewProvider {
    static var previews: some View {
        /// 小
        AdopterWidgetEntryView(entry: entry)
            .previewContext(WidgetPreviewContext(family: .systemSmall))
        
        /// 中
        AdopterWidgetEntryView(entry: entry)
            .previewContext(WidgetPreviewContext(family: .systemMedium))
        
        /// 大
        AdopterWidgetEntryView(entry: entry)
            .previewContext(WidgetPreviewContext(family: .systemLarge))
    }
}

5:熟悉的Hello World之后開(kāi)始編寫(xiě)新的demo。


image

6:上面預(yù)覽視圖的工具

  • 最左邊item:Live Preview : Xcode視圖預(yù)覽
  • 左邊第二個(gè):Preview on Device: 真機(jī)預(yù)覽,會(huì)在你的真機(jī)設(shè)備上編譯一個(gè)APP:Xcode Previews 里面是不含APP應(yīng)用Widget組件。

3:與主應(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.author)
          .font(.system(size: 16))
      Text(entry.quotes.content[0])
          .font(.system(size: 15))
          .foregroundColor(.black)
      Text("\(entry.quotes.date) at \(entry.quotes.place) ")
          .font(.system(size: 9))
          .foregroundColor(.gray)
  })
  .frame(maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/).padding(16)
  .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: {
      
      Text(entry.quotes.author)
          .font(.system(size: 18))
          .frame(height: 30, alignment: .top)
      
      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)
      }

      Link(destination: URL(string: "https://www.baidu.com/medium/2")!) {
          Text(entry.quotes.content[1])
              .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)
  })
  .frame(maxWidth: .infinity, maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, alignment: .center).padding(16)
}

注?。簊ystemSmall(小組件)只支持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);
}

4:部分代碼和方法注釋

1:默認(rèn)模板代碼注釋
  • 提供占位的方法,作為一種沒(méi)有特定屬性的通用可視化視圖。
func placeholder(in context: Self.Context) -> Self.Entry
  • 提供表示當(dāng)前時(shí)間和狀態(tài)的視圖,可以理解為虛假或者供用戶(hù)臨時(shí)選擇組件的展示信息??捎谜故驹贏dd Widget頁(yè)面數(shù)據(jù)。
func getSnapshot(in context: Self.Context, completion: @escaping (Self.Entry) -> Void)
  • 提供視圖更新數(shù)組,或者說(shuō)跟上面方法形成對(duì)比的真實(shí)信息數(shù),通過(guò)時(shí)間線(xiàn)來(lái)顯示??稍O(shè)置更新時(shí)間 如下方refreshDate的定義則是告訴組件每隔5分鐘重新加載一次。
func getTimeline(in context: Self.Context, completion: @escaping (Timeline<Self.Entry>) -> Void)

/// 這是更新時(shí)間5minute
let refreshDate = Calendar.current.date(byAdding: .minute, value: 5, to: currentDate)!

注?。?蘋(píng)果原文檔中有一段

Display Dynamic Dates
Because your widget extension is not always running, you can’t directly update your widget’s content. Instead, WidgetKit renders your widget’s view on your behalf and displays the result. However, some SwiftUI views let you display content that continues updating while your widget is visible.

由于您的窗口小部件擴(kuò)展程序并不總是運(yùn)行,因此您無(wú)法直接更新窗口小部件的內(nèi)容。而是,WidgetKit代表您渲染窗口小部件的視圖并顯示結(jié)果。但是,某些SwiftUI視圖可讓您顯示在可見(jiàn)窗口小部件時(shí)會(huì)繼續(xù)更新的內(nèi)容。

導(dǎo)致無(wú)法預(yù)測(cè)何時(shí)更新Widget。即使在上面設(shè)置了5分鐘后再次獲取時(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()
2:對(duì)于支持多個(gè)Widget和小、大、中頁(yè)面數(shù)據(jù)布局的思考?

在查看蘋(píng)果api之前我比較困惑的有兩點(diǎn)?

  • 如何定義多個(gè)Widget,并且小、中、大的布局完全不同,是否可以通過(guò)單個(gè)擴(kuò)展項(xiàng)目可以實(shí)現(xiàn)

  • 數(shù)據(jù)的定義和更新

上面通過(guò)默認(rèn)方法的介紹,第二個(gè)問(wèn)題已經(jīng)解決了。

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
}
  • 可以通過(guò)修改原Widget入口文件方法添加更多配置來(lái)支持多個(gè)Widget:
@main
struct SwiftWidgetsBundle: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        Widget1()
        Widget2()
        Widget3()
        Widget4()
        ...
    }
}

5:樣式/演示

  • 在編寫(xiě)Widget的過(guò)程中你可以直觀查看你的組件預(yù)覽,就如你開(kāi)發(fā)SwiftUI那樣,可以實(shí)時(shí)的Resume。
組件預(yù)覽
  • WidgetDemo完整演示
組件和點(diǎn)擊響應(yīng)演示

6:項(xiàng)目地址

  • 歡迎下載WidgetDemo查看更多關(guān)于演示項(xiàng)目中的源碼。

  • 項(xiàng)目github地址:iOS14WidgetDemo

  • 新的Widget組件還支持用戶(hù)用戶(hù)配置(Include Configuration Intent)這一點(diǎn)demo沒(méi)有演示到,后續(xù)有需要我會(huì)在更新下。

4:參考文獻(xiàn)

5:總結(jié)

更新完iOS14 不管是在用戶(hù)的使用上,還是組件的開(kāi)發(fā)的過(guò)程中體驗(yàn)都更順滑了。除了要切換成SwiftUI的開(kāi)發(fā)時(shí)間成本來(lái)說(shuō),把項(xiàng)目的組件完成最新形式還是很吸引人的,開(kāi)發(fā)上也很舒適。

新的Widget玩具感很強(qiáng),感覺(jué)還有很多要更新和補(bǔ)充的東西。

最后文檔中如果有理解有誤的地方,歡迎反饋,我將及時(shí)更新和修正。

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

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