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: 这就是主视图了,在这里自定义页面用来显示在手机桌面。

import WidgetKit
import SwiftUI
import Intents // 控制器,类似Controller,这里可以用来做小组件的刷新操作
struct Provider: IntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), configuration: ConfigurationIntent())
} func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), configuration: configuration)
completion(entry)
} func getTimeline(for configuration: ConfigurationIntent, 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, configuration: configuration)
entries.append(entry)
} let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
} // 数据模型,数据显示在View上必须经过这里
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationIntent
} // View,小组件的界面
struct WidgetExtensionEntryView : View {
var entry: Provider.Entry var body: some View {
Text(entry.date, style: .time) }
} // 程序入口,初始化相关信息,如Provider,View等
@main
struct WidgetExtension: Widget {
let kind: String = "WidgetExtension" var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
WidgetExtensionEntryView(entry: entry)
}
.configurationDisplayName("小组件")
.description("This is an 测试一下 widget.")
}
}
// 自定义样式
struct WidgetExtension_Previews: PreviewProvider {
static var previews: some View { // 设置小组件尺寸 systemSmall systemMedium systemLarge
WidgetExtensionEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}

自定义UI

自定义小组件尺寸

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

HStack、VStack、ZStack

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

传递数据

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

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

  • SceneDelegate

  • SceneDelegate中相应事件

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

适配不同尺寸小组件



// View,小组件的界面
struct WidgetExtensionEntryView : View {
@Environment(\.widgetFamily) var family:WidgetFamily
var entry: Provider.Entry var body: some View {
switch family {
case .systemSmall:
// 深度布局,屏幕深度
ZStack(alignment: .center, content: {
// 背景图
Image("2").resizable().aspectRatio(contentMode: .fill)
// 水平
HStack(alignment: .center, spacing: 5, content: {
// 左侧图
Image("1").frame(width: 80, height: 80, alignment: .center).aspectRatio(contentMode: .fit).cornerRadius(10.0)
// 垂直
VStack(alignment: .center, spacing: 5, content: {
// 右侧文字
Text("小组件1").foregroundColor(.blue)
Text("小组件2").foregroundColor(.blue).lineLimit(2)
}) })
}).widgetURL(URL(string: "widgetExtensionDemo://test1"))
case .systemMedium:
// 深度布局,屏幕深度
ZStack(alignment: .center, content: {
// 背景图
Image("2").resizable().aspectRatio(contentMode: .fill)
// 水平
HStack(alignment: .top, spacing: 5, content: {
// 左侧图
Image("1").resizable().aspectRatio(contentMode: .fit).frame(width: 200, height: 80, alignment: .leading).cornerRadius(10.0).foregroundColor(.blue)
// 垂直
VStack(alignment: .trailing, spacing: 5, content: {
// 右侧文字
Text("zh组件1").foregroundColor(.blue)
Text("小组件2").foregroundColor(.blue).lineLimit(2)
}).foregroundColor(.gray) })
}).widgetURL(URL(string: "widgetExtensionDemo://test2"))
case .systemLarge:
// 深度布局,屏幕深度
ZStack(alignment: .center, content: {
// 背景图
Image("2").resizable().aspectRatio(contentMode: .fill)
// 水平
HStack(alignment: .center, spacing: 5, content: {
// 左侧图
Image("1").aspectRatio(contentMode: .fit).cornerRadius(10.0).frame(width: 200, height: 100, alignment: .leading)
// 垂直
VStack(alignment: .center, spacing: 5, content: {
// 右侧文字
Text("小组件1").foregroundColor(.blue)
Text("小组件2").foregroundColor(.blue).lineLimit(2)
}) }).foregroundColor(.blue)
}).widgetURL(URL(string: "widgetExtensionDemo://test3")) default:
// 深度布局,屏幕深度
ZStack(alignment: .center, content: {
// 背景图
Image("2").resizable().aspectRatio(contentMode: .fit)
// 水平
HStack(alignment: .center, spacing: 5, content: {
// 左侧图
Image("1").frame(width: 80, height: 80, alignment: .center).aspectRatio(contentMode: .fit).cornerRadius(10.0)
// 垂直
VStack(alignment: .center, spacing: 5, content: {
// 右侧文字
Text("小组件1").foregroundColor(.blue)
Text("小组件2").foregroundColor(.blue).lineLimit(2)
}) })
}).widgetURL(URL(string: "widgetExtensionDemo://test1"))
} }
}

更多小组件创建

