iOS14 WidgetDemo使用

1.创建项目
正常的创建项目流程,我使用的是Object-C语言。
创建项目完成后引入Widget Extension。

File -> New -> target-> Widget Extension ->Next

由于是加入一个新的Target,所以Widget的名字不能与项目名相同,也不能起成“Widget”(因为Widget是一个已有的类名),删除时不能只是删除文件还要在项目的Targets中删除,起已经删除过一次的名字会报找不到文件的错误。如果 Widget 支持用户配置属性(例如天气组件,用户可以选择城市),就需要勾选Include Configuration Intent这个选项。

image.png

创建后,会自动生成5个struct和自带的方法。

2.探究自带的方法用法
预览视图-Previews

代码运行的预览视图是SwiftUI新特性,会将运行成果显示在右边的视图上且支持热更新,但是会很卡,它不是Widget的必须部分,可以直接将其删除或注释。

struct WidgetView_Previews: PreviewProvider {
    static var previews: some View {
        WidgetViewEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
            .previewContext(WidgetPreviewContext(family: .systemMedium))
        
    }
}

需要注意:Widget只支持3种尺寸systemSmall (2x2)、 systemMedium (4x2)、 systemLarge(4x4)
同时,一个APP可以支持多个不同的Widget。
数据提供-Provider
Provider是Widget最重要的部分,它决定了小组件的placeholder/getSnapshot/getTimeline这三种数据的显示。在项目创建时勾选了Include Configuration Intent后的话,Provider继承自IntentTimelineProvider支持用户自主编辑,没有勾选则继承自TimelineProvider不支持用户自主编辑。

struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date())
    }

    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date())
        completion(entry)
    }

    func getTimeline(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)
            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

官方的示例代码的意思是:显示从现在开始的5个小时的每个小时的时间,再显示完之后又重新运行一次getTimeline。所以,我们只需要控制刷新时间的Calendar.Component的Value与entries中元素的个数,并设置TimeLine的 policy 就可以控制Widget的刷新时间,次数和方法。但是,这个刷新次数是有苹果官方是有限制的,5分钟刷新一次是极限,低于5分钟刷新官方会觉得刷新次数太多,高频率的刷新也会导致耗电量的增加。

Timeline里面有三种方式:atEnd,after(date),never
atEnd: timeline 中最后一个 entry 显示后更新。timelines 方法会重新调用。
after(date): 指定日期,重新更新timeline。
never:系统不会自动更新,除非我们主动通过 Widget Center Api 来更新。

例子:实现一个按秒刷新的时钟,为了每一秒尽可能的准确刷新就应该向entries提供0-299这300秒的300个时间数据,View展示时转换成具体到秒的字符串展示即可,运行一个周期后再次获取5分钟的时间数据。但是我测试了之后,发现在刷新了一段时间之后,小组件就不在刷新了,所以,如果要做定时器还是有问题。

var currentDate = Date()
            // 每5分钟刷新一次
        let refreshDate = Calendar.current.date(byAdding: .minute, value: 5, to: currentDate)!
        var arr:[SimpleEntry] = []
        var tempDate = Date()
            for idx in 0...300 {
                tempDate = Calendar.current.date(byAdding: .second, value: idx, to: currentDate)!
                let tempEntry = SimpleEntry(date: tempDate, configuration: configuration)
                arr.append(tempEntry)
            }
        let timeline = Timeline(entries: arr, policy: .after(refreshDate))
        completion(timeline)

也可以主动刷新

//刷新所有 widget
WidgetCenter.shared.reloadAllTimelines

//或者

//刷新某一个widget.  xxxx 是该widget的 identifier
WidgetCenter.shared.reloadTimelines(ofKind: "xxxx")

宿主App的数据有更新时,可以主动刷新 widget UI。具体怎么做呢?对于宿主App是 OC 写的项目,需要下面两步:

建立桥接文件
一般在新建 swift 文件时 Xcode 会自动弹出是否建立 bridge 文件的弹框,选择创建。
也可以自己新建一个 Header file 文件,然后指定 Targets-> Swift Compiler -> Object-C Bridging Header -> Header文件
建立swift文件
WidgetCenter是 swift 的类,这里我们新建一个 swift 文件作为我们的刷新工具类, 里面代码如下:

// 导入 widget kit 库
import WidgetKit
//声明 14以上的系统才可用此 api
@available(iOS 14.0, *)
//定义 oc 方法
@objcMembers final class WidgetKitHelper : NSObject {
    class func reloadAllWidgets() {
       // arm64架构真机以及模拟器可以使用
        #if arch(arm64) || arch(i386) || arch(x86_64)
            WidgetCenter.shared.reloadAllTimelines()
        #endif
    }
}

刷新的时候,直接调用

[WidgetKitHelper reloadAllWidgets]
数据共享

我们刷新 widget 是因为有数据更新了所以要刷新 UI。那 widget 如何取出宿主App 的数据进行刷新呢?也需要两步:

  1. 建立 App GroupApp Group 定义一个唯一标识比如:group.com.yourcompany.xxx,同时,开发者证书的 Capabilities 这一项也要把 App Group 勾选上

  2. widget 中取出数据

UserDefaults(suiteName: "group.com.yourcompany.xxx").object(forKey: "xxxxxx")

下面是写组件UI的地方
UI用的是SwiftUI,具体语法可以参考这里https://gitee.com/TheAlgorithms/SwiftUI#HStack

struct WidgetViewEntryView : View {
    var entry: Provider.Entry
    static let taskDateFormat: DateFormatter = {
            let formatter = DateFormatter()
            formatter.dateFormat = "HH:mm:ss"
            return formatter
    }()
    var body: some View {
        Link(destination: URL(string: "widgetDemo://799")!){
            VStack {
                Text("\(entry.date, formatter: Self.taskDateFormat)")
            }
            .frame(height: 20)
            
        }
        Link(destination: URL(string: "widgetDemo://798")!){
            VStack {
                Text("I'm a Button")
            }
            .frame(height: 20)
            
        }

    }
}

通过Link标记控件 点击对应控件,跳转到appdeleget里,根据传过来的url参数,执行对应的操作。需要注意,systemSmall,小的Widget不支持这种link点击,小的Widget点击之后 直接跳转进去app。

-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options{
    NSLog(@"%@",url.absoluteString);
    if ([url.scheme isEqualToString:@"widgetDemo://799"]){
        //执行跳转后的操作
    }
    return YES;
}

最后是小组件用户手动配置数据


image.png

创建小组件的时候,要勾选这个配置项,intentdefinition的配置页面,在下面的地方增加一个title属性,String类型,注意右边的四个选项只需要勾选第2个。
image.png

回到代码实现页面,我们只要修改下WidgetEntryView的body里面的Text内容,从entry里面获取到configuration配置的title属性:
var body: some View {
//        Text(entry.date, style: .time)
        Text(entry.configuration.title == nil ? "没有值" : entry.configuration.title!)
    }

然后 就可以实现小组件显示用户输入文字。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容