iOS实用IOS开发iOS Developer

iOS widget(小部件)开发初探

2018-04-03  本文已影响21人  calary

1、前言

现在很多应用都有小部件功能,用起来非常方便,在用户安装包含Today小部件的应用后,他们可以将小部件添加到Today视图。当用户在“今日”视图中选择“编辑”时,通知中心会显示一个视图,允许用户添加,重新排序和删除小部件。
常见的有支付宝、日历和天气,那么我们也想为自己的应用增加widget功能该怎么办呢,那就继续往下看喽。
老版本(iOS9之前)的是直接下拉出现【今天】和【通知】两个选项,iOS10进行了更改,下拉是【通知】,右滑最左侧是小部件,所以下文提到的【今天】就是我们的小部件

iOS10之后 iOS9之前

2、准备工作

了解这个功能,当然官方文档App Extension Programming Guide是最值得读的了。
官方对小部件的一段介绍是:

App extensions in the Today view are called widgets. Widgets give users quick access to information that’s important right now. For example, users open the Today view to check current stock prices or weather conditions, see today’s schedule, or perform a quick task such as marking an item as done. Users tend to open the Today view frequently, and they expect the information they’re interested in to be instantly available.
“今日”视图中的附加应用信息称为小部件,小部件使用户能够快速访问现在非常重要的信息。例如,用户打开今日视图以检查当前股价或天气状况,查看今天的时间表,或者执行快速任务,例如将项目标记为已完成。用户倾向于经常打开“今日”视图,他们希望他们感兴趣的信息立即可用。

注意: 小部件是不支持键盘输入的

交互要求
确保今天的扩展点适合您想要提供的功能。最好的小部件为用户提供快速更新或启用非常简单的任务。如果您想要创建支持多步骤任务的应用扩展程序,或者帮助用户执行冗长的任务(如上传或下载内容),则“今日”扩展点不是正确的选择。

3、创建项目

<key>NSExtension</key>
    <dict>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.widget-extension</string>
        <key>NSExtensionPrincipalClass</key>
        <string>TodayViewController</string>
    </dict>
配置

创建布局什么的和平时开发一样,一些方法代码里也都有注释,下面主要说一下数据共享和打开app的方法

//TodayViewController.m
#import "TodayViewController.h"
#import <NotificationCenter/NotificationCenter.h>

@interface TodayViewController () <NCWidgetProviding>
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) UILabel *timeLabel;
@property (nonatomic, assign) int count;
@end

@implementation TodayViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self initView];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    
}

- (void)viewWillAppear:(BOOL)animated {
    // 设置折叠还是展开
    // 设置展开才会展示,设置折叠无效,左上角不会出现按钮, ❓
    self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
}

// 展开/折叠监听
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize{
    
    if (activeDisplayMode == NCWidgetDisplayModeCompact) { //折叠
        // 折叠后的大小是固定的,目前测试的更改无效,默认高度应该是110
        self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 100);
    }else { // 展开
        self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300);
    }
}

- (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);
}

- (void)initView {
    // 和主应用的数据共享,获取主应用里的数据
    NSUserDefaults *sharedData = [[NSUserDefaults alloc] initWithSuiteName:@"group.rs.testGroup"];
    NSString *name = [sharedData objectForKey:@"name"];
    // 官方建议使用自动布局创建控件,这里是写的固定的
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width/2.0, 50)];
    label.text = [NSString stringWithFormat:@"姓名:%@",name];
    label.textAlignment = NSTextAlignmentCenter;
    label.textColor = [UIColor blueColor];
    [self.view addSubview:label];
    
    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.frame.size.width/2.0, 0, self.view.frame.size.width/2.0, 50)];
    [btn addTarget:self action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];
    [btn setTitle:[self readByFileManager] forState:UIControlStateNormal];
    [btn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
    [self.view addSubview:btn];
    
    // 添加计时器
    _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
    
    _timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 50, self.view.frame.size.width, 50)];
    _timeLabel.textAlignment = NSTextAlignmentCenter;
    _timeLabel.textColor = [UIColor redColor];
    [self.view addSubview:_timeLabel];
    _count = 100;
}

