iOS桌面小插件 Widget Extension

  • 这个插件时iOS14以后才出现的,基于SwiftUI
  • 旧项目新建时可能一堆错误,其中一个时要把插件target 开发sdk版本设置为14.0以上

新建target

  • File - Target - Widget Extension

项目结构

  • @main 这里是主入口,这里可以设置小组件的 Provider以及 WidgetEntryView,以及长按后弹出框的 APP 信息设置。
  • Provider:控制器,这里可以用来做小组件的刷新操作
  • SimpleEntry: 这个是数据模型,Provider 里如果想更新数据到 WidgetEntryView,必须通过 SimpleEntry 来实现,当然命名随意了,但是这个必须继承 TimelineEntry。同时也可以新增参数,变量什么的,用来传递自己需要的数据类型。
  • WidgetEntryView: 这就是主视图了,在这里自定义页面用来显示在手机桌面。

  1. import WidgetKit
  2. import SwiftUI
  3. import Intents
  4. // 控制器,类似Controller,这里可以用来做小组件的刷新操作
  5. struct Provider: IntentTimelineProvider {
  6. func placeholder(in context: Context) -> SimpleEntry {
  7. SimpleEntry(date: Date(), configuration: ConfigurationIntent())
  8. }
  9. func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
  10. let entry = SimpleEntry(date: Date(), configuration: configuration)
  11. completion(entry)
  12. }
  13. func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
  14. var entries: [SimpleEntry] = []
  15. // Generate a timeline consisting of five entries an hour apart, starting from the current date.
  16. let currentDate = Date()
  17. for hourOffset in 0 ..< 5 {
  18. let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
  19. let entry = SimpleEntry(date: entryDate, configuration: configuration)
  20. entries.append(entry)
  21. }
  22. let timeline = Timeline(entries: entries, policy: .atEnd)
  23. completion(timeline)
  24. }
  25. }
  26. // 数据模型,数据显示在View上必须经过这里
  27. struct SimpleEntry: TimelineEntry {
  28. let date: Date
  29. let configuration: ConfigurationIntent
  30. }
  31. // View,小组件的界面
  32. struct WidgetExtensionEntryView : View {
  33. var entry: Provider.Entry
  34. var body: some View {
  35. Text(entry.date, style: .time)
  36. }
  37. }
  38. // 程序入口,初始化相关信息,如Provider,View等
  39. @main
  40. struct WidgetExtension: Widget {
  41. let kind: String = "WidgetExtension"
  42. var body: some WidgetConfiguration {
  43. IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
  44. WidgetExtensionEntryView(entry: entry)
  45. }
  46. .configurationDisplayName("小组件")
  47. .description("This is an 测试一下 widget.")
  48. }
  49. }
  50. // 自定义样式
  51. struct WidgetExtension_Previews: PreviewProvider {
  52. static var previews: some View {
  53. // 设置小组件尺寸 systemSmall systemMedium systemLarge
  54. WidgetExtensionEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
  55. .previewContext(WidgetPreviewContext(family: .systemSmall))
  56. }
  57. }

自定义UI

自定义小组件尺寸

  1. // 自定义样式
  2. struct WidgetExtension_Previews: PreviewProvider {
  3. static var previews: some View {
  4. // 设置小组件尺寸 systemSmall systemMedium systemLarge
  5. WidgetExtensionEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
  6. .previewContext(WidgetPreviewContext(family: .systemSmall))
  7. }
  8. }

HStack、VStack、ZStack

  • HStack、VStack相当于UIStackView,H是水平方向,V是竖直方向。ZStack可以理解为相对于屏幕里外方向,也就是相当于以前superView和subView的方式。
  1. // View,小组件的界面
  2. struct WidgetExtensionEntryView : View {
  3. var entry: Provider.Entry
  4. var body: some View {
  5. // 深度布局,屏幕深度
  6. ZStack(alignment: .center, content: {
  7. // 背景图
  8. Image("2").resizable().aspectRatio(contentMode: .fit)
  9. // 水平
  10. HStack(alignment: .center, spacing: 5, content: {
  11. // 左侧图
  12. Image("1").frame(width: 80, height: 80, alignment: .center).aspectRatio(contentMode: .fit).cornerRadius(10.0)
  13. // 垂直
  14. VStack(alignment: .center, spacing: 5, content: {
  15. // 右侧文字
  16. Text("小组件1").foregroundColor(.blue)
  17. Text("小组件2").foregroundColor(.blue).lineLimit(2)
  18. })
  19. })
  20. })
  21. }
  22. }

