[iOS14]WidgetKit開發實戰2-開發一言小部件

前言

2020年06月22日的WWDC上iOS14的新特性-小部件正式在iOS上線,同時WidgetKit也正式面向廣大開發者使用。git

也正是由於對Android的小部件有所瞭解,故想嘗試下iOS的小部件的開發,而且發現當前並無相關的文章,故記錄下我學習WigetKit的經歷,如下均爲本身學習路上的經歷,可能會有些問題,還望大佬指正。github

同時已把學習路上寫的代碼開源 - iWiget,看完這篇文章認爲有用就點個Star唄!json

項目地址: github.com/Littleor/iW…swift

沒有看過前一節的建議看看這個: (iOS14)WidgetKit開發實戰1-初識iOS小部件 api

效果演示

演示GIF
白天模式
黑夜模式

開發一言小部件

使用WigetKit開發Widget的主要就是View、Provider、Data。簡單來講,獲取到Data以後使用Provider顯示在View上就是Widget了。bash

1.編寫View

一言小部件的View多是這三個當中最簡單的部分了,這裏爲了方便理解直接使用一個Text來表示吧。markdown

struct OneWordView: View {
    var content:String = "每日一言"
    var body: some View {
        Text(content)
    }
}
複製代碼

如上述代碼,content用來控制顯示內容,一個最簡單的Text顯示便可,還能靠Swift實現自動暗黑模式。 這裏對View就很少談了,這部分View比較容易。ide

2.獲取Data

一言部件的數據如何獲取?從哪來? 目前iWidget採起的方案是經過Hitokoto提供的接口來獲取對應的數據。oop

不得不說,Hitokoto的接口很方便有質量。post

import Foundation
struct OneWord {
    let content: String
    let length: Int
}
struct OneWordLoader {
    static func fetch(completion: @escaping (Result<OneWord, Error>) -> Void) {
        let oneWordURL = URL(string: "https://v1.hitokoto.cn/")!
        let task = URLSession.shared.dataTask(with: oneWordURL) { (data, response, error) in
            guard error == nil else {
                completion(.failure(error!))
                return
            }
            let oneWord = getOneWordInfo(fromData: data!)
            completion(.success(oneWord))
        }
        task.resume()
    }

    static func getOneWordInfo(fromData data: Foundation.Data) -> OneWord {
        let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
        let content = json["hitokoto"] as! String
        let length = json["length"] as! Int
        return OneWord(content: content, length: length)
    }
}
複製代碼

其中OneWord聲明瞭解析事後的數據類型,經過OneWordLoaderfetch方法請求API獲取JSON數據後經過內部的getOneWordInfo方法解析JSON數據返回OneWord數據。

這部分代碼我封裝到了iWidget/Data/OneWordData.swift中,具體可見GitHub

3.編寫Provider

Provider的做用主要用於控制Widget的刷新 一言的Provider中,咱們須要在獲取API數據後再給Widget刷新顯示,故大概流程爲: 獲取數據->獲取成功後刷新數據。

struct OneWordProvider: IntentTimelineProvider {
    public func snapshot(for configuration: ConfigurationIntent, with context: Context, completion: @escaping (OneWordEntry) -> ()) {
        let entry = OneWordEntry(date: Date(),data: OneWord(content: "一言", length: 2))
        completion(entry)
    }
    
    public func timeline(for configuration: ConfigurationIntent, with context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        let currentDate = Date()
        let refreshDate = Calendar.current.date(byAdding: .minute, value: 60, to: currentDate)!
          
        OneWordLoader.fetch { result in
            let oneWord: OneWord
            if case .success(let fetchedData) = result {
                oneWord = fetchedData
            } else {
                oneWord = OneWord(content: "獲取失敗", length: 4)
            }
            let entry = OneWordEntry(date: currentDate,data: oneWord)
            let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
            completion(timeline)
        }
    }
}
複製代碼

其中

let timeline = Timeline(entries: [entry], policy: .after(refreshDate))

entries提供了下次更新的數據,policy提供了下次更新的時間。

其中policy可填.never永不更新(可經過WidgetCenter更新)、.after(Date)指定多久以後更新、.atEnd指定Widget經過你提供的entries的Date更新。

預覽的坑

介紹

我的被這個坑卡了好久,後來看了WidgetKit的代碼才找到緣由,這個真心坑。 當使用預覽的時候編譯會報錯:

reference to invalid associated type 'Entry' of type 'Provider'
複製代碼

這是由於你啓用了預覽:

struct MainWidget_Previews: PreviewProvider {
    static var previews: some View {
        PayToolsEntryView(entry: SimpleEntry(date: Date()))
            .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}
複製代碼

解決方案

對於這個問題我目前只想到了2個辦法解決:

1.直接註釋預覽

在編譯前註釋Preview的代碼便可,須要預覽再解除

2.Provider添加typealias

直接在Provider中添加:

typealias Entry = SimpleEntry
複製代碼

其中SimpleEntry須要使用你的Entry的變量名替換。

後記

這一次大概整理了下本身開發一個Widget的大概過程,還有可配置小部件和小部件Link等操做下次再分享,敬請期待。

完整代碼見GitHub

後續還會慢慢完善WidgetKit開發的文章,同時iWiget也會不斷完善,這篇文章對你有用就點個Star吧!

項目地址: github.com/Littleor/iW…

相關文章
相關標籤/搜索