扩展(Extension)是iOS 8中新引入的特性。iOS 8系统有6个支持扩展的系统区域,分别是
- 今日插件(Today widget)
- 分享(Share)
- 操作(Action)
- 图片编辑(Photo Editing)
- 文档管理(Document Provider)
- 自定义键盘(Custom keyboard)
支持扩展的系统区域也被称为扩展点。对于赛事比分,股票、天气、快递,位置信息等需要实时获取的信息,可以在通知中心的Today视图中创建一个Today扩展实现。Today扩展又称为Widget,本文主要是介绍Today Extension的用法。
苹果文档:https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/NotificationCenter.html
如图就是Widget效果(UC浏览器的)
Widget的创建直接在targets里点击下边添加就可以了
Xcode6新建的是自带SB的,直接在模拟器运行,注意图中箭头处
运行效果,熟悉的Hello World
博主习惯了没有用SB,修改Info.plist文件,删除SB即可
在TodayViewController的- (void)viewDidLoad方法种添加
self.preferredContentSize = CGSizeMake(0, 200);
self.view.backgroundColor = [UIColor redColor];
运行是如图效果
值得注意:
1、尽量不要使用背景,默认的毛玻璃效果很好,也比较统一;
2、尽量保持默认的缩进,即左边会空几个像素。
如果想改变默认缩进,有一个方法:
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets{
return UIEdgeInsetsZero;
}
在TodayViewController里面实现以下,缩进就没有了,下面说几点关键的
1.共享数据
插件和主应用是独立的两个进程,以前是无法共享数据的,现在可以通过AppGroup来共享数据,同属于一个group的App可以共享数据
target里选择主应用打开App Group,如果之前没有,则新加group,id格式可如图,创建之后,在你的Widget里同样地方勾选上即可,程序间共享数据使用的是
//存
NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.xxxx.app"];
[shared setObject:@"value" forKey:@"key"];
[shared synchronize];
获取数据
//取
NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.xxxx.app"];
[shared objectForKey:@"key"];
Name的名字跟上边添加的App Group需要一致
2.跳转到主应用
在widget里按钮添加点击事件,跳转到主app不同页面,widget里是没有UIApplication类的,所以相关方法都不能用,所以self.extensionContext代指当前widget
- (void)skip:(UIButton *)button
{
if (button.tag == 1) {
[self.extensionContext openURL:[NSURL URLWithString:@"iOSWidgetApp://action=GotoHomePage"] completionHandler:^(BOOL success) {
}];
}
else if(button.tag == 2) {
[self.extensionContext openURL:[NSURL URLWithString:@"iOSWidgetApp://action=GotoOtherPage"] completionHandler:^(BOOL success) {
}];
}
}
主app里添加协议
然后主app里解析
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
NSString* prefix = @"iOSWidgetApp://action=";
if ([[url absoluteString] rangeOfString:prefix].location != NSNotFound) {
NSString* action = [[url absoluteString] substringFromIndex:prefix.length];
if ([action isEqualToString:@"GotoHomePage"]) {
}
else if([action isEqualToString:@"GotoOtherPage"]) {
}
}
return YES;
}
3.国际化问题
建议参考下这微博主http://www.jianshu.com/p/0efd62ee033a 2.6处所提到的,这点需要注意
4.网络请求
常用的AF和MK框架中都有用到UIApplication类,无法编译通过,可以自行修改相关地方,也可以直接使用AFHTTPSessionManager类,
相关类这些,都没有用到UIApplication(AFKit.h和AFHTTPShareRequest.h是我自己写的两个类),一个是头文件合集,一个单例类
AFKit.h
#ifndef AFKit_h
#define AFKit_h
#endif /* AFKit_h */
#ifdef __OBJC__
#import "AFHTTPSessionManager.h"
#import "AFNetworkReachabilityManager.h"
#import "AFSecurityPolicy.h"
#import "AFURLResponseSerialization.h"
#import "AFHTTPSessionManager.h"
#import "AFURLSessionManager.h"
#endif
AFHTTPShareRequest.h
#import "AFHTTPSessionManager.h"
@interface AFHTTPShareRequest : AFHTTPSessionManager
+ (instancetype)sharedClient;
@end
AFHTTPShareRequest.m
#import "AFHTTPShareRequest.h"
@implementation AFHTTPShareRequest
+ (instancetype)sharedClient
{
static AFHTTPShareRequest *client = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
client = [[AFHTTPShareRequest alloc] init];
});
return client;
}
@end
(报错的话看下是不是少导入哪个类库了?)
最后再说几点细节的地方
- (void)viewDidLoad
方法并不是每次下拉都会调用(甚至很少调用),但是
- (void)viewWillAppear:(BOOL)animated
方法一定是会调用的,所以数据实时更新的代码最好不要写在 - (void)viewDidLoad方法里;第二点,断点调试时,需要用Today 来运行widget才有用,并且用真机调试有时打断点不一定有用,用模拟器一定有用,我感觉这是个bug,用模拟器调试时每次改动的代码需要把模拟器退出再重启(注意这点)才会生效,真机不需要。
暂时想到的就这么写东西。第一次开发Widget坑其实挺多的,建议下手前先多看几篇相关文章,会顺利许多,如有疑问,欢迎探讨
ps:评论有人提到划出屏幕后再划回来不能交互,我测试了下,线上版本,iphone6,6p(9.3.2)、iphone6(8.3)没有这个问题,但是开发版本确实有这个问题,不知道是不是开发版本的bug,如有知道此问题的,欢迎留言