传递数据

  • 通过widgetURL 和Link
  • 在主应用添加 URL Types
  1. // View,小组件的界面
  2. struct WidgetExtensionEntryView : View {
  3. var entry: Provider.Entry
  4. var body: some View {
  5. // 深度布局,屏幕深度
  6. ZStack(alignment: .center, content: {
  7. // 背景图
  8. Image("2").resizable().aspectRatio(contentMode: .fit)
  9. // 水平
  10. HStack(alignment: .center, spacing: 5, content: {
  11. // 左侧图
  12. Image("1").frame(width: 80, height: 80, alignment: .center).aspectRatio(contentMode: .fit).cornerRadius(10.0)
  13. // 垂直
  14. VStack(alignment: .center, spacing: 5, content: {
  15. // 右侧文字
  16. Text("小组件1").foregroundColor(.blue)
  17. Text("小组件2").foregroundColor(.blue).lineLimit(2)
  18. })
  19. })
  20. }).widgetURL(URL(string: "widgetExtensionDemo://test1"))
  21. }
  22. }
  • 接受数据

  • 只能用SceneDelegate来接受数据,AppDelegate不行。

  • SceneDelegate

  • SceneDelegate中相应事件

  1. - (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts{
  2. NSLog(@"%s",__FUNCTION__);
  3. UIOpenURLContext * context = URLContexts.allObjects.firstObject;
  4. NSLog(@"%@", context.URL);
  5. }

适配不同尺寸小组件



  1. // View,小组件的界面
  2. struct WidgetExtensionEntryView : View {
  3. @Environment(\.widgetFamily) var family:WidgetFamily
  4. var entry: Provider.Entry
  5. var body: some View {
  6. switch family {
  7. case .systemSmall:
  8. // 深度布局,屏幕深度
  9. ZStack(alignment: .center, content: {
  10. // 背景图
  11. Image("2").resizable().aspectRatio(contentMode: .fill)
  12. // 水平
  13. HStack(alignment: .center, spacing: 5, content: {
  14. // 左侧图
  15. Image("1").frame(width: 80, height: 80, alignment: .center).aspectRatio(contentMode: .fit).cornerRadius(10.0)
  16. // 垂直
  17. VStack(alignment: .center, spacing: 5, content: {
  18. // 右侧文字
  19. Text("小组件1").foregroundColor(.blue)
  20. Text("小组件2").foregroundColor(.blue).lineLimit(2)
  21. })
  22. })
  23. }).widgetURL(URL(string: "widgetExtensionDemo://test1"))
  24. case .systemMedium:
  25. // 深度布局,屏幕深度
  26. ZStack(alignment: .center, content: {
  27. // 背景图
  28. Image("2").resizable().aspectRatio(contentMode: .fill)
  29. // 水平
  30. HStack(alignment: .top, spacing: 5, content: {
  31. // 左侧图
  32. Image("1").resizable().aspectRatio(contentMode: .fit).frame(width: 200, height: 80, alignment: .leading).cornerRadius(10.0).foregroundColor(.blue)
  33. // 垂直
  34. VStack(alignment: .trailing, spacing: 5, content: {
  35. // 右侧文字
  36. Text("zh组件1").foregroundColor(.blue)
  37. Text("小组件2").foregroundColor(.blue).lineLimit(2)
  38. }).foregroundColor(.gray)
  39. })
  40. }).widgetURL(URL(string: "widgetExtensionDemo://test2"))
  41. case .systemLarge:
  42. // 深度布局,屏幕深度
  43. ZStack(alignment: .center, content: {
  44. // 背景图
  45. Image("2").resizable().aspectRatio(contentMode: .fill)
  46. // 水平
  47. HStack(alignment: .center, spacing: 5, content: {
  48. // 左侧图
  49. Image("1").aspectRatio(contentMode: .fit).cornerRadius(10.0).frame(width: 200, height: 100, alignment: .leading)
  50. // 垂直
  51. VStack(alignment: .center, spacing: 5, content: {
  52. // 右侧文字
  53. Text("小组件1").foregroundColor(.blue)
  54. Text("小组件2").foregroundColor(.blue).lineLimit(2)
  55. })
  56. }).foregroundColor(.blue)
  57. }).widgetURL(URL(string: "widgetExtensionDemo://test3"))
  58. default:
  59. // 深度布局,屏幕深度
  60. ZStack(alignment: .center, content: {
  61. // 背景图
  62. Image("2").resizable().aspectRatio(contentMode: .fit)
  63. // 水平
  64. HStack(alignment: .center, spacing: 5, content: {
  65. // 左侧图
  66. Image("1").frame(width: 80, height: 80, alignment: .center).aspectRatio(contentMode: .fit).cornerRadius(10.0)
  67. // 垂直
  68. VStack(alignment: .center, spacing: 5, content: {
  69. // 右侧文字
  70. Text("小组件1").foregroundColor(.blue)
  71. Text("小组件2").foregroundColor(.blue).lineLimit(2)
  72. })
  73. })
  74. }).widgetURL(URL(string: "widgetExtensionDemo://test1"))
  75. }
  76. }
  77. }

更多小组件创建

  • 重写@main入口
  1. // 更多小组件
  2. @main
  3. struct Widgets:WidgetBundle {
  4. init() {
  5. }
  6. @WidgetBundleBuilder
  7. var body: some Widget{ // 最多创建5次,也就是15个小组件
  8. WidgetExtension()
  9. CustomWidget()
  10. CustomWidget()
  11. CustomWidget()
  12. CustomWidget()
  13. }
  14. }
  15. struct CustomWidget:Widget {
  16. var kind:String="自定义组件"
  17. var body: some WidgetConfiguration{
  18. IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
  19. CustomEntryView(entry:entry)
  20. }
  21. .configurationDisplayName("自定义更多组件")
  22. .description("ios14自定义更多小组件")
  23. }
  24. }
  25. // 自定义Ui
  26. struct CustomEntryView:View {
  27. @Environment(\.widgetFamily) var family:WidgetFamily
  28. var entry: Provider.Entry
  29. @ViewBuilder
  30. var body: some View {
  31. switch family {
  32. case .systemSmall:
  33. // 深度布局,屏幕深度
  34. ZStack(alignment: .center, content: {
  35. // 背景图
  36. Image("2").resizable().aspectRatio(contentMode: .fill)
  37. // 水平
  38. HStack(alignment: .center, spacing: 5, content: {
  39. // 左侧图
  40. Image("1").frame(width: 80, height: 80, alignment: .center).aspectRatio(contentMode: .fit).cornerRadius(10.0)
  41. // 垂直
  42. VStack(alignment: .center, spacing: 5, content: {
  43. // 右侧文字
  44. Text("小组件1").foregroundColor(.blue)
  45. Text("小组件2").foregroundColor(.blue).lineLimit(2)
  46. })
  47. })
  48. }).widgetURL(URL(string: "widgetExtensionDemo://test1"))
  49. case .systemMedium:
  50. // 深度布局,屏幕深度
  51. ZStack(alignment: .center, content: {
  52. // 背景图
  53. Image("2").resizable().aspectRatio(contentMode: .fill)
  54. // 水平
  55. HStack(alignment: .top, spacing: 5, content: {
  56. // 左侧图
  57. Image("1").resizable().aspectRatio(contentMode: .fit).frame(width: 200, height: 80, alignment: .leading).cornerRadius(10.0).foregroundColor(.blue)
  58. // 垂直
  59. VStack(alignment: .trailing, spacing: 5, content: {
  60. // 右侧文字
  61. Text("zh组件1").foregroundColor(.blue)
  62. Text("小组件2").foregroundColor(.blue).lineLimit(2)
  63. }).foregroundColor(.gray)
  64. })
  65. }).widgetURL(URL(string: "widgetExtensionDemo://test2"))
  66. case .systemLarge:
  67. // 深度布局,屏幕深度
  68. ZStack(alignment: .center, content: {
  69. // 背景图
  70. Image("2").resizable().aspectRatio(contentMode: .fill)
  71. // 水平
  72. HStack(alignment: .center, spacing: 5, content: {
  73. // 左侧图
  74. Image("1").aspectRatio(contentMode: .fit).cornerRadius(10.0).frame(width: 200, height: 100, alignment: .leading)
  75. // 垂直
  76. VStack(alignment: .center, spacing: 5, content: {
  77. // 右侧文字
  78. Text("小组件1").foregroundColor(.blue)
  79. Text("小组件2").foregroundColor(.blue).lineLimit(2)
  80. })
  81. }).foregroundColor(.blue)
  82. }).widgetURL(URL(string: "widgetExtensionDemo://test3"))
  83. default:
  84. // 深度布局,屏幕深度
  85. ZStack(alignment: .center, content: {
  86. // 背景图
  87. Image("2").resizable().aspectRatio(contentMode: .fit)
  88. // 水平
  89. HStack(alignment: .center, spacing: 5, content: {
  90. // 左侧图
  91. Image("1").frame(width: 80, height: 80, alignment: .center).aspectRatio(contentMode: .fit).cornerRadius(10.0)
  92. // 垂直
  93. VStack(alignment: .center, spacing: 5, content: {
  94. // 右侧文字
  95. Text("小组件1").foregroundColor(.blue)
  96. Text("小组件2").foregroundColor(.blue).lineLimit(2)
  97. })
  98. })
  99. }).widgetURL(URL(string: "widgetExtensionDemo://test1"))
  100. }
  101. }
  102. }

