iOS小组件

打印 上一主题 下一主题

主题 799|帖子 799|积分 2397

基本知识

时间轴

小组件通过AppIntentTimelineProvider举行 UI 刷新
  1. struct Provider: AppIntentTimelineProvider {
  2.         func placeholder(in context: Context) -> SimpleEntry {
  3.             // 添加占位的(选择添加的时候使用)
  4.             // todo
  5.         }
  6.         func snapshot(for configuration: WidgetCfgIntent, in context: Context) async -> SimpleEntry {
  7.             // 添加预览的时候会调用, 建议这里进行和timeline方法一样的数据处理。
  8.             // todo
  9.         }
  10.         
  11.         func timeline(for configuration: WidgetCfgIntent, in context: Context) async -> Timeline<SimpleEntry> {
  12.           // 返回每个时间点的数据。
  13.             var entries: [SimpleEntry] = []
  14.            // 按需添加自己的时间片段
  15.            // entries.append(entry)
  16.             let nextTimeMin = 20
  17.             let nextUpDate = Calendar.current.date(byAdding: .minute, value:nextTimeMin, to: .now) ?? Date(timeIntervalSince1970: Date().timeIntervalSince1970 + Double(nextTimeMin*60)) // 16-50分钟刷新一次, 不能设置时间太小,太小会被系统忽略
  18.             return Timeline(entries: entries, policy: .after(nextUpDate))
  19.         }
  20.         // 推荐配置。
  21.         func recommendations() -> [AppIntentRecommendation<WidgetCfgIntent>] {
  22.            // todo
  23.         }
  24.     }
复制代码
数据共享

与其他扩展一样,小组件可以通过Group 的UserDefault 共享数据。 还可以通过 SwiftData 共享数据。
交互

widgetURL:所有地域
Link: 不同元素
具有交互性的 Tog 或 Button(iOS 16):需要基于 AppIntent (与体系通用的 Intent 共用)参考https://developer.apple.com/documentation/widgetkit/adding-interactivity-to-widgets-and-live-activities
AppIntet 理解

Intent: 提供的一个小功能(通过入参、实现某个功能、返回什么结果),好比打开什么、记载什么
Entity:用来表示 App的内容,提供给 Intent 利用(对这个小功能的抽象数据)。
AppShortcut:用来包装 Intent,使之能被体系大概调用方发现。
APPIntent 怎样返回数据
  1. func perform() async throws -> some IntentResult & ReturnsValue<Int> {
  2. // 如果需要返回其他类型,在 Int 处替换。
  3. ​                let returnValue: Int = 1
  4. ​                //在这里添加你要执行的代码
  5. ​                return .result(value: returnValue)
  6. ​        }
复制代码
APPIntent 的参数通常用@Parameter标志, 支持基本常用值, 也可以继承AppEntity或AppEnum–枚举举行自定义范例。
   实现了 AppIntent 的功能默认自动提供给快捷指令利用。注意多语言和相关测试
  小组件更新

主 APP 内:通过WidgetCenter刷新
  1. // 通过 kind 刷新某个类型小组件
  2. WidgetCenter.shared.reloadTimelines(ofKind: "com.mygame.gamestatus")
  3. // 刷新所有小组件
  4. WidgetCenter.shared.reloadAllTimelines()
复制代码
体系刷新:


  • 通过时间轴刷新(15~50分钟一次,设置时间小了,体系也会忽略)
  • 执行交互型 Intent 后会触发刷新
支持小组件范例

  1. struct DemoWidget: Widget {
  2.     var body: some WidgetConfiguration {
  3.       // AppIntentConfiguration 为可以配置的小组件
  4.         AppIntentConfiguration(kind: kind, intent: WidgetCfgIntent.self, provider: Provider()) { entry in
  5.             SwiftUIWidgetEntryView(entry: entry)
  6.                 .widgetBackground(Color.container) // 添加自定义背景色(扩展方法),注意小组件最底层始终有一个底色。 无法做到像 Apple Home 那样的半透明背景效果(据说是只有系统才能用的 API)
  7.         }
  8.         .configurationDisplayName("展示名字")
  9.         .description("描述")
  10.         .supportedFamilies(supportFamilies) // 支持小组件类型。
  11.         .disableContentMarginsIfNeeded() // 忽略边距(扩展方法)
  12.     }
  13.     private var supportFamilies:[WidgetFamily] {
  14.         return [.systemSmall, .systemMedium, .systemLarge]
  15.     }
  16.     private var kind: String {
  17.         return "com.xxxx.Widget"
  18.     }
  19. }
复制代码
小组件配置

通过继承WidgetConfigurationIntent 实现。探索 App Intents 的功能更新 – 小专栏
一组一组配置

相关接口:https://developer.apple.com/documentation/appintents/intentitemcollection
  1. struct DeviceDefaultProvider:DynamicOptionsProvider
  2. // 注意这里需要返回 IntentItemCollection, 即分组显示
  3. func results() async throws -> IntentItemCollection<DeviceEntity>  {
  4. //        return ItemCollection(promptLabel: "----collection") {
  5. //            ItemSection("title1", subtitle: "subtitle1", image: DisplayRepresentation.Image(named: "pic_xxxx")) {
  6. //                DeviceEntity(deviceID: "111", name: "device111")
  7. //                DeviceEntity(deviceID: "112", name: "device112")
  8. //            }
  9. //            ItemSection("title2", subtitle: "subtitle2", image: DisplayRepresentation.Image(named: "pic_em0xxxx")) {
  10. //                DeviceEntity(deviceID: "211", name: "device211")
  11. //                DeviceEntity(deviceID: "212", name: "device212")
  12. //            }
  13. //        }
  14. }
