swift項(xiàng)目中添加iOS14 Widget小組件

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ā)多年.....


叨逼叨.jpeg

悠悠記得那是一個(gè)月黑風(fēng)高的夜晚溉旋,我默默的按下了笨重機(jī)箱的開(kāi)機(jī)按鈕,輕車熟路的點(diǎn)開(kāi)了D盤(pán)嫉髓,然后打開(kāi)了加密的文件夾....


精神抖擻.jpeg

Double click 打開(kāi)了. xcodeproj項(xiàng)目观腊!


忍著.png

喔,對(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)有一次....


忍不了了.gif

嗯(不喔了- -!)現(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)建


新建widget.png

輸入Product Name(我用的是TestWidget)其他的都默認(rèn)選擇活尊,點(diǎn)擊Finish隶校!


widget添加后的樣子.png

編譯一下沒(méi)問(wèn)題后,運(yùn)行在模擬器上看下Xcode為我們生成的默認(rèn)效果(默認(rèn)三個(gè)樣式)蛹锰。
widget樣式.png

看一下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)目矾瑰,貼圖的話需要打碼~)


widget目錄列表.png

最后在真機(jī)上的效果:


添加彈出.PNG

主屏上展示.png

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í)的好孩子震桶,你懂的!

images.jpeg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末征绎,一起剝皮案震驚了整個(gè)濱河市蹲姐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌人柿,老刑警劉巖柴墩,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異凫岖,居然都是意外死亡江咳,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門隘截,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)扎阶,“玉大人,你說(shuō)我怎么就攤上這事婶芭《危” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵犀农,是天一觀的道長(zhǎng)惰赋。 經(jīng)常有香客問(wèn)我,道長(zhǎng)呵哨,這世上最難降的妖魔是什么赁濒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮孟害,結(jié)果婚禮上拒炎,老公的妹妹穿的比我還像新娘。我一直安慰自己挨务,他們只是感情好击你,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著谎柄,像睡著了一般丁侄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上朝巫,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天鸿摇,我揣著相機(jī)與錄音,去河邊找鬼劈猿。 笑死拙吉,一個(gè)胖子當(dāng)著我的面吹牛潮孽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庐镐,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼恩商,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了必逆?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤揽乱,失蹤者是張志新(化名)和其女友劉穎名眉,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體凰棉,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡损拢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了撒犀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片福压。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖或舞,靈堂內(nèi)的尸體忽然破棺而出荆姆,到底是詐尸還是另有隱情,我是刑警寧澤映凳,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布荣病,位于F島的核電站伏恐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绍在,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望崔列。 院中可真熱鬧让禀,春花似錦、人聲如沸庙洼。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)送膳。三九已至员魏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間叠聋,已是汗流浹背撕阎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留碌补,地道東北人虏束。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓棉饶,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親镇匀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子照藻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359