依旧是因为繁琐,所以这个文章也一直没有写。明天情人节,今天就赶紧写一写吧,因为明天肯定是各种虐狗分享。汪~

小组件widget就是例如懒猪清单的生日提醒这样,左滑直接在系统显示,比如每日天气等,方便快捷,可以当成一个展示的工具。

Simulator Screen Shot - iPhone 8 Plus - 2018-06-08 at 23.14.46 2.png

一、widget的创建

widget是一个依附于app的插件,所以如果你想创建对应项目的widget的话,在对应的项目中,新建一个target

屏幕快照 2018-08-16 下午3.00.11.png

target类型选择Today Extension,然后填写对应的Product Name创建这个target

屏幕快照 2018-08-16 下午3.00.01.png

1.1、证书和配置文件的配置

如果你的这个项目的证书签名是自动管理的话,那可以跳过这一段,如果是手动管理的话,需要配置一下widget和项目的配置文件

屏幕快照 2018-08-16 下午3.01.44.png

选择这个widget对应的target,如果你是手动管理证书的话,需要去开发后台创建widget对应的bundle id和对应的配置文件,和创建app的的bundle id和配置文件一样,去后台对应的创建即可。因为基本上widget都需要读取app的信息,所以需要将app的bundle id和widget的bundle id绑定同一个group里面去读取沙盒信息,所以先去创建一个app group

屏幕快照 2018-08-16 下午3.18.59.png

然后将app的bundle id和widget的bundle id都绑定一下这个app group,这样是为了两个数据交互,绑定之后去创建下载对应的Provisioning Profiles就行了,app和widget都需要创建对应的开发环境的配置文件和App Store送审的配置文件,这样才能去一起送审。

屏幕快照 2018-08-16 下午3.19.12.png

1.2、修改widget的样式为代码布局

widget虽然和项目是一个整体的工程,但是文件和图片都是不互通的,如果有一张图片在app路径下面,你在widget使用是读取不到的。所以如果有什么布局想复用,需要在widget的文件下也复制一份。

屏幕快照 2018-08-16 下午3.25.55.png

因为我喜欢使用代码布局,所以在widget的info.plist文件修改成代码布局的设置。如果你是用storyboard布局的话可以跳过

屏幕快照 2018-08-16 下午3.26.58.png

首先将原有NSExtensionMainStoryboard字段删除,添加字段NSExtensionPrincipalClass,value是你所写的controller的名称,一般默认的都是TodayViewController。

如果使用的swift,会出现报错crash

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSDictionaryM setObject:forKey:]: object cannot be nil (key: A8ED0DD2-EC6A-4A70-8B99-20BE30875FDA)'

这个是因为如果使用的swift,NSExtensionPrincipalClass字段需要设置项目名称,比如在OC中填写的是TodayViewController,那么swift可以写成$(PRODUCT_NAME).TodayViewController。如果需要修改显示的名字,在info.plist直接把默认的display name里面的$(PRODUCT_NAME)修改为名字即可

1.3、开始布局widget

在TodayViewController中,开始布局widget的样式。通用组件都可以使用,但是tableview等滚动视图是无法滚动的,所以可以使用tableview布局,但是要注意高度,是不能滚动的。

iOS10之后,Widget支持展开及折叠两种展现方式,通过设置widgetLargestAvailableDisplayMode属性可以让Widget程序实现展开布局,同时在左滑到widget显示的时候,会调用这个viewWillAppear,这时候可以去刷新数据获取最新的数据。

- (void)viewDidLoad {
    [super viewDidLoad];
    if (@available(iOS 10.0, *))
    {
        self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
    }
    self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 110);
    
    [self createUI];
    [self loadData];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self loadData];
}

宽高切换时的代理布局,因为我用的是tableview布局,所以在宽高变化时,将tableview的宽高也进行变换即可。

- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize
{
    NSLog(@"maxWidth %f maxHeight %f",maxSize.width,maxSize.height);

    if (@available(iOS 10.0, *)) {
        if (activeDisplayMode == NCWidgetDisplayModeCompact)
        {
            [self.m_tableView setFrame:CGRectMake(10, 0, maxSize.width-20, 110)];
            self.preferredContentSize = CGSizeMake(maxSize.width, 110);
        }
        else
        {
            [self.m_tableView setFrame:CGRectMake(10, 0, maxSize.width-20, 310)];
            self.preferredContentSize = CGSizeMake(maxSize.width, 310);
        }
    } else {
        [self.m_tableView setFrame:CGRectMake(10, 0, maxSize.width-20, 110)];
        self.preferredContentSize = CGSizeMake(maxSize.width, 110);
    }
}

