iOS开发日记-Widget_TimeLine篇

1,651 阅读3分钟
今天来聊一聊关于小组件Timeline相关的问题。
  • 我个人的理解为,小组件只要搞清楚Timeline与页面更新的关系,就差不多可以开发一个完整模块的小组件。 注意备注
创建工程

@main小组件的入口我们可以清晰的看到这个struct包含两块内容
kind小组件模块的标识符
body小组件的主题:里面包含三个参数kind,intent, provider

  • 根据这三个入参就可以大概猜测,小组件的渲染是通过这三个参数渲染出对应的content而且在Provider这个结构体中有关于SimpleEntry相关的闭包回调
@main
struct TestWidget: Widget {
    let kind: String = "TestWidget"

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind,
                            intent: ConfigurationIntent.self,
                            provider: Provider())
        { entry in
            TestWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}
  • 然后我们做的第一件事就是这个入口进行改造一下 注意代码备注

// 对这个SimpleEntry 改造一下用于打印
struct SimpleEntry: TimelineEntry {
    let date: Date
    let configuration: ConfigurationIntent
    
    // 给一个测试的属性
    var testString: String = "test" 
}

// 对展示的View 也进行改造
struct TestWidgetEntryView : View {
    var entry: Provider.Entry
    
    // 展示设定的textValue指
    var body: some View {
        Text("\(entry.testString)")
    }
}
@main
struct TestWidget: Widget {
    let kind: String = "TestWidget"
    
    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind,
                            intent: ConfigurationIntent.self,
                            provider: Provider()) { (entry) -> TestWidgetEntryView in
            // 添加一个打印函数主要查看 content 回调的调用情况
            print("[content]--\(entry.testString)")
            return TestWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}

// 同时对 Provider 进行改造, 在每个SimpleEntry初始化的时候添加对应的testvalue
struct Provider: IntentTimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(),
                    configuration: ConfigurationIntent(),
                    testString: "placeholder")
    }

    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry =  SimpleEntry(date: Date(),
                                 configuration: ConfigurationIntent(),
                                 testString: "getSnapshot")
        completion(entry)
    }

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

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}
在修改完成后我们运行一下看看
image-20210425143140072 image-20210425143140072 image.png image.png
其中有两个问题
  • 明明回调了5次却只显示第一次的回调
  • placeholder去哪里了
我们抱着这俩问题继续往下走

首先我们去TimelineEntry这个协议瞅一眼 发了个好东西

public protocol TimelineEntry {

    /// 就是这玩意
    /// The date for WidgetKit to render a widget.
    var date: Date { get }

    /// The relevance of a widget’s content to the user.
    var relevance: TimelineEntryRelevance? { get }
}

date的备注可以很清晰的看到The date for WidgetKit to render a widget.
这句话我理解的意思是: 根据设定的时间来更新widget
那么接下来去验证一下我们的猜想
将更新的时间间隔单位改为秒

    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []

        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
        // 将小时改为秒
            let entryDate = Calendar.current.date(byAdding: .second,
                                                  value: hourOffset,
                                                  to: currentDate)!
            let entry = SimpleEntry(date: entryDate,
                                    configuration: configuration,
                                    testString: "getTimeline\(hourOffset)")
            entries.append(entry)
        }

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

运行一下瞅瞅

image.png

果然在这个这个犹如读秒一样刷新了5次 紧接着会想到之前的第二个问题:
placeholder去哪了?
进入到官方的备注看一下写到 Provides a timeline entry representing the current time and state of a widget. 其中的current time会不会就是当前的时间。 我们对齐在进行一次改造:我们设定为第一次更新在当前时间的三秒后

func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []

        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
        // 第一次更新是当前时间的三秒后
            let entryDate = Calendar.current.date(byAdding: .second,
                                                  value: hourOffset + 3,
                                                  to: currentDate)!
            let entry = SimpleEntry(date: entryDate,
                                    configuration: configuration,
                                    testString: "getTimeline\(hourOffset)")
            entries.append(entry)
        }

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

image.png
但是好像并没有发生什么,那会不会是在complete回调之前才会执行这个placeholder操作呢
延迟三秒complete闭包

    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []

        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .second,
                                                  value: hourOffset,
                                                  to: currentDate)!
            let entry = SimpleEntry(date: entryDate,
                                    configuration: configuration,
                                    testString: "getTimeline\(hourOffset)")
            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .atEnd)
        
        // 延迟三面后执行complete闭包
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
            completion(timeline)
        }
    }
image.png

这下可以看到placeholder可以正常执行了

那么我们可以得到相应的关系

image.png

同时给大家留个作业(之后会更新作业内容)

  1. 如何自定义秒级刷新的小组件,并且一直读秒刷新
  2. 主APP如果控制小组件的显示内容
  3. 如何与主APP互相通讯
  4. IntentConfiguration 相关操作