iOS点点滴滴iOS 新系统新功能研究

iOS14Widget简介

2020-09-13  本文已影响0人  Money_YC

简介

新的 WidgetKit 框架和 SwiftUI 关于 widget 新的 api,创建的 widget能满足 iOS,iPadOS,和 macOS 平台,能放在 Home screen 的任意位置。而且还拥有智能堆栈 Smart Stack ,集成siri的智能化推荐能力,能根据你使用时间,位置等因素,来智能显示组件。

image

项目构建

添加Widget到原工程

打开你的 Xcode 工程, 并且选择 File > New > Target,在 Application Extension 中选择 Widget Extension,输入 Widget 的名字,如果 Widget 提供了用户可配置的属性,请选中“ Include Configuration Intent ”复选框。

image

在这里简单介绍下Widget的配置信息

小部件扩展模板提供了一个符合小部件协议的初始小部件实现。Widget的body属性决定了该Widget是否具有用户可配置的属性。

创建widget时,包含配置意图「Include Configuration Intent」复选框决定了Xcode使用哪种配置。当选择这个复选框时,Xcode使用将使用默认设置进行配置。

Configuration有两种配置可供选择:

1.StaticConfiguration: 对于一个没有用户可配置属性的Widget。「例如,显示一般市场信息的股票市场Widget,或显示趋势标题的新闻Widget。」

2.IntentConfiguration。对于一个具有用户可配置属性的Widget来说,你可以使用SiriKit自定义意图来定义属性。您使用 SiriKit 自定义意图来定义属性。「例如,一个天气Widget需要一个城市的邮政编码或邮政编码,或者一个包裹跟踪Widget需要一个跟踪号码。」

以下代码创建一个常规的StaticConfiguration,不可配置的状态的 Widget:

struct TestWidget: Widget {
    let kind: String = "TestWidget"
    var body: some WidgetConfiguration { 
     StaticConfiguration(kind: kind,provider: Provider()) { entry in
            TestWidget EntryView(entry: entry)
        }
        .configurationDisplayName("name")
        .description("des")
        .supportedFamilies([.systemMedium])
    }
}

Provider 时间线配置(Provide Timeline Entries)

struct Provider: TimelineProvider {
  
    public typealias Entry = SimpleEntry
  
    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] = []

        // 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)
            entries.append(entry)
        }

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

Provider会生成由时间线条目组成的时间线,每个条目都指定了更新小组件内容的日期和时间。

Widget的刷新策略

Timeline里面有三种方式:atEnd,after(date),never

保持组件状态为最新(Keeping a Widget Up To Date)

Widget使用SwiftUI视图来显示它们的内容。WidgetKit在一个单独的过程中代表您渲染视图。因此,即使小组件在屏幕上,小组件扩展也不会持续活跃。尽管widget并不总是处于活动状态,但有几种方法可以使其内容保持最新。

为可预测的事件生成一个时间轴
定义widget时,实现一个自定义的TimelineProvider。WidgetKit从你的provider那里获取一个时间线,并使用它来跟踪何时更新widget。时间线是一个TimelineEntry对象的数组。时间线中的每个条目都有日期和时间,以及小组件显示其视图所需的附加信息。除了时间线条目,时间线还指定了一个刷新策略,该策略告诉WidgetKit何时请求新的时间线.

下面是苹果官网给的一个显示角色健康水平的游戏小部件的例子。
当健康水平低于100%时,角色以每小时25%的速度恢复。例如,当角色的健康水平为25%时,需要3小时才能完全恢复到100%。下图显示了WidgetKit如何从provider那里请求时间线,在时间线条目中指定的每个时间渲染小部件。

func getTimeline(for configuration: CharacterSelectionIntent, with context: Self.Context, completion: @escaping (Timeline<Self.Entry>) -> ()) {
    let selectedCharacter = characher(for: configuration)
    let endDate = selectedCharacter.fullHealthDate
    let oneHour: TimeInterval = 60 * 60
    var currentDate = Date()
    var entries: [SimpleEntry] = []

    while currentDate < endDate {
        let relevance = TimelineEntryRelevance(score: Float(selectedCharacter.healthLevel))
        let entry = SimpleEntry(date: currentDate, character: selectedCharacter, relevance: relevance)
        currentDate += oneHour
        entries.append(entry)
    }
    let timeline = Timeline(entries: entries, policy: .atEnd)
    completion(timeline)
}

