添加 Today Extension 工程
在原有的项目基础上,想要使用 Today Extension
,即 Widget
。我们需要创建一个新的 target
,点击File-->New-->Target-->Today Extention
,如下图所示:
创建成功后如图所示:
此时直接运行项目,如下图所示:
本人习惯使用纯代码布局,所以我删除了默认创建的 MainInterface.storyboard
,并在info.plist
中删除 NSExtensionMainStoryboard
字段,添加NSExtensionPrincipalClass
为 TodayViewController
,如下图所示:
当然,如果你习惯使用 xib
或者 storyboard
布局的话,可以直接在 MainInterface.storyboard
文件中进行 UI 实现。
实现下面的协议,配置 widget
的边距,否则你会发现 UI 的位置会与左侧边界有一定距离。
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets { return UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0); }
|
然后初始化一个 UILabel 显示未登录的提示,并给 UILabel 添加一个点击事件,使点击后能够打开 App 的登录页面。代码如下:
self.loginInLabel = [[UILabel alloc] init]; self.loginInLabel.textColor = [UIColor colorWithRed:(214.0/255.0) green:(33.0/255.0) blue:(25.0/255.0) alpha:1]; self.loginInLabel.backgroundColor = [UIColor clearColor]; self.loginInLabel.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width-16, 90); self.loginInLabel.textAlignment = NSTextAlignmentCenter; self.loginInLabel.text = @"未登录,点击登录账户"; self.loginInLabel.font = [UIFont systemFontOfSize:20]; self.loginInLabel.userInteractionEnabled = YES; UITapGestureRecognizer *openURLContainingAPP = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(openURLContainingAPP)]; [self.loginInLabel addGestureRecognizer:openURLContainingAPP]; [self.view addSubview:self.loginInLabel];
|
当然,widget 的 UI 实现是根据具体的业务来进行实现的,此处只是举例。
在NSExtensionContext
中,有widgetLargestAvailableDisplayMode
属性,来确认当前widget
是展开还是折叠状态。所以,我们可以先在viewWillAppear
中设置widget
的mode
。
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded; }
|
然后,就是展开和折叠的处理了。在NCWidgetProviding
协议中,有widgetActiveDisplayModeDidChange
方法
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize { if (activeDisplayMode == NCWidgetDisplayModeCompact) { self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 110); } else { self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300); } }
|
在这里需要注意的是,Widget 的折叠和展开时的高度不是可以随便设置的。在 widgetActiveDisplayModeDidChange
协议方法里可以打印出
withMaximumSize
其中在 5s 模拟器下:
NCWidgetDisplayModeCompact模式下:{304, 110} NCWidgetDisplayModeExpanded模式下:{304, 528}
|
6s模拟器下:
NCWidgetDisplayModeCompact模式下:{359, 110} NCWidgetDisplayModeExpanded模式下:{359, 616}
|
从上面的限制可知,widget 在折叠状态下最低为110,最高也根据机型有最大限制。注意处理好折叠和展开时 widget UI 和数据的变化,在此不做赘述。
刚才我们在 widget
中添加了一个充满折叠视图的 UILabel
,并想在用户点击时直接打开 App 的登录页面。
这里我们要设置一下 URL Schemes
,URL Schemes
主要作用是 App 之间相互调用打开,包括我们在实现第三方分享、第三方登录时都需要用到 URL Schemes
。
如图所示,在 info -> URL Types
里设置好 Schemes
后
再在 Today Extention
对应的 info.plist
里设置 Schemes
,如图所示:
设置好 Schemes 后,实现 UILabel 的点击事件:
- (void)openURLContainingAPP { [self.extensionContext openURL:[NSURL URLWithString:@"xiaozhumi://"] completionHandler:^(BOOL success) { NSLog(@"open url result:%d",success); }]; }
|
此时我们可以处理点击事件,让用户点击后直接打开 App 的登录页面并登录。
当用户登录成功后再次查看 widget 时,这时候如果继续显示登录的提示显然是不妥的,此时根据业务需求,我需要在用户登录账号成功后再次查看 widget 时,widget 展示用户的个人积分。
由于沙盒机制,拓展应用是不允许访问宿主应用的沙盒路径的,因此上述用法是不对的,需要搭配 app group完成实例化 UserDefaults。
首先需要去苹果开发者中心 Identifiers -> APP Groups
中创建一个 APP Group
,命名方式 group.com.companyName.xxx
,如下图
当创建好 App Group
后,分别在 主项目和 Today
的 Capabilities
设置选项中打开 App Group
选项,并选中在苹果开发者中心设置的 App Group
。
如下图所示:
此时,Todey 就可以和主项目进行数据共享了。
通过 NSUserDefaults 共享数据
当用户登录成功后,保存用户的积分到本地供 Widget 读取并展示。
NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.xiaozhumi.today"]; [shared setObject:jf forKey:@"UserJF"]; [shared synchronize];
|
widget 读取积分
NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.xiaozhumi.today"]; NSString *value = [shared valueForKey:@"UserJF"];
|
展示结果如图所示:
通过 NSFileManager 共享数据
- (BOOL)saveDataByNSFileManager { NSError *error = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.xxx.xxx"]; containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/test"]; NSString *value = @"test"; BOOL result = [value writeToURL:containerURL atomically:YES encoding:NSUTF8StringEncoding error:&error]; if (!result) { NSLog(@"%@",error); } else { NSLog(@"save value:%@ success.",value); } return result; } - (NSString *)readDataByNSFileManager { NSError *error = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.xxx.xxx"]; containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/test"]; NSString *value = [NSString stringWithContentsOfURL:containerURL encoding:NSUTF8StringEncoding error:&error]; return value; }
|
至此,基本已经实现了 widget 的基本功能。由于本文是在实际项目中截图记录的,所以暂不提供Demo查看。
微信扫一扫,向我赞赏
支付宝扫一扫,向我赞赏