iOS 小组件学习记录

前言

由于公司需要,需要在桌面上增加一个小组件,让用户实时的看到盘口或者仓位信息,让我调研一番,之前没做过,经过学习现在记录一下。小组件是iOS14之后才有的功能,所以小组件功能必须要是iOS14以上。

小组件创建

  1. 创建一个新的target,点击左下角的"+"号,然后选中Widget Extension,点击Next,填好新target的名字,点击Finish,然后点击Active按钮。


    01.png
    02.png

注意:Include ConFiguration Intent这个选项勾选的意思就是我们创建的组件是可以被用户自定义编辑的
完成后,新的target会出现在这里。

03.png
  1. 这里能看到新的target的相关代码,新创建出来的文件是有默认代码的,这个@main就是小组件target的入口。
    04.png
  2. 现在可以先运行下看看效果,图二可以看到,默认的样式是有三种的,小、中、大,截图的是中样式,iOS15之后又多了一种超大模式。


    05.PNG
    06.PNG
    07.PNG

代码分析

  1. @main对应是小组件的入口,每一个小组件有且只能有一个入口。
@main
struct firstWidget: Widget {
    //小组件的标识
    let kind: String = "firstWidget"
    var body: some WidgetConfiguration {
        //Provider()是数据源,给小组件提供数据的,放在数组中,这里等于是对数组的遍历,entry是遵循TimelineEntry协议的结构体对象,是数组中的元素。
        IntentConfiguration(kind: kind, intent:              ConfigurationIntent.self, provider:              Provider()) { entry in
             // 这里会获取到下面在Provider里getTimeline方法里放在数组中的entry对象
            // 小组件的UI样式
            firstWidgetEntryView(entry: entry)
        }
        //这里是小组件的标题,添加组件(上面截图)的时有展示
        .configurationDisplayName("My Widget")
        //这是对小组件的说明,添加组件(上面截图)的时候有展示
        .description("This is an example
        widget.")
        //小组件支持的样式,iOS14有三种,小、中、大,iOS15之后有四种,多了一个超大样式。
        .supportedFamilies([.systemSmall,.system
        Medium,.systemLarge])
    }
}
  1. 我们现在看看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(timeIntervalSinceNow: -12*60*60),
           configuration: configuration)
        completion(entry)
}

方法三:最终要的方法,获取显示数据

//小组件的内容变化都依赖于TimeLine,本质是TimeLine驱动的一连串静态视图,它其实就是一个元素为TimelineEntry的数组,如果只有一个元素,那么小组件内容是一成不变的,如果需要随着时间变化,就需要给每个元素设置合适的时间,系统会在指定的时间使用元素来驱动视图
    //决定widget何时刷新
    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        
        //页面刷新的所有数据都来自这个数组,每次刷新从这个数组里取出一个数据刷新页面
        var entries: [SimpleEntry] = []
        
        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
            //数组里有很多元素,我们先展示一个元素后,什么时候展示下一个元素,由byAdding: .hour控制,默认是1小时
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate, configuration: configuration)
            entries.append(entry)
        }

        //atEnd:所有的元素显示完毕后刷新,只要还有没显示完的元素,就不会刷新当前的Timeline
        //never:永远不会刷新TimeLine,如果最后一个元素显示完毕,就不动了
        //after:是指定下次刷新的时间,系统会在这个时间对TimeLine进行刷新
        //上面可以看做是刷新的最早时间(比如说atEnd,数组中的元素刷新一遍后,立马会从头继续刷新;afte:到指定时间时立即刷新),但是因为刷新时机是由系统统一控制的,频繁的请求刷新TimeLine,可能会被系统限制反而达不到理想的刷新效果,刷新时机就会比设置的时间延后
        //系统决定每个不同的TimeLine的刷新频次,超过频次的刷新请求不会生效,高频使用的小组件会获得更多的刷新频次
        //这个方法的调用时机是系统说了算的,我们只能等系统刷新的时候在这里请求数据,然后刷新样式。不能轮询,因为轮询下来数据后,就是为了刷新组件样式,但是因为刷新频率太高的话就会被系统忽略,刷新无效。
        //系统的刷新频率从上看到大概是每天70次,刷新时机是5分钟,但是我写demo看了下,是15分钟就会刷新一次
        let timeline = Timeline(entries: entries, policy: .atEnd)
        //这里就要去刷新组件的样式了
        completion(timeline)
    }
  1. Provider提供的数据源中的元素必须是遵循了TimelineEntry的元素。
struct MyNetDataWidgetEntry: TimelineEntry {
    let date: Date
    let configuration : MyNetDataWidgetIntent
    //里面放我们要给小组件提供提供的数据,DLData是我自己创建的数据模型
    let mode : DLData  
}
  1. 数据有了,我们看看展示的UI在哪