当WidgetKit最初请求时间轴时,provider会创建一个有四个条目的时间轴。第一个条目代表当前的时间(Now),之后是每小时一次的三个条目。在刷新策略设置为默认的atEnd的情况下,WidgetKit会在时间线条目中的最后一个日期之后请求一个新的时间线。当时间线中的每个日期到达时,WidgetKit会调用小组件的内容闭包显示结果。在最后一个时间线条目过后,WidgetKit会重复这个过程,要求提供者提供一个新的时间线。由于角色的健康度已经达到了100%,提供者会以当前时间的单一条目和刷新策略设置为never来回应。在这种设置下,WidgetKit不会要求另一条时间线,直到应用程序使用WidgetCenter告诉WidgetKit请求新的时间线。

当时间线改变时通知WidgetKit
当某件事情影响到小组件的当前时间线时,或者说当你要刷新Widget的时候,App可以告诉WidgetKit请求新的时间线。App可以告诉WidgetKit重新加载时间线并更新小组件的内容。要重载特定类型的widget,使用WidgetCenter

WidgetCenter.shared.reloadTimelines(ofKind: "com.testWidget")

kind参数包含与用于创建widget的WidgetConfiguration的值相同的字符串。当然也只有一个widget的话也可以用reloadAllTimelines。

如果widget具有用户可配置的属性,那么通过使用WidgetCenter来验证是否存在具有适当设置的widget,从而避免不必要的重新加载。

WidgetCenter.shared.getCurrentConfigurations { result in
    guard case .success(let widgets) = result else { return }
    if let widget = widgets.first(
        where: { widget in
            let intent = widget.configuration as? SelectCharacterIntent
            return intent?.character == characterThatReceivedHealingPotion
        }
    ) {
        WidgetCenter.shared.reloadTimelines(ofKind: widget.kind)
    }
}

如果只有一个Widget,你可以使用WidgetCenter来重新加载所有widget的时间线。

WidgetCenter.shared.reloadAllTimelines()

Link WidgetUrl

从 Widget 跳转到 App 指定界面,只需要用 SwiftUI Link 的方式,View 的外层包上一个 Link,destination 是设定好的 url,就能实现跳转了。

Link(destination: URL(string: topJump.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)!) {
  HStack() {
    Spacer()
      .frame(width: Len(15))
    // titile
    Text(topDesc)
      .fontWeight(.regular)
      .font(.system(size: Len(14)))
      .foregroundColor(Color(red: 51/255, green: 51/255, blue: 51/255, opacity: 1.0))
    Image(triangleIcon)
      .padding(.trailing, 4.0)
      .frame(width: Len(8), height: Len(8))
    Spacer()
  }
}

widgetURL:在View上加一个widgetURL ,URL是设定好的 url,就能实现跳转了

HStack() {
  Spacer()
    .frame(width: UMEWLen(15))
  // titile
  Text(newBean.topDesc)
    .fontWeight(.regular)
    .font(.system(size: Len(14)))
    .foregroundColor(Color(red: 51/255, green: 51/255, blue: 51/255, opacity: 1.0))
  Image(triangleIcon)
    .padding(.trailing, 4.0)
    .frame(width: Len(8), height: Len(8))
  Spacer()
}.widgetURL(URL(string: topJump.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)!)

Widget bundles

有时候我们会有多个样式不同种类的 Widget,就需要用 @WidgetBundleBuilder 把多个 Widget 放在一起

@main
struct MainBundle: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        oneWidget()
        twoWidget()
        threeWidget()
    }
}

Intent

在你的Xcode项目中,选择File > New File并选择SiriKit Intent Definition File。点击 "下一步 "并在提示时保存文件。Xcode创建一个新的.intenttdefinition文件,并将其添加到项目中。
Xcode从意图定义「intent definition file」文件中生成代码。要在一个目标「target」中使用这些代码。
-将intent定义文件作为目标的一个成员。
-通过添加 intent 的类名到 target 属性的 Supported Intents 部分来指定要包含在 target 中的特定 intents。

稍后会上传demo和自己遇到的一些坑
[NewWidget]https://github.com/moneyYouCai/NewWidget-iOS14

上一篇 下一篇

猜你喜欢

热点阅读