如何用纯代码构建一个Widget(today extension)
前言
随着iOS8的发布 各种iPhone的新玩法出现了 其中最引人关注的就是today extension
(也叫做widget) 这个在android上存在了多年的小玩意 也是iPhone一直被人诟病的东西 终于能用上了
网上有很多相关的文章教你如何编写一个简单的widget 但是却没有一篇适合我们这种纯代码的拥趸(也有很多人说应该放弃纯代码 改用Storyboard了) 那么接下来我就说说 如何用纯代码的方式来构建一个widget
准备
首先 你得有个正常的app项目(这是必须的 extension必须依附于某个app中 当然 不这样 你也无法单独安装某个widget)
打开项目工程 选择新建一个target 在Application Extension
中选择today exntension
然后填入名字 确认即可
新建target
填入信息
这时你的项目里多个一个target 同时也多了下面
修改plist
接下来 删掉这个讨厌的MainInterface.storyboard
然后修改plist文件中的NSExtension
字段
- 删掉
NSExtensionMainStoryboard
字段 - 添加
NSExtensionPrincipalClass
字段 并设为TodayViewController
(你也可以指定其他的ViewController)
修改plist
修改完以后 Widget就可以开始编译运行了
运行
关于调试Widget 我推荐使用模拟器而不用真机 因为在研究过程中我发现真机调试的效果非常差 经常提示无法连接到手机(也有可能是5S的性能够不?) 导致无法正常的debug 或者无法reinstall 而模拟器则好点(至少能顺利的打印出log) 但是每次修改好代码以后 最好都先退出模拟器 再重新编译运行 如果你退出重新运行时提示下面这个错误 不用怕 多运行两次就ok了
模拟器报错
按Command+R
编译运行 会弹出提示框让你选择宿主app 就选择默认的today就好了
运行
如无意外 模拟器启动时会自动打开today 并显示你的widget
效果
是不是发现什么都没有? 如果用Storyboard构建的Widget 会默认有个Hello
可我们弄出来的Widget 却啥都没有 连高度都没有
那么问题来了…
修改
首先 我们设置一下widget的高度 并添加一个contentView和一个button(注意 这里我使用的是Masonry
来完成autolayout 相关信息可见我的上一篇文章:Masonry介绍与使用实践(快速上手Autolayout)
12345678910111213141516171819202122232425262728293031323334353637 |
@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呢? 重载如下方法即可
1234 |
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets{ return UIEdgeInsetsZero;} |
修改完以后您再看
效果
一个Widget的模子就这样构建完成了
进阶
接下来 我们给button加个点击事件 用来改变widget的大小
1234 |
- (void) actionTest{ self.preferredContentSize = CGSizeMake(0, self.contentView.frame.size.height>250?200:300);} |
效果
测试发现效果”还可以” 为什么仅仅是”还可以”呢 可以看到当size变化时 其他区域其实是有个动态变化的效果 但是我们的widget的变化是立即的 所以看上去不那么流畅(storyboard里就不存在这个问题了 因为使用了autolayout)
那么我们可不可以也使用autolayout 而不设置这个preferredContentSize
呢? 答案是可以的
首先修改viewDidLoad的代码
1234567891011121314151617181920212223 |
- (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(); }]; ... ... ... } |
然后修改按钮的动作
12345678910 |
- (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 而纯代码的方式我相信会让很多人更得心应手