iOS Today Widget设置

环境: xcode10.1,iOS 12

效果图:


t.gif

demo地址,欢迎star

1.创建Target

  • File -> New ->target
创建Target
  • 创建完成之后的效果


    效果图
  • 默认使用Storyboard


    图片.png

2.Info.plist设置

如果想代码绘制UI,要进行以下操作

  • Bundle display name:Widget在通知栏显示的名称
  • NSExtension
    如果你是使用纯代码进行开发,请按照下面进行操作:
对于 OC

1)请删除NSExtensionMainStoryboard 的键值对和MainInterface.storyboard文件;
2)请添加NSExtensionPrincipalClass这个key,并将value设置为控制器(如TodayViewController)

对于 Swift

1)请删除NSExtensionMainStoryboard 的键值对和MainInterface.storyboard文件;
2)请添加NSExtensionPrincipalClass这个key,并将value设置为控制器(如$(PRODUCT_NAME).TodayViewController)

Swift有命名空间的问题,如果不加会报如下报错

图片.png

2.Widget细节处理

iOS 8
    1. iOS8下没有折叠和展开功能,默认的Widget高度为self.preferredContentSize设置的高度。
preferredContentSize = CGSize(width: UIScreen.main.bounds.size.width, height: 110)
  • 2.iOS8下所有组件默认右移30单位,可以通过下面的方法修改上下左右的距离
func widgetMarginInsets(forProposedMarginInsets defaultMarginInsets: UIEdgeInsets) -> UIEdgeInsets {
    return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
 }
iOS 10

iOS 10以后,有两种显示模式

NCWidgetDisplayModeCompact, // Fixed height,高度固定,最低高度为110
NCWidgetDisplayModeExpanded, // Variable height,高度可变
NCWidgetDisplayModeExpanded下高度一览表

模拟器测试结果(实际结果应以真机为准).png

. 修正:iPhoneX 和XS尺寸相同,但是最大的widget高度是不同的,上图上是模拟器的测试结果,XS的的最大高度应该是748。另外widget的最小高度也不一定是110(大部分情况下是),XS的高度是98,所以最好使用系统的方法动态的获取高度(extensionContext?.widgetMaximumSize(for: .compact).height)

  • 在设置最大高度的时候要考虑到具体机型和系统
  • Widget 的收起、展开 则是通过这个代理方法:
