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. python3 爬取深圳主板公司名称,公司网址

    需要阅读的文档: Requests:http://cn.python-requests.org/zh_CN/latest/user/quickstart.html BeautifulSoup:http ...

  2. 深入了解promise

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

  3. yum更新,docker安装

    备份 cd /etc/yum.repos.d/ mkdir repo_bak mv *.repo repo_bak/ 安装wget(若已安装了wget,则跳过此步 wget -V yum instal ...

  4. 深入学习python内存管理

    深入Python的内存管理   作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 语言的内存管理是语言设计的一个重要方面.它是决定语 ...

  5. Java项目开发中实现分页的三种方式一篇包会

    前言   Java项目开发中经常要用到分页功能,现在普遍使用SpringBoot进行快速开发,而数据层主要整合SpringDataJPA和MyBatis两种框架,这两种框架都提供了相应的分页工具,使用 ...

  6. Python property动态属性

    from datetime import datetime, date class User: def __init__(self, name, birthday): self.name = name ...

  7. js trim()方法

    从字符串中移除前导空格.尾随空格和行终止符. 语法 stringObj.trim() 参数 stringObj 必选.String 对象或字符串.trim 方法不修改该字符串. 返回值 已移除前导空格 ...

  8. 一致性协议之ZAB

    前言 一致性协议 包括 Paxos,Raft,2PC,3PC等等,今天我们讲一种协议,ZAB 协议,该协议应该是所有一致性协议中生产环境中应用最多的了.为什么呢?因为他是为 Zookeeper 设计的 ...

  9. java创建自定义类的对象数组

      1 public class Student{ 2 static int number = 0; // 静态变量的访问可以不用创建类的实例就可就可使用< 类名.属性 >的方法访问 3 ...

  10. LNMP架构的源码编译以及yum安装

    LNMP架构的源码编译以及yum安装 目录 LNMP架构的源码编译以及yum安装 一.LNMP架构的编译安装 1. 安装nginx服务 (1)关闭防火墙 (2)安装依赖包 (3)创建运行用户 (4)编 ...