struct firstWidgetEntryView : View {
    //Provider提供的数据
    var entry: Provider.Entry
    
    var body: some View {
        //创建小组件对应的view,默认就一个text
        Text(entry.date, style: .time)
    }
}

多组件

上面的是单个组件的的创建方法,如果我们要创建多个组件,也是可以的。

  1. 第一步是创建一个swift文件,注意target要选中你创建的小组件target。
    默认生成的代码不是这样的,需要对它进行修改,改成下面这样子,添加main标识,注意:把之前的main标识去掉
import SwiftUI
import WidgetKit
@main
struct manyWidgets: WidgetBundle {
    var body: some Widget {
        //添加多个组件 
        firstWidget()
        secondWidget()
    }
}
  1. 我们看下组件预览,已经有6种样式了,我是添加了两个组件


    08.PNG

可编辑的组件

  1. 如果你像我上面那样创建的本身就是可编辑组件的话,就会在目录看到这样一个文件
    09.png

    选中之后可以看到下面的页面
    10.png
    当然你看到的应该是没有title参数的页面,这个是我添加的,然后我们可以设置它的类型,我这里设置的字符串,以及它的默认值。

默认的名字就是Configuration,对应我们刚才代码中的let configuration: ConfigurationIntent,如果我们希望每个组件都有自己的编辑参数,就需要修改这个名字,它对应组件一,然后点击左下角“+”号在创建一个文件,修改名字,添加参数,对应组件二。

  1. 在代码中使用这个title
struct firstWidgetEntryView : View {
  var entry: Provider.Entry
  var body: some View {            Text(entry.configuration.title    ?? "")
  }
}

运行程序后,看到了上面设置的默认值,长按组件选择编辑,设置新的标题后返回桌面就看到了新的标题。


11.PNG

12.PNG

13.PNG
  1. 如果你最开始创建的是一个不可编辑的组件,看不到1中所显示的文件,那就commadn+n键创建文件,剩下的就一样了。
    14.png

组件点击跳转主项目

点击有两种方式,widgetUrl和Link,widgetUrl是作用于整个组件的,Link是作用于部分view。需要注意的是:组件的small样式是不能添加Link的,它只有一个widgetUrl,不管点击哪里,都响应的是widgetUrl,并且点击组件的任何地方,都会打开主app

  1. 每个组件只能添加一个widgetUrl,作用范围是整个组件。
GeometryReader{geo in
  //这里就是小组件的UI内容
  Text(entry.configuration.title
    ?? "")
  }.widgetURL(URL(string: "jump://first")!)

然后在主项目的AppDelegate中的openUrl方法中获取获取到url,执行想要的操作。

if ([url.absoluteString isEqualToString:@"jump://first"]) {
  NSLog(@"点击了整个first组件");
}

2.如果是想要点击局部,就需要使用Link啦


15.PNG
VStack(alignment:.leading,spacing:10){
  Text(entry.configuration.title
  ??"").frame(height:50).backgrou
    nd(Color.blue)
  Link(destination: URL(string: 
    "jump://red")!){
       Text("").frame(width: 100,height:30).background(Color.red)
  }
  Link(destination: URL(string: "jump://orange")!){
       Text("").frame(width: 100, height: 30).background(Color.orange)
  }
  Spacer()
}

然后在主项目的AppDelegate中的openUrl方法中获取获取到url,执行想要的操作。

主项目主动刷新组件

app杀死状态:小组件的刷新时机是由系统控制的,并且每天的刷新次数有限,有网友测试说是75次,小组件内部的代码主动刷新的话,也不一定会刷新,由系统控制,它觉得你刷新过于频繁了,就禁止你刷新。
app在前台或者后台保活:是可以随时刷新组件的,并且次数不受限制。

  1. 创建一个swift文件
import Foundation
import WidgetKit

@objc
@available(iOS 14.0, *)
class WidgetKitManager: NSObject {

    @objc
    static let shareManager = WidgetKitManager()
    
    /// MARK: 刷新所有小组件
    @objc
    func reloadAllTimelines() {
       #if arch(arm64) || arch(i386) || arch(x86_64)
            WidgetCenter.shared.reloadAllTimelines()
            #endif
    }

    /// MARK: 刷新单个小组件
    /*
     kind: 小组件Configuration 中的kind
     */
    @objc
    func reloadTimelines(kind: String) {
          #if arch(arm64) || arch(i386) || arch(x86_64)
        WidgetCenter.shared.reloadTimelines(ofKind: kind)
            #endif
    }
}
  1. 在想要刷新的地方调用方法刷新组件
[WidgetKitManager.shareManager reloadAllTimelines];
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容