2021 - 43周(Widget入门)

本文介绍了iOS 14+中Widget的基础知识和开发流程,包括创建Widget Extension、配置Timeline、展示内容、用户交互等方面,适合SwiftUI和Swift开发者参考,帮助你将应用内容呈现在主屏幕和消息中心。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Widget入门

        总结Widget的常规实现方式,主要参考苹果官方文档,并结合自己在实际项目中的一些经验和体会,以备后续查证。

        总的来说Widget实现步骤并不复杂,且Xcode会自动生成其中的绝大多数通用流程的文件以及类和方法,在基本了解了Widget的一些系统设定以后,开发者只需要根据需要添加相应的配置即可。对于熟悉SwiftUI和Swift的开发者来说尤其如此。

目录

Widget入门

一:Widget是什么

        一句话描述:

        概述:

        开发步骤:

二:Widget开发

        1.创建Widget Extension

         2.添加配置条目 

        3.提供Timeline Entries

        4.提供占位图(主页面)

        5.展示Widget的内容

        6.添加动态内容

        7.用户交互

        8.添加多个Widget

        9.强制更新Widget


一:Widget是什么

        一句话描述:

        在iOS主屏幕或者macOS的消息中心展示与你app相关的概要内容(文档:Show relevant, glanceable content from your app on the iOS Home screen or macOS Notification Center.)

        支持Widget的平台iOS 14.0+、macOS 11.0+ 、 Mac Catalyst 14.0+

        概述:

        Widget既可以展示最新的概要信息,也可以把用户直接带入app的特定页面。

        Widget有三种(小,中,大)大小,可以展示多种信息,用户可以定制Widget并任意排列它们,当把Widget堆叠时,系统会自动轮转它们,以使最有价值的Widget处于顶端。

        开发步骤:

        实现一个Widget,你需要:

        1.为你的app添加一个widget extension。

        2.通过timeline provider来配置widget,timeline provider会告诉WidgetKit何时更新widget的内容。

        3.通过SwiftUI来展示widget的内容。

        4.如果想要widget是用户可配置的,你需要为你的extention添加一个自定义的SiriKit定义,WidgetKit会自动提供定制界面来让用户使用。

二:Widget开发

        1.创建Widget Extension

  • Xcode打开app项目 选择 File > New > Target
  • 从Application Extension组内选择Widget Extension并点击Next
  • 输入extension的名字
  • 如果想要Widget是用户可配置的,就勾选Include Configuration Intent checkbox复选框
  • 点击Finish

        一般来说只创建一个widget extension来包含所有的widget,(尽管可以创建多个,比如单独创建需要定位信息的widget, 以使系统单独提示)。

        创建成功后,在你的项目目录中与app文件夹同级的位置会多出一个extension文件夹,其中有一个swift文件,就是我们创建widget的主要文件。

提示:

许多通用流程已经在上述swift文件中自动生成,开发者做的更多的是定制配置工作

         2.添加配置条目 

        Widget extension模板提供了一个遵循Widget协议的Widget实现,这个Widget的属性决定其是否可以由用户配置。有两种可选的配置:

  • StaticConfiguration:用户不可配置
  • IntentConfiguration:用户可以配置,使用SiriKit自定义属性

        这两种配置是由上一步的(勾选Include Configuration Intent checkbox复选框)决定的

        初始化配置需要提供以下信息:

  • Kind:一个唯一字符串(ID),最好可以表示widget的作用

注意:

Kind参数不要与其它Widget重复,建议用公司网址倒叙做前缀。本人遇到过疑似因为命名重复而导致Widget的占位图只显示一个黑色底图的情况

  • Provider:一个遵循TimelineProvider协议的对象,它会提供一个timeline来告诉WidgetKit何时渲染widget。一个timeline包含一个你自定义的TimelineEntry类型。TimelineEntry声明了你想要WidgetKit更新widget的时间和你的widget的view需要渲染使用的属性。
  • Content Closure:一个包含SwiftUI视图的闭包,WidgetKit引用这个来渲染widget的内容,并传递给视图TimelineEntry的参数
  • Custom Intent:自定义Intent来定义用户可配置属性(具体见可配置Widget)

        使用modifiers提供额外配置信息,包括:

  • Widget名称:configurationDisplayName
  • 描述:description
  • 支持的大小:supportedFamilies(大、中、小)
//Widget设置 —— 来自苹果官方文档的示例
@main
struct GameStatusWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: "com.mygame.game-status",
            provider: GameStatusProvider(),
        ) { entry in
            GameStatusView(entry.gameStatus)
        }
        .configurationDisplayName("Game Status")
        .description("Shows an overview of your game status")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

注意:

用户至少要在安装后启动一次app,才能使得app关联的widget在系统的小组件列表中展示 

        3.提供Timeline Entries

        Timeline entry提供更新widget的时间和数据。遵循TimelineEntry协议。

        WidgetKit会向provider索要占位图,此图会展示在widget列表中(非主屏幕,见下图)。

通过getSnapshot(in:completion:)方法中传入的context参数的isPreview变量可以判断当前视图是否是占位图。占位图要快速生成,不要占用太长生成时间,可以考虑使用示例数据

