• iOS桌面小插件 Widget Extension


    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

  • 相关阅读:
    CVE-2022-30190 Follina Office RCE分析【附自定义word钓鱼模板POC】
    Git原理及常用命令小结——实用版(ing......)、Git设置用户名邮箱
    软件开发项目 质量管理的6大关键事项
    Worthington公司有关白蛋白,无核酸酶的分子特征
    【分布式技术专题】「Zookeeper中间件」给大家学习一下Zookeeper的”开发伴侣”—Curator-Framework(基础篇)
    【atoi函数的介绍以及模拟实现】
    makefile备忘
    【MyBatis笔记01】MyBatis框架介绍以及开发环境搭建
    [附源码]java毕业设计基于实时定位的超市配送业务管理
    16. 最接近的三数之和
  • 原文地址:https://www.cnblogs.com/songliquan/p/15971298.html