iOS14發(fā)布后新增加了很多的功能(屏幕小組件widget、App Library 頁(yè)面、「App Clips」蘋(píng)果版的「小程序」......),可能對(duì)開(kāi)發(fā)者來(lái)說(shuō),最關(guān)注的新功能就是widget小組件和App Clips配并。今天主要來(lái)說(shuō)說(shuō)如何在自己的項(xiàng)目中添加widget小組件,喔高镐,對(duì)了話說(shuō)我從事iOS開(kāi)發(fā)多年.....
悠悠記得那是一個(gè)月黑風(fēng)高的夜晚溉旋,我默默的按下了笨重機(jī)箱的開(kāi)機(jī)按鈕,輕車熟路的點(diǎn)開(kāi)了D盤(pán)嫉髓,然后打開(kāi)了加密的文件夾....
Double click 打開(kāi)了. xcodeproj項(xiàng)目观腊!
喔,對(duì)了是在swift4.0的時(shí)候算行,就開(kāi)始放棄了OC語(yǔ)言開(kāi)發(fā)梧油,轉(zhuǎn)而擁抱swift語(yǔ)言開(kāi)發(fā)項(xiàng)目,關(guān)于swift這門語(yǔ)言怎么說(shuō)呢州邢?(哎呀儡陨,真香!)可能使用過(guò)的人才有評(píng)價(jià)的資格吧×刻剩現(xiàn)在swift5.xABI穩(wěn)定了骗村,擁抱她的人應(yīng)該更多了吧。到目前為止我的項(xiàng)目中一直是swift(90%)+ OC(10%)這樣的方式來(lái)開(kāi)發(fā)項(xiàng)目呀枢,等有時(shí)間想把OC相關(guān)的都去掉叙身,完全用swift來(lái)構(gòu)建項(xiàng)目(我是個(gè)追求完美的人),喔硫狞,對(duì)了后來(lái)有一次....
嗯(不喔了- -!)現(xiàn)在來(lái)說(shuō)說(shuō)項(xiàng)目中添加widget小組件(Follow Me)!
此處說(shuō)明:(項(xiàng)目中添加Widget我是借鑒了這個(gè)作者的文章残吩,包括下面的這些方法和截圖 作者:2狗子你變了 鏈接:http://www.reibang.com/p/55dce7a524f5财忽,他的文章很詳細(xì)如果想更清晰的了解添加過(guò)程及方法可以去他那兒溜達(dá)一會(huì)會(huì)兒,跟優(yōu)秀的人學(xué)習(xí)也會(huì)變得很優(yōu)秀泣侮,比如我即彪!??)
1.打開(kāi)Xcode -> File -> New -> Target菜單路徑找到 Widget Extension,雙擊創(chuàng)建
輸入Product Name(我用的是TestWidget)其他的都默認(rèn)選擇活尊,點(diǎn)擊Finish隶校!
編譯一下沒(méi)問(wèn)題后,運(yùn)行在模擬器上看下Xcode為我們生成的默認(rèn)效果(默認(rèn)三個(gè)樣式)蛹锰。
看一下Xcode生成的默認(rèn)的Widget源碼:
Provider:為小組件展示提供一切必要信息的結(jié)構(gòu)體深胳,實(shí)現(xiàn)TimelineProvider協(xié)議
placeholder:提供一個(gè)默認(rèn)的視圖,當(dāng)網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求失敗或者其他一些異常的時(shí)候铜犬,用于展示
getSnapshot:為了在小部件庫(kù)中顯示小部件舞终,WidgetKit要求提供者提供預(yù)覽快照,在組件的添加頁(yè)面可以看到效果
getTimeline:在這個(gè)方法內(nèi)可以進(jìn)行網(wǎng)絡(luò)請(qǐng)求癣猾,拿到的數(shù)據(jù)保存在對(duì)應(yīng)的entry中敛劝,調(diào)用completion之后會(huì)到刷新小組件
struct Provider: TimelineProvider {
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] = []
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)
}
}
實(shí)現(xiàn)TimelineEntry協(xié)議,保存所需要的數(shù)據(jù)
struct SimpleEntry: TimelineEntry {
let date: Date
}
用來(lái)展示的視圖View纷宇,可以進(jìn)行自己想要的界面搭建
struct TestWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
Text(entry.date, style: .time)
}
}
@main struct TestWidget: Widget
@main:代表著Widget的主入口夸盟,系統(tǒng)從這里加載
kind:是Widget的唯一標(biāo)識(shí)
StaticConfiguration:初始化配置代碼
configurationDisplayName:添加編輯界面展示的標(biāo)題
description:添加編輯界面展示的描述內(nèi)容
supportedFamilies這里可以限制要提供三個(gè)樣式中的哪幾個(gè)
@main
struct TestWidget: Widget {
let kind: String = "TestWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
TestWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
//supportedFamilies不設(shè)置的話默認(rèn)三個(gè)樣式都實(shí)現(xiàn)
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}
其實(shí)根據(jù)自己的需要可以設(shè)置多個(gè)樣式,但是不變的就是固定的三個(gè)樣式([.systemSmall, .systemMedium, .systemLarge])
由于我是在我的swift+OC項(xiàng)目中添加的像捶,所以對(duì)swiftUI還不太熟悉(widget必須要用swiftUI實(shí)現(xiàn)),所以只能簡(jiǎn)單的實(shí)現(xiàn)一下功能(太復(fù)雜的UI樣式還搞不定上陕,等swiftUI熟悉后再折騰吧),為了簡(jiǎn)單方便一點(diǎn) 我自定義的樣式就一個(gè).systemMedium
下面這個(gè)是我在自己的項(xiàng)目中添加的 >>>>>>
wishesWidget.swift
import WidgetKit
import SwiftUI
import Intents
struct wishesModel {
let title: String //標(biāo)題
let content: String // 內(nèi)容
}
struct wishesRequest {
static func request(completion: @escaping (Result<wishesModel, Error>) -> Void) {
let url = URL(string:"自己的url鏈接地址")
guard let requestUrl = url else { fatalError() }
var request = URLRequest(url: requestUrl)
request.httpMethod = "POST"
let postString = "自己的參數(shù)值"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else {
completion(.failure(error!))
return
}
if let data = data, let dataString = String(data: data, encoding: .utf8) {
let model = modelFromJson(fromData: data)
completion(.success(model))
}
}
task.resume()
}
static func modelFromJson(fromData data: Data) -> wishesModel {
let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
guard let data = json["data"] as? [String: Any] else {
return wishesModel(title:NSLocalizedString("sq_send_world_title", comment: ""),content: NSLocalizedString("sq_req_fail", comment: ""))
}
let title = data["title"] as! String
let content = data["content"] as! String
return wishesModel(title: title, content: content)
}
}
struct wishesProvider: IntentTimelineProvider {
func placeholder(in context: Context) -> wishesEntry {
let model = wishesModel(title: NSLocalizedString("sq_send_world_title", comment: ""), content: NSLocalizedString("sq_default_des", comment: ""))
return wishesEntry(date: Date(), item: model, configuration: ConfigurationIntent())
}
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (wishesEntry) -> ()) {
let model = wishesModel(title: NSLocalizedString("sq_send_world_title", comment: ""), content: NSLocalizedString("sq_default_des", comment: ""))
let entry = wishesEntry(date: Date(), item: model, configuration: configuration)
completion(entry)
}
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<wishesEntry>) -> ()) {
let currentDate = Date()
// 下一次更新間隔以小時(shí)為單位作岖,間隔1小時(shí)請(qǐng)求一次新的數(shù)據(jù)
let updateDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate)
wishesRequest.request { result in
let model: wishesModel
if case .success(let response) = result {
model = response
} else {
model = wishesModel(title: NSLocalizedString("sq_send_world_title", comment: ""), content: NSLocalizedString("sq_req_fail", comment: ""))
}
let entry = wishesEntry(date: updateDate!, item: model, configuration: configuration)
let timeline = Timeline(entries: [entry], policy: .after(updateDate!))
completion(timeline)
}
}
}
struct wishesEntry: TimelineEntry {
let date: Date
let item: wishesModel
let configuration: ConfigurationIntent
}
struct wishesWidgetEntryView : View {
var entry: wishesEntry
var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .lastTextBaseline){
Text(entry.item.title)
.font(.title2)
.bold()
Spacer()
VStack(alignment: .trailing){
Image("item_logo_icon")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
}
}
Spacer()
Text(entry.item.content)
.font(.system(size: 13))
.bold()
Spacer()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .leading)
.padding()
//widget背景圖片
.background(
Image("item_bg_icon")
.resizable()
.scaledToFill()
)
.widgetURL(URL(string: "url://123"))//獲取點(diǎn)擊標(biāo)記 需要在SceneDelegate里面實(shí)現(xiàn)跳轉(zhuǎn)處理唆垃,因?yàn)閕OS13后,APP的UI生命周期交由SceneDelegate管理
/*
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
for context in URLContexts {
print(context.url) //獲取widget點(diǎn)擊標(biāo)記 url://123
}
}
*/
/*注意??:由于我的項(xiàng)目是swift+OC 所以痘儡,點(diǎn)擊widget控件打開(kāi)響應(yīng)在AppDelegate 中辕万,
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if url.relativeString == "url://123" {
//TODO:
return true
}
}
*/
}
}
//systemMedium 中樣式
struct wishesWidget: Widget {
let kind: String = "wishesWidget"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: wishesProvider()) { entry in
wishesWidgetEntryView(entry: entry)
}
.configurationDisplayName(LocalizedStringKey("sq_title_widget"))
.description(LocalizedStringKey("sq_intro_widget"))
.supportedFamilies([.systemMedium])//我只需要一個(gè)中樣式所以這個(gè)位置放置了一個(gè).systemMedium,如果需要多個(gè)樣式可以從這三個(gè)樣式([.systemSmall, .systemMedium, .systemLarge])里面選擇沉删,也可以重復(fù)使用屬性值渐尿,展示多個(gè)樣式
}
}
wwsqWidget.swift添加MyWidgetBundle,關(guān)于這個(gè)MyWidgetBundle名字應(yīng)該可以隨意主要是@main主入口(如果你是用上面的步驟的話是這個(gè)文件TestWidget.swift)
@main
struct MyWidgetBundle: WidgetBundle {
@WidgetBundleBuilder
var body: some Widget {
wishesWidget()//寄語(yǔ)組件
}
}
這是我項(xiàng)目中添加的文件目錄(由于這是公司的項(xiàng)目矾瑰,貼圖的話需要打碼~)
最后在真機(jī)上的效果:
End:其實(shí)關(guān)于項(xiàng)目(swift或oc或swift和oc混合項(xiàng)目)中添加widget項(xiàng)目還有很多問(wèn)題砖茸,比如說(shuō)如何實(shí)現(xiàn)widget項(xiàng)目中Localizable.strings多語(yǔ)言本地化?如果是swiftUI應(yīng)該可以全局訪問(wèn)到Localizable.strings殴穴,但是不是的話需要單獨(dú)新建Localizable.strings凉夯,而且還發(fā)現(xiàn)widget(swiftUI如何訪問(wèn)獲取swift項(xiàng)目中的方法)無(wú)法訪問(wèn)swift項(xiàng)目中的文件和方法货葬,或許愚鈍的我沒(méi)有發(fā)現(xiàn)互相調(diào)用的方法,大家有知道的可以給我留言告訴我劲够,我是個(gè)愛(ài)學(xué)習(xí)的好孩子震桶,你懂的!