iOS小組件Widget從0到1開發(fā)

Widget支持iOS 14以上系統(tǒng),UI必須為swift UI(不能調(diào)用UIKit橋接過來,會顯示一個黃色背景的紅色圓圈斜杠),其他可以是swift和OC混編

如果要全面理解widget,需要學習(沒時間的話只看自己需要的部分)
1.swift 官方中文教程
2.swift UI Apple官方文檔

但是如果只是完成業(yè)務(wù)需求,沒有太多時間,只看下面widget介紹

首先新建Widget Extention,步驟如下圖:


新建Widget Extention.png

勾選Include Configuration Intent選項,會多生成一個.intentdefinition文件。


勾選Include Configuration Intent選項.png

生成的文件目錄.png

這個選項的作用是配置widget控件,比如系統(tǒng)自帶的天氣,長按顯示編輯小組件

長按編輯小組件.png
小組件編輯.png

這個背景色可以在Assets.xcassets/WidgetBackground中設(shè)置
交互按鈕的字體顏色在Assets.xcassets/AccentColor中設(shè)置

小組件編輯選擇城市.png

注意:如果勾選Include Configuration Intent選項,并且項目配置類前綴Class Prefix,則生成的.swift文件中ConfigurationIntent類,要手動添加類前綴

先看自動生成的.swift文件,這是一個時鐘的widget。編譯一下,看能不能成功。

可以看到里面都是struct:
struct ConfigureWidget: Widgetwidget入口,也負責整體項目配置
struct ConfigureWidgetEntryView : View負責UI相關(guān)
struct Provider: TimelineProvider數(shù)據(jù)的提供者
struct SimpleEntry: TimelineEntry數(shù)據(jù)模型
struct ConfigureWidget_Previews: PreviewProviderxcode熱重載配置文件
可以看到整體是一個MVVM結(jié)構(gòu)。
這幾個struct都可以重命名,對應(yīng)更改里面的配套名字就可以,比如更改SimpleEntry數(shù)據(jù)模型,那么傳遞的數(shù)據(jù)模型都需要更改。

1.入口和配置widget的不同樣式

入口是遵守Widget協(xié)議的struct

@main
struct ConfigureWidget: Widget {
    let kind: String = "ConfigureWidget"

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
            ConfigureWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}

一個widget Extention只能有一個@main入口。
kind:widget的唯一標識
provider:數(shù)據(jù)源
return ConfigureWidgetEntryView 返回,UI界面

.configurationDisplayName("Your Widget")添加widget界面,顯示的標題
.description("This is an example widget.")添加widget界面,顯示的描述
.supportedFamilies([.systemSmall])添加widget界面,顯示可以添加的widget樣式,默認是small,medium,large三種樣式用一套適配,但是一般我們?nèi)N樣式,會分別適配。有三種方式達到分別適配的效果

1.在UI層配置三種模式顯示不同的UI

加載一個widget,然后根據(jù)小中大,匹配加載不同的UI,這種方式數(shù)據(jù)沒加載出來有可能會黑屏,定制化差,不推薦這種方式

struct ConfigureWidgetEntryView : View {
    var entry: Provider.Entry

    @Environment(\.widgetFamily) var family: WidgetFamily

        @ViewBuilder
        var body: some View {
            switch family {
            case .systemSmall: SmallView(entry:entry)
            case .systemMedium: MediumView(entry:entry)
            case .systemLarge: LargeView(entry:entry)
            default: SmallView(entry:entry)
            }
        }
}
2.返回不同的widget支持不同的樣式

不同widget可以返回多個相同樣式,比如返回多個small樣式的widget,每個widget單獨加載,對應(yīng)一個單獨的UI,小中大可以添加個性化的添加widget話術(shù),推薦這種方式

@main
struct YourWidget : WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        SmallWidget()
        SmallWidget2()
        SmallWidget3()
        MediumWidget()
        LargeWidget()
    }
}
3.新建多個Widget Extention

一個APP有幾個完全不同的widget,可以用這種方式,會有多個bundleID,不推薦

2.struct SimpleEntry: TimelineEntry數(shù)據(jù)模型

struct SimpleEntry: TimelineEntry {
    let date: Date
    let configuration: ConfigurationIntent
}

