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. NOIP2018 Day2T3 保卫王国

    首先不考虑强制要求的话是一个经典问题,令 \(f_{i, 0 / 1}\) 为 \(i\) 选或不选时以 \(i\) 为根的子树的最优答案.那么就有转移 \(f_{u, 0} = \sum f_{v, ...

  2. JavaScript闭包的那些事

    JavaScript闭包 1.函数在JavaScript中的地位 在介绍闭包之前,可以先聊聊函数在JavaScript中的地位,因为闭包的存在是与函数息息相关的. JavaScript之所以可以称之为 ...

  3. Nginx实现跨域配置详解

    主要给大家介绍了关于Nginx跨域使用字体文件的相关内容,分享出来供大家参考学习,下面来一起看看详细的介绍: 问题描述 今天在使用子域名访问根域名的CSS时,发现字体无法显示,在确保CSS和Font字 ...

  4. H5架构和原生架构的区别

    1.App的3种开发方式 表面上看,手机App都是同样的东西,就是手机上的应用程序,点击图标就能运行,但是它们的底层技术不一样.按照开发技术,App可以分成三大类.原生应用(简称nativeApp), ...

  5. LVS调度算法总结

    LVS 调试算法分为两种:静态方法和动态方法. 静态方法 RR:轮询 WRR:加权轮询 SH:源地址哈希,将来自于同一个IP地址的请求始终发往第一次挑中的RS,从而实现会话绑定 DH:目标地址哈希,第 ...

  6. MySQL5.7 库、表结构、表字段的查询、更改操作

    1.查询所有数据库 SHOW DATABASES; 2.查询库中所有表 写法1: ① USE [DATABASE_NAME]; ② SHOW TABLES; 写法2: SHOW TABLES FROM ...

  7. Typora软件的使用

    Typora软件 一.简介 1.该软件编写文档采用markdown格式是目前最为频繁的一种格式 2.该软件生成的文档后缀名是.md结尾 3.下载网址 https://www.typora.io/ 二. ...

  8. Solution -「HDU 6643」Ridiculous Netizens

    \(\mathcal{Description}\)   Link.   给定一棵含有 \(n\) 个结点的树,点 \(u\) 有点权 \(w_u\),求树上非空连通块的数量,使得连通块内点权积 \(\ ...

  9. Solution -「ARC 124E」Pass to Next

    \(\mathcal{Description}\)   Link.   有 \(n\) 个人站成一个环,初始时第 \(i\) 个人手里有 \(a_i\) 个球.第 \(i\) 个人可以将自己手中任意数 ...

  10. 【软件实施面试】MySQL和Oracle联合查询以及聚合函数面试总结

    软件实施面试系列文章第二弹,MySQL和Oracle联合查询以及聚合函数的面试总结.放眼望去全是MySQL,就不能来点Oracle吗?之前面过不少公司,也做过不少笔试题,现在已经很少做笔试题了.你肚子 ...