// NSFileManager 读取数据
- (NSString *)readByFileManager {
    NSError *error = nil;
    NSURL *containUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.rs.testGroup"];
    containUrl = [containUrl URLByAppendingPathComponent:@"group.data"];
    NSString *text = [NSString stringWithContentsOfURL:containUrl encoding:NSUTF8StringEncoding error:&error];
    return text;
}

- (void)timerAction {
    if (_count > 0){
        _count -= 1;
    }else {
        _count = 100;
    }
    _timeLabel.text = [NSString stringWithFormat:@"倒计时:%ds",_count];
}

// 点击按钮打开主app
- (void)btnAction {
    [self.extensionContext openURL:[NSURL URLWithString:@"TodayWidget://"] completionHandler:^(BOOL success) {
        
    }];
}

@end

运行后的效果


运行效果图

4、 调起app

因为 extension 和 主app 是两个完全独.立的进程,所以它们之间不不能直接通信(不能像应用内部点击按钮,跳转到指定页面)。为了了实现 Widget 调起 app,这里通过 openURL 的方式来启动 主app。

// 点击按钮打开主app
- (void)btnAction {
    [self.extensionContext openURL:[NSURL URLWithString:@"TodayWidget://"] completionHandler:^(BOOL success) {
        
    }];
}
// AppDelegate.m
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options{
    if ([url.scheme isEqualToString:@"TodayWidget"]){
        //这里监听到是通过widget打开,可以进行发送通知等操作
        return YES;
    }
    return NO;
}

5、数据共享

扩展程序一般都不是脱离宿主程序单独运行的,难免需要和宿主程序进行数据交互。由于拓展与宿主应用是两个完全独立的App,并且iOS应用基于沙盒的形式限制,所以一般的共享数据方法都是实现不了数据共享,这里就需要使用App Groups(App Groups这是iOS8新开放的功能,在OS X上早就可用了。它主要用于同一Group下的App共享同一份读写空间,以实现数据共享)。

通过 App Groups 提供的同一 group 内 app 共同读写区域,可以用 NSUserDefaults 和NSFileManager 两种方式实现 extension 和 主app 之间的数据共享。

widget
 NSUserDefaults *sharedData = [[NSUserDefaults alloc] initWithSuiteName:@"group.rs.testGroup"];
    [sharedData setValue:@"Mr Right" forKey:@"name"];
    [sharedData synchronize];

在widget中读取数据

NSUserDefaults *sharedData = [[NSUserDefaults alloc] initWithSuiteName:@"group.rs.testGroup"];
    NSString *name = [sharedData objectForKey:@"name"];

注意:保存读取数据的时候必须指明group id;

// NSFileManager 存储数据
- (void)saveFile {
    NSError *error = nil;
    NSURL *containUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.rs.testGroup"];
   containUrl = [containUrl URLByAppendingPathComponent:@"group.data"];
    NSString *text = @"打开app";
    BOOL result = [text writeToURL:containUrl atomically:YES encoding:NSUTF8StringEncoding error:&error];
    if (result){
        NSLog(@"save success");
    }else {
        NSLog(@"error:%@", error);
    }
}

在widget中读取数据

// NSFileManager 读取数据
- (NSString *)readByFileManager {
    NSError *error = nil;
    NSURL *containUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.rs.testGroup"];
    containUrl = [containUrl URLByAppendingPathComponent:@"group.data"];
    NSString *text = [NSString stringWithContentsOfURL:containUrl encoding:NSUTF8StringEncoding error:&error];
    return text;
}

6、总结

至此,小部件的简单开发算是完成了,后续可能还有发布的证书配置,网络请求等情况,我还没有尝试,等实际应用了再进行补充,希望能对你有所帮助,笔者也是第一次尝试,如果有哪里不对的,请指正。
最后附上Demo地址

7、参考链接

本文简书地址---- >>>>

上一篇 下一篇

猜你喜欢

热点阅读