前言
随着iOS8的发布 各种iPhone的新玩法出现了 其中最引人关注的就是 today extension
(也叫做widget) 这个在android上存在了多年的小玩意 也是iPhone一直被人诟病的东西 终于能用上了
网上有很多相关的文章教你如何编写一个简单的widget 但是却没有一篇适合我们这种纯代码的拥趸(也有很多人说应该放弃纯代码 改用Storyboard了) 那么接下来我就说说 如何用纯代码的方式来构建一个widget
准备
首先 你得有个正常的app项目(这是必须的 extension必须依附于某个app中 当然 不这样 你也无法单独安装某个widget)
打开项目工程 选择新建一个target 在 Application Extension
中选择 today exntension
然后填入名字 确认即可
这时你的项目里多个一个target 同时也多了下面
接下来 删掉这个讨厌的 MainInterface.storyboard
然后修改plist文件中的 NSExtension
字段
- 删掉
NSExtensionMainStoryboard
字段 - 添加
NSExtensionPrincipalClass
字段 并设为TodayViewController
(你也可以指定其他的ViewController)
修改完以后 Widget就可以开始编译运行了
运行
关于调试Widget 我推荐使用模拟器而不用真机 因为在研究过程中我发现真机调试的效果非常差 经常提示无法连接到手机(也有可能是5S的性能够不?) 导致无法正常的debug 或者无法reinstall 而模拟器则好点(至少能顺利的打印出log) 但是每次修改好代码以后 最好都先退出模拟器 再重新编译运行 如果你退出重新运行时提示下面这个错误 不用怕 多运行两次就ok了
按 Command+R
编译运行 会弹出提示框让你选择宿主app 就选择默认的today就好了

如无意外 模拟器启动时会自动打开today 并显示你的widget

是不是发现什么都没有? 如果用Storyboard构建的Widget 会默认有个 Hello
可我们弄出来的Widget 却啥都没有 连高度都没有
那么问题来了…
修改
首先 我们设置一下widget的高度 并添加一个contentView和一个button(注意 这里我使用的是 Masonry
来完成autolayout 相关信息可见我的上一篇文章: Masonry介绍与使用实践(快速上手Autolayout) )
@interface TodayViewController () <NCWidgetProviding>
@property (strong, nonatomic) UIView *contentView;
@property (nonatomic, strong) UIButton *btnTest;
@end
- (void)viewDidLoad {
[super viewDidLoad];
//使用preferredContentSize设置大小 且只用设置高度就好了
self.preferredContentSize = CGSizeMake(0, 200);
__weak __typeof(&*self)ws = self;
self.contentView = [UIView new];
self.contentView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.contentView];
[self.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(ws.view);
}];
self.btnTest = [UIButton buttonWithType:UIButtonTypeCustom];
[self.btnTest setTitle:[[NSDate date] description] forState:UIControlStateNormal];
self.btnTest.backgroundColor = [UIColor redColor];
[self.btnTest addTarget:self action:@selector(actionTest) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:self.btnTest];
[self.btnTest mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(ws.contentView);
make.size.mas_equalTo(CGSizeMake(300, 40));
}];
}
运行一下 看看效果

控件是出来了 可是没有如我们的意 旁边还空了一块 原来widget默认会有一个inset 那么如何取消这个inset呢? 重载如下方法即可
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets
{
return UIEdgeInsetsZero;
}
修改完以后您再看
一个Widget的模子就这样构建完成了
进阶
接下来 我们给button加个点击事件 用来改变widget的大小
- (void) actionTest
{
self.preferredContentSize = CGSizeMake(0, self.contentView.frame.size.height>250?200:300);
}
测试发现效果”还可以” 为什么仅仅是”还可以”呢 可以看到当size变化时 其他区域其实是有个动态变化的效果 但是我们的widget的变化是立即的 所以看上去不那么流畅(storyboard里就不存在这个问题了 因为使用了autolayout)
那么我们可不可以也使用autolayout 而不设置这个 preferredContentSize
呢? 答案是可以的
首先修改viewDidLoad的代码
- (void)viewDidLoad {
[super viewDidLoad];
//去掉这一步
//self.preferredContentSize = CGSizeMake(0, 200);
__weak __typeof(&*self)ws = self;
self.contentView = [UIView new];
self.contentView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.contentView];
[self.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(ws.view);
//设置内部view的高度(一定要设置高优先级 不然会有冲突)
make.height.mas_equalTo(@200).priorityHigh();
}];
...
...
...
}
然后修改按钮的动作
- (void) actionTest
{
//去掉这一步
//self.preferredContentSize = CGSizeMake(0, self.contentView.frame.size.height>250?200:300);
//更新autolayout
[self.contentView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(@(self.contentView.frame.size.height>250?200:300)).priorityHigh();
}];
}
试着运行一下 你会发现世界变得很美丽了
至此 发挥你的想象吧 you can do whatever you want!
小结
所有的准备工作都已经做完了 编写一个Widget也变得很简单 你可以像写任何一个ViewController一样来写Widget 而纯代码的方式我相信会让很多人更得心应手