复制代码
结果图
小组件扩展

  1. import WidgetKit
  2. import SwiftUI
  3. extension View {
  4.     /// 统一设置备件
  5.     @ViewBuilder
  6.     func widgetBackground(_ backgroundView: some View) -> some View {
  7.         if Bundle.main.bundlePath.hasSuffix(".appex"){ // 小组件才生效
  8.             if #available(iOS 17.0, *) {
  9.                 containerBackground(for: .widget) {
  10.                     backgroundView
  11.                 }
  12.             } else {
  13.                 background(backgroundView)
  14.             }
  15.         } else {
  16.             background(backgroundView)
  17.         }
  18.     }
  19. }
  20. extension WidgetConfiguration {
  21.     func disableContentMarginsIfNeeded() -> some WidgetConfiguration {
  22.         if #available(iOSApplicationExtension 17.0, *) {
  23.             // 禁用边距
  24.             return self.contentMarginsDisabled()
  25.         } else {
  26.             return self
  27.         }
  28.     }
  29. }
复制代码
小组件刷新失败

entities 查询返回数据空

背景:我在主 APP 修改了小组件表现数据源, 同时调用了WidgetCenter.shared.reloadAllTimelines刷新,但是回到小组件,数据依然没有变革,通过调试,控制台打印如下:
  1. Error getting AppIntent from LNAction: AppIntent has missing parameter value for 'xxxx'. You may need to set a default value in the initializer of your @Parameter, or using the default method on your Query.
  2. No AppIntent in timeline(for:with:)
复制代码
  上面这种情况属于 APP 内自动刷新失效, 但是体系的时间轴刷新有用。
  通过反复测试发现,在桌面小组件自定义配置后才发生。
我的配置参数是基于 AppEntity 实现的自定义范例。
发现每次在调用 EntityQuery 的entities(for)方法,内部返回空数组,都会打印上面的错误。
  1. struct DeviceEntityQuery: EntityQuery, Sendable {
  2.     // 通过 ID 查询的会调用, (比如通过配置匹配,提供数据源等)
  3.     func entities(for identifiers: [DeviceEntity.ID]) async throws -> [DeviceEntity] {
  4.         //具体逻辑:通过 ID 过滤数据。 如果没匹配到,就返回空。
  5.     }
  6. }
复制代码
APP自动刷新小组件 -> 通过自定义配置在 EntityQuery 方法中查询 -> 查询到,触发刷新小组件的生命周期方法,(没查询,好比返回空数组,则不会刷新小组件生命周期方法)
结论:在entities(for)的返回结果时,不要返回空数组,否则可能无法刷新。
解决方法:如果没有匹配到,不返回空数组,构建对应的 Entity 返回。
其他问题

验证策约

在 AppIntent 协议定义的属性中,包含
  1. static var authenticationPolicy: IntentAuthenticationPolicy { get } //默认值为alwaysAllowed
复制代码
该属性可以配置 Intent 运行验证策略
值分别为
  1. /// 一直允许允许,包括锁屏情况下
  2. case alwaysAllowed
  3. /// 需要验证、通过类似 Apple Watch 验证都可以执行
  4. case requiresAuthentication
  5. /// 需要本地验证,即一定需要手机解锁,有Watch 在也需要解锁。
  6. case requiresLocalDeviceAuthentication
复制代码
  注意这个配置是对Intent有用,设置alwaysAllowed后,实际测试发现提供给快捷指令利用时不需要解锁,小组件运行改 Intent 还是需要解锁。
  widgetURL配置

征象:通过 WidgtURL装饰器配置的 URL 跳转非常。
缘故原由:我在最底层配置得了 WidgetURL, 在子视图也配置了 Widget,导致跳转非常. 根据官网文档表明,如果有多层View 利用了 WidgetURL, 状态和跳转将是不可预料的(在 iOS18.0上,多数情况下都相应最底层的 WidgetURL)
解决方案:通过 Link 大概 Button(AppIntent 交互)处理连接。
  1. Link(destination: URL(string: "xxxxx" ) {
  2. // your view           
  3. }
复制代码
Summery 怎么多语言化

利用新的多语言文件 xcstrings
利用占位模式
  1. Get the daylight duration on ${date} in ${location}
复制代码
利用示例
  1. Summary("Switch \(\.$device) \(\.$isOn)")// 可选, table: "Localizable1")
复制代码
  注意:变量名要同等
  Next

将 App 控制扩展到体系级别: 将功能扩展到体系控制,通过范例小组件 AppIntent 的方式。
资料参考

Apple 官方指导:https://developer.apple.com/cn/documentation/widgetkit/
iOS 小组件系列教程:https://juejin.cn/post/7297513663435210771?searchId=2024082914404396F94C65488FE5528F7E
iOS 小组件开辟全面总结 – 掘金
WidgetExamples – github
SwiftUI 控件 Demo – github
SwiftUI 细节学习参考 --肘子的记事本: 各种SwiftUI 控件、结构理解。
给 Widget 添加动画–一个特别的方法:利用 Font 大概 gif 图片分解来实现动画。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

北冰洋以北

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表