  • 重写@main入口
// 更多小组件
@main
struct Widgets:WidgetBundle {
init() { } @WidgetBundleBuilder
var body: some Widget{ // 最多创建5次,也就是15个小组件
WidgetExtension()
CustomWidget()
CustomWidget()
CustomWidget()
CustomWidget()
} } struct CustomWidget:Widget {
var kind:String="自定义组件"
var body: some WidgetConfiguration{
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
CustomEntryView(entry:entry)
}
.configurationDisplayName("自定义更多组件")
.description("ios14自定义更多小组件")
} }
// 自定义Ui
struct CustomEntryView:View {
@Environment(\.widgetFamily) var family:WidgetFamily
var entry: Provider.Entry
@ViewBuilder
var body: some View {
switch family {
case .systemSmall:
// 深度布局,屏幕深度
ZStack(alignment: .center, content: {
// 背景图
Image("2").resizable().aspectRatio(contentMode: .fill)
// 水平
HStack(alignment: .center, spacing: 5, content: {
// 左侧图
Image("1").frame(width: 80, height: 80, alignment: .center).aspectRatio(contentMode: .fit).cornerRadius(10.0)
// 垂直
VStack(alignment: .center, spacing: 5, content: {
// 右侧文字
Text("小组件1").foregroundColor(.blue)
Text("小组件2").foregroundColor(.blue).lineLimit(2)
}) })
}).widgetURL(URL(string: "widgetExtensionDemo://test1"))
case .systemMedium:
// 深度布局,屏幕深度
ZStack(alignment: .center, content: {
// 背景图
Image("2").resizable().aspectRatio(contentMode: .fill)
// 水平
HStack(alignment: .top, spacing: 5, content: {
// 左侧图
Image("1").resizable().aspectRatio(contentMode: .fit).frame(width: 200, height: 80, alignment: .leading).cornerRadius(10.0).foregroundColor(.blue)
// 垂直
VStack(alignment: .trailing, spacing: 5, content: {
// 右侧文字
Text("zh组件1").foregroundColor(.blue)
Text("小组件2").foregroundColor(.blue).lineLimit(2)
}).foregroundColor(.gray) })
}).widgetURL(URL(string: "widgetExtensionDemo://test2"))
case .systemLarge:
// 深度布局,屏幕深度
ZStack(alignment: .center, content: {
// 背景图
Image("2").resizable().aspectRatio(contentMode: .fill)
// 水平
HStack(alignment: .center, spacing: 5, content: {
// 左侧图
Image("1").aspectRatio(contentMode: .fit).cornerRadius(10.0).frame(width: 200, height: 100, alignment: .leading)
// 垂直
VStack(alignment: .center, spacing: 5, content: {
// 右侧文字
Text("小组件1").foregroundColor(.blue)
Text("小组件2").foregroundColor(.blue).lineLimit(2)
}) }).foregroundColor(.blue)
}).widgetURL(URL(string: "widgetExtensionDemo://test3")) default:
// 深度布局,屏幕深度
ZStack(alignment: .center, content: {
// 背景图
Image("2").resizable().aspectRatio(contentMode: .fit)
// 水平
HStack(alignment: .center, spacing: 5, content: {
// 左侧图
Image("1").frame(width: 80, height: 80, alignment: .center).aspectRatio(contentMode: .fit).cornerRadius(10.0)
// 垂直
VStack(alignment: .center, spacing: 5, content: {
// 右侧文字
Text("小组件1").foregroundColor(.blue)
Text("小组件2").foregroundColor(.blue).lineLimit(2)
}) })
}).widgetURL(URL(string: "widgetExtensionDemo://test1"))
} }
}

参考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. 深入了解promise

    1. Promise基础 什么是回调地狱? 当使用回调函数来进行事件处理的时候,如果嵌套多层回调函数的时候,就会出现回调地狱,例如: method1(function(err, result) { i ...

  2. 在build中配置resources, 来防止我们资源导出失败问题

    <!--在build中配置resources, 来防止我们资源导出失败问题--> <build> <resources> <resource> < ...

  3. 题解 - 「MLOI」小兔叽

    小兔叽 \(\texttt{Link}\) 简单题意 有 \(n\) 个小木桩排成一行,第 \(i\) 个小木桩的高度为 \(h_i\),分数为 \(c_i\). 如果一只小兔叽在第 \(i\) 个小 ...

  4. Spring系列7:`autowire`自动装配怎么玩

    回顾 前几篇我们介绍各种依赖依赖注入,都是显式指定的,配置明确但同时也有些繁杂和重复."很多发明的出发点,都是为了偷懒,懒人是推动社会进步的原动力".Spring 提供了自动注入依 ...

  5. ApacheCN 数据科学译文集 20210313 更新

    新增了五个教程: Python 和 Jupyter 机器学习入门 零.前言 一.Jupyter 基础知识 二.数据清理和高级机器学习 三.Web 爬取和交互式可视化 Python 数据科学和机器学习实 ...

  6. 精通 TensorFlow 1.x·翻译完成

    原文:Mastering TensorFlow 1.x 协议:CC BY-NC-SA 4.0 不要担心自己的形象,只关心如何实现目标.--<原则>,生活原则 2.3.c 在线阅读 Apac ...

  7. 技术管理进阶——Leader应该关注成长慢的同学吗?

    原创不易,求分享.求一键三连 两个故事 我该怎么办? ​在大学毕业的时候,恩师跟我说了一个故事: 有一个女同学跟他说,不知道毕业了该干撒,不知道该怎么办. 正处于「低谷期」的恩师突然一怔,想到貌似自己 ...

  8. JSP response.setCharacterEncoding与response.setContentType的区别

    问题描述 昨天在参考别人的项目时,发现页面引用js,css等文件总是乱码,后来才发现是MIME类型统一设置为text/html,并且仅仅编码设置了浏览器端的解析编码.另外,可以先通过文本编辑器(如no ...

  9. Java 自定义窗体(标题栏、窗体背景)

    感谢大佬:https://blog.csdn.net/ltx06/article/details/28996839 最近在书上看到这篇文章,觉得挺有意思.与大家分享一下,具体可以参见明日科技出版的&l ...

  10. jdk1.5新特性之-----自动装箱与自动拆箱

    import java.util.ArrayList; /* jdk1.5新特性之-----自动装箱与自动拆箱. java是面向对象 的语言,任何事物都可以使用类进行描述,sun就使用了 一些类描述j ...