参考1

参考2

iOS桌面小插件 Widget Extension的更多相关文章

  1. Android桌面小插件——Widget

    Android桌面小插件--Widget 效果图 实现 1. 创建Widget类 创建一个Widget类,并实现页面创建的时候,就实现显示时间 package com.kongqw.kqwwidget ...

  2. Android-Widget桌面小组件

    1, 掌握Widget的用:Widget的用途,能够添加到手机桌面的程序 2, Widget的特点和用法步骤: 特点:快捷,方便,个性化,可自定义功能,可及时控制更新Widget显示内容 3, 用法步 ...

  3. AppWidgetProvider 桌面插件 Widget 广播 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  4. 【转】【iOS测试系列】常用测试小插件的使用

    背景介绍 由于iOS系统的限制,在非越狱的自动化测试中无法实现一些常用的功能,比如不同应用之间来回切换.模拟全局的点击事件等等.但是在越狱的环境下,这些限制就不存在了,我们可以利用各种小插件来实现我们 ...

  5. 闲聊select和input常用的小插件

    前言 在pc端的项目中,经常会用到表单标签,莫过于是select和input这两种,这两种相当常用.但往往原生的功能不尽人意,即使 input中type有n多属性,甚至连时间控件都有,但仍旧满足不了我 ...

  6. 【Bootstrap】优秀小插件收集

    Bootstrap中不乏很多优秀的小插件来让界面更加漂亮.比如之前做过笔记的bootstrap-fileinput,select2,datetimepicker等都是属于这一系列的.这些相对而言比较大 ...

  7. Android 桌面小部件

    1. 添加AppWidgetProvider 实际上就是个带有界面的BroadcastReceiver public class SimpleWidgetProvider extends AppWid ...

  8. Android开发中实现桌面小部件

    详细信息请参考原文:Android开发中实现桌面小部件 在Android开发中,有时候我们的App设计的功能比较多的时候,需要根据需要更简洁的为用户提供清晰已用的某些功能的时候,用桌面小部件就是一个很 ...

  9. 桌面小部件AppWidgetProvider简单分析

    1.一般桌面小部件涉及到的类 AppWidgetProvider :BroadcastRecevier子类,用于接收更新,删除通知 AppWidgetProvderInfo:AppWidget相关信息 ...