// iOS10之前,视图原点默认存在一个间距,可以实现以下方法来调整视图间距,这句是为了兼容性
// 注:该方法在iOS10之后被遗弃,iOS10默认不存在间距。
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets
{
    return UIEdgeInsetsMake(0, 10, 0, 10);
}

二、widget和app之间的数据处理

2.1、widget给app传值

widget中可以有按钮事件,也可以使用tableview的点击代理等,点击时相当于其他app使用scheme打开这个app,比如tableview的点击。

#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    NSURL *url = [NSURL URLWithString:@"lanzhuList://"];
    //打开app
    [self.extensionContext openURL:url completionHandler:^(BOOL success) {
        
        NSLog(@"isSuccessed %d",success);
    }];
}

当然scheme里面也可以传值,然后在app的appdelegaete的URL回调中处理

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
    if ([[url absoluteString] hasPrefix:@"lanzhuList"])
    {
      //处理widget的传值
    }
    return  YES;
}

2.2、app和widget的数据共享

在上面的配置文件中,已经给app和widget配置过app group,所以在app和widget工程中,都将App Groups打开

屏幕快照 2018-08-16 下午4.03.56.png

打开之后,有两种数据存储方式,NSUserDefault适合小量存储,而一般如果想多种数据的话,还是推荐第二种使用文件数据共享。两个都可以写入和读取,但是正常情况下,基本都是app负责写入,widget负责读取显示

2.2.1、使用NSUserDefault

由于沙盒机制,拓展应用是不允许访问宿主应用的沙盒路径的,因此上述用法是不对的,需要搭配app group完成实例化UserDefaults。这里不能使用

[NSUserDefaults standardUserDefaults];

方法来初始化NSUserDefault对象,而是需要设置的app groups去读取

写入数据

NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.huaimayi.lanzhuList"];
[userDefaults setObject:self.textField.text forKey:@"widget"];
[userDefaults synchronize];

读取数据

NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.huaimayi.lanzhuList"];
self.contentStr = [userDefaults objectForKey:@"widget"];    

2.2.2、通过文件NSFileManager共享数据

写入数据

- (void)updateWidgetData {
    
    //获取到共享数据的文件地址
    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.huaimayi.lanzhuList"];
    NSURL *birthdayContainerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/birthday.json"];
    
    //将需要存储的数据写入到该文件中
    NSString *jsonString = @"需要写入的数据或者json字符等";
    
    //写入数据
    NSError *err = nil;
    BOOL result = [jsonString writeToURL:birthdayContainerURL atomically:YES encoding:NSUTF8StringEncoding error:&err];
    if (!result)
    {
        NSLog(@"%@",err);
    }
    else
    {
//        NSLog(@"save value:%@ success.",jsonString);
    }
   
}

读取数据

-(NSString *)readDataByNSFileManager
{
    NSError *err = nil;
    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.huaimayi.lanzhuList"];
    containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/birthday.json"];
    NSString *value = [NSString stringWithContentsOfURL:containerURL encoding: NSUTF8StringEncoding error:&err];
    return value;
}

三、测试,审核发布

3.1、测试

如果你想测试,需要用模拟器去测试,因为如果你这个之前没有发不过,就算能安装成功,手机上也没有显示的,但是模拟器上可以

3.2、打包送审

屏幕快照 2018-08-16 下午4.17.30.png

选择主项目,然后正常archive打包,提交即可,如果是导出使用application launcher提交的话,会自动让你选择app的配置文件和widget的配置文件,你选择对应的配置文件即可,两个类似于独立的app,是不同的bundle id和配置文件。

屏幕快照 2018-08-16 下午4.21.14.png

3.3、注意事项

最好将widget的版本号和构建版本号设置的和app一致,不然提交到app store的时候会出现警告,虽然不影响提交审核和正常使用,但是最好还是一致比较好。


☟☟可点击下方广告支持一下☟☟

最后修改:2019 年 12 月 01 日
请我喝杯可乐,请随意打赏: ☞已打赏列表