//getSnapshot函数实现,提供占位视图 —— 来自苹果官方文档的示例
truct GameStatusProvider: TimelineProvider {
    var hasFetchedGameStatus: Bool
    var gameStatusFromServer: String

    func getSnapshot(in context: Context, completion: @escaping (Entry) -> Void) {
        let date = Date()
        let entry: GameStatusEntry

        if context.isPreview && !hasFetchedGameStatus {
            entry = GameStatusEntry(date: date, gameStatus: "—")
        } else {
            entry = GameStatusEntry(date: date, gameStatus: gameStatusFromServer)
        }
        completion(entry)
    }

        之后WidgetKit调用 getTimeline(in:completion:)来向provider请求timeline的规则。Timeline由一个或多个timeline entry组成,同时包含一个说明何时请求下一次timeline的更新规则。下例中展示了包含一个entry并声明更新规则为15分钟后触发下次请求的getTimeline方法

//getTimeline方法包含了timeline entry和15分钟后更新的规则 —— 来自苹果官方文档的代码
struct GameStatusProvider: TimelineProvider {
    func getTimeline(in context: Context, completion: @escaping (Timeline<GameStatusEntry>) -> Void) {
        // Create a timeline entry for "now."
        let date = Date()
        let entry = GameStatusEntry(
            date: date,
            gameStatus: gameStatusFromServer
        )

        // Create a date that's 15 minutes in the future.
        let nextUpdateDate = Calendar.current.date(byAdding: .minute, value: 15, to: date)!

        // Create the timeline with the entry and a reload policy with the date
        // for the next update.
        let timeline = Timeline(
            entries:[entry],
            policy: .after(nextUpdateDate)
        )

        // Call the completion to pass the timeline to WidgetKit.
        completion(timeline)
    }
}

        4.提供占位图(主页面)

        Widget被首次添加到主屏幕的时候,WidgetKit会使用占位图渲染它,通过调用placeholder(in:)方法来获取占位图信息。

//展示于桌面的占位图 —— 来自苹果官方文档的示例
struct GameStatusProvider: TimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        GameStatusEntry(date: Date(), gameStatus: "—")
    }
}

        5.展示Widget的内容

        使用SwiftUI来定义页面,需要提供你的widget支持的所有大小的页面。

//SwiftUI定义页面 —— 来自苹果官方文档的示例
struct GameStatusView : View {
    @Environment(\.widgetFamily) var family: WidgetFamily
    var gameStatus: GameStatus

    @ViewBuilder
    var body: some View {
        switch family {
        case .systemSmall: GameTurnSummary(gameStatus)
        case .systemMedium: GameStatusWithLastTurnResult(gameStatus)
        case .systemLarge: GameStatusWithStatistics(gameStatus)
        default: GameDetailsNotAvailable()
        }
    }
}

注意:

Widget展示的是“只读”信息,不支持交互组件(如:滚动控件),WidgetKit会在渲染的时候忽略交互组件 

        6.添加动态内容

        尽管widget是基于你的view的一个截图,但你仍然可以使用各种SwiftUI组件来持续更新它。详见:Keeping a Widget Up To Date

        Widget可以通过Userdefault或者文件系统与app共享数据,使用Userdefault的话,需要在app中添加App Groups,然后使用App Group来初始化Userdefault。

NSUserDefaults *userDef = [[NSUserDefaults alloc] initWithSuiteName:@"group.yourappgroup"];

//初始化UserDefaults

        7.用户交互

        当用户点击widget,系统会打开你的app。Widget可以通过一个URL来通知app具体跳转到哪个模块。

        对于三种大小的widget,他们支持的交互方式不同

        小(systemSmall)只支持widgetURL(_:)方式

        中(systemMedium)和大(systemLarge)除了支持widgetURL(_:)方式,还可以通过添加Link的方式添加跳转

//通过widgetURL为widget添加跳转交互 —— 来自苹果官方文档的示例
@ViewBuilder
var body: some View {
    ZStack {
        AvatarView(entry.character)
            .widgetURL(entry.character.url)
            .foregroundColor(.white)
    }
    .background(Color.gameBackground)
}

        App端通过onOpenURL(perform:), application(_:open:options:), or application(_:open:)等系统方法来获取到widget传入的跳转链接

        8.添加多个Widget

        为了支持多Widget,你需要添加支持WidgetBundle协议的结构体,在该结构体中把多个widget聚合到它的body属性中

//多Widget —— 来自苹果官方的示例
@main
struct GameWidgets: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        GameStatusWidget()
        CharacterDetailWidget()
        LeaderboardWidget()
    }
}

        9.强制更新Widget

        WidgetKit的reloadAllTimelines()方法可以强制重置所有时间线。该方法只能用Swift调用,所以如果主工程是OC写的,就需要进行Swift混编。具体步骤为:

  • 主app添加Swift文件,在其中实现调用WidgetKit的reloadAllTimelines()方法的函数
  • 根据提示添加Bridging文件
  • 在要调用强制刷新的地方引用"appName-Swift.h",其中appName是你工程名,该文件会自动生成,并且无输入提示,直接import即可
  • 在要刷新的地方调用第一步中你实现的方法

        以上即是初步实现Widget的一些流程。

参考链接:

https://developer.apple.com/documentation/widgetkit/creating-a-widget-extensionhttps://developer.apple.com/documentation/widgetkit/creating-a-widget-extensionWidgets Code-along, part 1: The adventure begins - WWDC20 - Videos - Apple DeveloperTake your app on a most wondrous adventure to the home and Today screens of iPhone, iPad, and Mac. Grab the starter project and code...https://developer.apple.com/videos/play/wwdc2020/10034/Add intelligence to your widgets - WWDC21 - Videos - Apple DeveloperDiscover how to you can add intelligence to your widgets in Smart Stacks. We'll show you how to use the new Widget Suggestions API in...https://developer.apple.com/videos/play/wwdc2021/10049/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值