随机推荐

  1. Nginx代理常用参数

    目录 一:Nginx代理常用参数 1.添加发往后端服务器的请求头信息 二:参数案例 1.lb01配置文件 2.web01 web02 web服务器 3.测试 4.重启 5.DNS域名解析 6.网址测试 ...

  2. fio硬盘压力测试

    fio测试工具支持同步(pread/pwrite)和异步(libaio)FIO是测试IOPS的非常好的工具,用来对硬件进行压力测试和验证,支持13种不同的I/O引擎,包括:sync,mmap, lib ...

  3. 文本图Tranformer在文本分类中的应用

    原创作者 | 苏菲 论文来源: https://aclanthology.org/2020.emnlp-main.668/ 论文题目: Text Graph Transformer for Docum ...

  4. Serverless Workflow项目

    维基百科对工作流的定义是:对工作流程及其各操作步骤之间业务规则的抽象.概括描述.我们认为工作流的主要职责是: 保证结果一致性,提高容错性要求:对错误重试,捕获,执行回滚或补偿逻辑 为长时间运行的流程维 ...

  5. 在Android中用纯Java代码布局

    感谢大佬:https://www.jianshu.com/p/7aedea560f16 在Android中用纯Java代码布局 本文的完成了参考了一篇国外的教程,在此表示感谢. Android中的界面 ...

  6. RPC原理及RPC实例分析(转)

    出处:https://my.oschina.net/hosee/blog/711632 在学校期间大家都写过不少程序,比如写个hello world服务类,然后本地调用下,如下所示.这些程序的特点是服 ...

  7. mac不能用ip访问项目的, 还有80端口不能开启的问题

    开启80端口 参考这篇文章 解决mac无法使用80端口问题,亲测可用 1. 防火墙设置问题 2. 网卡问题 3. 服务器地址配置问题

  8. MySQL server has gone away 异常

    原因 一种可能是发送的SQL语句太长,以致超过了max_allowed_packet的大小,如果是这种原因,你只要修改my.cnf,加大max_allowed_packet的值即可. 还有一种可能是因 ...

  9. centOs编译安装php7.2支持微擎php扩展

    发现yum安装许多坑 于是只好编译安装 第一步得到镜像地址 在      https://www.php.net/downloads.php     有的地址比较慢,需要耐心等待 cd /usr/lo ...

  10. KVC替换系统的tabbar为自定义tabbar---秀清

    CustomTabbar *tabbar = [[CustomTabbar alloc]init]; //KVC,更换系统的tabbar为自定义tabbar tabbar.tabbarDelegate ...