date是必須要實現(xiàn)的,因為要遵守TimelineEntry協(xié)議,他的作用是多久刷新一次數(shù)據(jù)。
另外自己要實現(xiàn)自己需要的數(shù)據(jù)模型。
configuration是用戶自定義小組件的配置模型,在.intentdefinition文件parameter中添加參數(shù)名,選擇相應(yīng)的類型,也可以自定義類型

3.struct ConfigureWidgetEntryView : View負責UI相關(guān)

struct ConfigureWidgetEntryView : View因為遵循View協(xié)議,所以在var body中用swift UI實現(xiàn)UI相關(guān)的代碼,每次TimelineProvider中刷新UI,也會走這里的代碼,所以這里會有一個數(shù)據(jù)模型的屬性,也就是自己自定義的數(shù)據(jù)模型屬性。

4.struct Provider: TimelineProvider數(shù)據(jù)的提供者

這里就是負責加載數(shù)據(jù),有下面三個方法
// 沒加載到數(shù)據(jù)前的占位控件,但是經(jīng)過測試并不會加載這的數(shù)據(jù)

    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), configuration: ConfigurationIntent())
    }

// 譯為屏幕快照,在選擇添加控件的地方展示的數(shù)據(jù)

    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), configuration: configuration)
        completion(entry)
    }
getSnapshot方法展示數(shù)據(jù)的位置.png

// 根據(jù)時間軸,獲取網(wǎng)絡(luò)真實數(shù)據(jù)

    func getTimeline(for configuration: ConfigurationIntent, 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, configuration: configuration)
            entries.append(entry)
        }

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

控制器有自己的生命周期,但widget需要定時獲取數(shù)據(jù);
默認例子中是1小時執(zhí)行一次getTimeline,每次加載5條SimpleEntry數(shù)據(jù),(60/5=12min),12分鐘更新一次UI
Calendar.current.date(byAdding: .second, value: 5, to: currentDate)意思是返回當前時間+5秒鐘的時間。

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

entries中的數(shù)據(jù)執(zhí)行完之后policy: .atEnd立刻completion(timeline)重新獲取數(shù)據(jù),執(zhí)行func getTimeline。

policy:.atEnd執(zhí)行完這個entries,立刻刷新
.never不刷新,可以調(diào)用WidgetCenter.shared.reloadAllTimelines()刷新所有widget,或者指定widget刷新
.after指定時間刷新

點擊事件:
systemSmall:只能是widgetURL方式跳轉(zhuǎn)
systemMedium、systemLarge:widgetURL、Link都可以
widgetURL:

    var body: some View {
        VStack{
            
        }
        .widgetURL(URL(string: "www.myWidget.com"))
    }

Link包裹:

var body: some View {
      Link(destination: URL(string: "www.myWidget.com")!){
            VStack{
                
            }
      }
}

處理事件點擊,相當于處理URL Schemes,iOS14UI生命周期交給了scene

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        for context in URLContexts {
                print(context.url)
            }
    }

和OC混編問題

如果原項目是OC,或者現(xiàn)在widget target項目里面加入OC文件,需要加入橋接文件,如果加入.intentdefinition的配置文件,需要在橋接文件中,引入#import "ConfigurationIntent.h",否則會報錯誤Cannot find type 'ConfigurationIntent' in scope。

如果需要用cocopod中的其他庫,需要添加新的target,加入在widget擴展中需要用到的庫,比如加入AFNetworking:

target 'WidgetExtension' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  pod 'AFNetworking'
  # Pods for WidgetExtension

end

如果需要主項目中的文件,在需要用到的.m文件Target Membership中勾選widgetExtention,這樣widget小組件才能用這個OC 文件??赡苄枰恍┘嫒菪愿膭樱阂胍恍┤鄙俚念^文件,勾選其他關(guān)聯(lián)文件的Target Membership,添加widget關(guān)聯(lián)。勾選后編譯一下,沒問題后把用到的OC文件添加到橋接文件中聲明,這樣swiftUI才能調(diào)用原來OC主項目的文件

可以看下我寫的另一篇,記錄widget遇到的問題http://www.itdecent.cn/p/a4d61a880bac

最后編輯于
?著作權(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)容