iOS随笔

Widget开发指南

2019-01-31  本文已影响1人  cc412

目前负责的App新增了Widget功能,之后在组内分享中分享了下Widget的开发经验。基于之前的PPT提炼出了这篇文章。本篇文章只讲基于Widget关于iOS10+ 之后的知识点。

Widget是iOS8以后Apple推出的一项功能,并且在iOS10后进行了大幅的优化。

在主屏幕和锁定屏幕上向右滑动来访问Widget,也可以在对应的App图标上面使用3D Touch按压访问相应的Widget。


Widget设计规范和要求

Widget是一个单独的进程,和主App独立,但是支持数据共享。在设计和开发Widget时候要注意以下几点设计规范:

Widget用来执行非常简单的任务,尽可能提供点击一次就能完成的任务,Widget不支持窗口滚动,不支持键盘输入(其实是可以做到键盘输入的 具体办法见后面)
详见《App Extension Programming Guide》


建立Widget Target

选择主工程,在Project设置界面下方点击加号,新建Today Extension



系统会自动生成TodayViewController和storyBoard。不要忘记在Target设置里面设置基本信息,版本号和主App保持一致,否则上传iTunes Connect会有警告邮件

也要注意选择Deployment Target。Xcode10默认是iOS12

和Widget共享代码

Xcode10 后,如果在Build Phases中运行Script。执行pod可能报错。解决办法见
《#iOS知识小集# Xcode10 pod install 报错》

Widget代码实现

NCWidgetProviding协议

Widget工程建立后会自动生成TodayViewController。
会遵循NCWidgetProviding协议
iOS10以后这个协议只有两个方法

- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult result))completionHandler;
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize NS_AVAILABLE_IOS(10_0);

其中widgetPerformUpdateWithCompletionHandler 默认返回NCUpdateResultNewData

- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler {
    // Perform any setup necessary in order to update the view.
    
    // If an error is encountered, use NCUpdateResultFailed
    // If there's no update required, use NCUpdateResultNoData
    // If there's an update, use NCUpdateResultNewData

    completionHandler(NCUpdateResultNewData);
}

这个可以忽略掉,直接返回NCUpdateResultNewData就好了
iOS10以后支持折叠和展开功能,折叠状态下默认高度为110且不可更改。展开高度不超过一个屏幕的高度。(官方文档说最低高度为2.5个默认行高 44*3.5=110)

在ViewDidLoaded方法中设置是否开启折叠功能

//NCWidgetDisplayModeCompact 收起模式
//NCWidgetDisplayModeExpanded  展开模式
     
self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize {
    
    if (activeDisplayMode == NCWidgetDisplayModeCompact) {
        self.preferredContentSize = CGSizeMake(maxSize.width, 110);
    } else {
        self.preferredContentSize = CGSizeMake(maxSize.width, 200);
    }
}

使用纯代码

示例工程会默认使用StoryBoard,如果想使用纯代码。进行以下步骤

  1. 删除MainInterface.storyboard文件和NSExtensionMainStoryboard键值对


2.添加NSExtensionPrincipalClass为key ,value为TodayViewController


图片管理

Widget可以使用Asset Catalog管理图片,命名为Assets,和主工程使用方式一致
[图片上传失败...(image-63b41f-1548926798999)]

代码调试

在Widget工程更新代码后,可以运行主工程,然后添加Widget。就可以看到最新的效果展示。
如果想断点调试,要选择Widget Target


和主工程共享数据

Widget和主工程是完全独立的两个工程,两个独立的进程。所以数据共享是通过App Groups进行的。

App Groups需要去开发者中心去创建。ID必须以group开头。后面一般跟公司名称。


建立完成后回到主工程,打开App Groups开关,就能刷新出刚刚创建的Groups,打钩远中

然后把Widget Target 也打开App Groups,选中同一个Groups

App Groups可以通过NSUserDefaults和NSFileManager共享数据

//主工程中存
NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.YouDao.xxxx"];
[shared setObject:_targetLanguage.abb forKey:@"UD_TargetLanguage_Widget_key"];
[shared synchronize];


//Widget 中取
[[NSUserDefaults alloc] initWithSuiteName:@"group.YouDao.xxxx"] objectForKey:@"UD_TargetLanguage_Widget_key"];
//存
NSString *groupID = @"group.YouDao.xxxx";
NSError *err = nil;
NSURL *fileUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupID];
fileUrl = [fileUrl URLByAppendingPathComponent:@"Library/Caches/test"];
NSString *value = @"test";
BOOL result = [value writeToURL:fileUrl atomically:YES encoding:NSUTF8StringEncoding error:&err];
if(result){
    NSLog(@"写入成功");
}
//取
NSString *groupID = @"group.YouDao.xxxx";
NSError *err = nil;
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupID];
containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/test"];
NSString *value = [NSString stringWithContentsOfURL:containerURL encoding:NSUTF8StringEncoding error:&err];

App Gropu是跨App的,只要在同一个开发中账号。不同的App使用同一个Gropu ID都是可以共享数据的。在Shared目录下还有AppGroup目录。里面有各个Group ID的文件夹。其中通过NSUserDefault共享的数据在Library/Prefrences下。是一个plist文件。

Widget吊起主工程

Widget吊起主App通过URLSchemes

  1. 为主App设置URLSchemes


2.Widget添加交互

[self.extensionContext openURL:[NSURL URLWithString:@"YDUDictionary://action=CameraTranslate"] completionHandler:^(BOOL success) {
                NSLog(@"open url result:%d",success);
            }];

3.主App中处理Scheme.在AppDelegate中实现application:openURL:options:

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
      NSString *urlStr = [url.absoluteString stringByRemovingPercentEncoding];
        if ([urlStr hasPrefix:@"YDUDictionary://action="]) {
            NSString *parameter = [urlStr stringByReplacingOccurrencesOfString:@"YDUDictionary://action=" withString:@""];
                if ([parameter isEqualToString:@"CameraTranslate"]) {
                //Do Somthinhg
                }

        }
}

主App中控制Widget是否显示

在Widget编辑页面可以进行Widget排序很删减。



当添加Widget以后,主工程还可以控制Widget是否显示。

//为什么要引入NotificationCenter呢?可以思考下
#import <NotificationCenter/NotificationCenter.h>
//youdao.com.WidgetTest.Widget是Widget的Bundle ID
[[NCWidgetController widgetController] setHasContent:YES forWidgetWithBundleIdentifier:@"youdao.com.WidgetTest.Widget"];

刷新机制

Widget有自己进程,有特殊的生命周期和内存限制。通过测试得出

Widget离开屏幕2s以上,就会被销毁回收掉。每次离开前系统会做快照处理。下次进来先加载快照。
离开超过2s以上,下次进入就会调用ViewDidLoad,然后是viewWillAppear
离开不超过2s 下次进入会调用viewWillAppear

所以为了交互体验,最好是记录用户上次的使用状态,下次加载时候进行还原操作。

当内存不足时候,系统会优先kill掉Widget。所以要注意内存问题,不要进行需要大量内存的操作。
网络请求如果需要频繁刷新。可以在viewWillAppear方法中启用一个Timer,在Timer中请求接口数据。在viewWillDisAppear中取消定时器。

如何在Widget中使用键盘

Apple官方文档说Widget是不支持键盘输入的。如果在TodayViewController中新建一个输入框。点击是没有反应的。但是我们可以用另外一种办法绕过去。效果如下图。

做法就是做一个假的输入框,让用户点击。点击后present一个ViewController,在这个Controller新建UITextView或者UITextField就可以获取焦点,出现键盘啦

上一篇下一篇

猜你喜欢

热点阅读