/**
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize {
    if(activeDisplayMode == NCWidgetDisplayModeCompact) {
        // 尺寸只设置高度即可,因为宽度是固定的,设置了也不会有效果
        self.preferredContentSize = CGSizeMake(0, 110);
    } else {
        self.preferredContentSize = CGSizeMake(0, 310);
    }
}
  • 在设置 UI 的过程中,若想使用本体 Target 中的类:


    图片.png

3.数据共享

  • 主App target设置Group
开启Group

添加Group标识
  • Widget target设置Group

    同上

  • Userdefault共享数据

设置数据

//主项目设置widget数据(通过Userdefaults)  AppDelegate.swift
    private func setWidgetData() {
        let userDafault = UserDefaults(suiteName: "group.tsc")
        userDafault?.set("主App名字(Userdefaults)", forKey: "FirstDataName")
        userDafault?.set("主App内容(Userdefaults)", forKey: "FirstDataContent")
        userDafault?.synchronize()
    }

取数据

//获取主项目设置widget数据(通过Userdefaults)TodayViewController.swift
    func fetchWidgetData() -> WidgetModel {
        let userDefault = UserDefaults(suiteName: "group.tsc")

        let model = WidgetModel(name: userDefault?.string(forKey: "FirstDataName") ?? "未获从主App取到名字", date: "Yesterday", content: userDefault?.string(forKey: "FirstDataContent") ?? "未从主App获取到内容", imageName: "Report_Success_icon_120x120_")
        return model
    }
  • FileManager共享数据

设置数据

    private var models: [WidgetModel] = {
        let imageNameArray = ["Report_Success_icon_120x120_", "Report_Success_icon_120x120_", "Report_Success_icon_120x120_", "Report_Success_icon_120x120_", "Report_Success_icon_120x120_"]
        let nameArray = ["Alex & Jessica(FileManager)", "Jessica(FileManager)", "Rocky(FileManager)", "Work Order(FileManager)", "Work Order(FileManager)"]
        let dateArray = ["5:30 pm", "Yesterday", "11/02/2018", "11/02/2018", "11/02/2018"]
        let conentArray = ["Rocky: Where are you Jessic?", "Rocky: Where are you Jessic?I will arrive station about 30n munites later", "Rocky: Where are you Jessic?I will arrive station about 30n munites later", "Rocky: Where are you Jessic?I will arrive station about 30n munites laterRocky: Where are you Jessic?I will arrive station about 30n munites later", "Rocky: Where are you Jessic?I will arrive station about 30n munites laterRocky: Where are you Jessic?I will arrive station about 30n munites later"]
        var array = [WidgetModel]()
        for i in 0..<nameArray.count {
            let model = WidgetModel(name: nameArray[i], date: dateArray[i], content: conentArray[i], imageName: imageNameArray[i])
            array.append(model)
        }
        return array
    }()
//主项目设置widget数据(通过FileManager)
    func setWidgetFileData() {
        let manager = FileManager.default
        var url = manager.containerURL(forSecurityApplicationGroupIdentifier: "group.tsc")
        //路径上多了个file:// 要去掉
        var urlString = url!.absoluteString.replacingOccurrences(of: "file://", with: "")
        urlString.append("Library/MainWidget")
        //1创建路径和文件
        if !manager.fileExists(atPath: urlString) {
            do {
                if let _:() = try? manager.createDirectory(atPath: urlString, withIntermediateDirectories: true, attributes: nil) {
                    print("路径创建成功")
                    urlString.append("/group.json")
                    do {
                        let result = manager.createFile(atPath: urlString, contents: nil, attributes: nil)
                        result ? print("文件创建成功") : print("文件创建失败")
                    }
                } else {
                    print("路径创建失败")
                }
            }
        }
        //2写入数据
        if let json = serialization() {
            url?.appendPathComponent("Library/MainWidget/group.json")
            print("Group路径----\(url!)")
            if let _:() = try? json.write(to: url!) {
                print("写入成功")
            } else {
                print("写入失败")
            }
        } else {
            print("序列化失败")
        }
    }

取数据

 //获取主项目设置widget数据(通过FileManager)
    func setWidgetFileData() {
        let manager = FileManager.default
        var url = manager.containerURL(forSecurityApplicationGroupIdentifier: "group.tsc")
        //路径上多了个file:// 要去掉
        var urlString = url!.absoluteString.replacingOccurrences(of: "file://", with: "")
        url?.appendPathComponent("Library/MainWidget/group.json")

        urlString.append("Library/MainWidget/group.json")
        //获取json数据
        if manager.fileExists(atPath: urlString) {
            do {
                let json = try! String(contentsOf: url!)
                let modelArray = try? JSONDecoder().decode([WidgetModel].self, from: json.data(using: .utf8)!)
                models = modelArray ?? []
            }
        } else {
            print("不存在")
        }
    }

4.Scheme配置

  • 宿主App配置Scheme
图片.png
  • Widget配置跳转
@objc func buttonDidClick(_ sender: UIButton) {
        var action = ""
        switch sender.tag {
        case 100:
            action = "scan"
        case 101:
            action = "pay"
        case 102:
            action = "rec"
        case 103:
            action = "zhuanzhang"
        case 104:
            action = "code"
        default:
            break
        }
        extensionContext?.open(URL(string: "MainWidgetScheme://action=\(action)")!, completionHandler: { (_) in
        })
    }
  • 宿主App处理跳转
 func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        let urlString = url.absoluteString
        let startIndex = urlString.startIndex
        let string = "MainWidgetScheme://action="

        if urlString.contains(string) {
            let newStartIndex = urlString.index(startIndex, offsetBy: string.count)
            let code = urlString[newStartIndex..<urlString.endIndex]
            switch code {
            case "scan":
                tabBar.alert(title: "扫一扫", cancelString: "取消", confirmString: "确认")
            case "pay":
                tabBar.alert(title: "付款", cancelString: "取消", confirmString: "确认")
            case "rec":
                tabBar.alert(title: "收款", cancelString: "取消", confirmString: "确认")
            case "zhuanzhang":
                tabBar.alert(title: "转账", cancelString: "取消", confirmString: "确认")
            case "code":
                tabBar.alert(title: "乘车码", cancelString: "取消", confirmString: "确认")
            default:
                break
            }
        }
        return true
    }

demo地址,欢迎star

Extension 证书配置

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

推荐阅读更多精彩内容