3D Touch功能主要分为以下三个模块:
- 1、Home Screen Quick Actions
通过主屏幕的应用Icon,我们可以用3D Touch呼出一个菜单,进行快速定位应用功能模块相关功能的开发。
- 2、peek and pop
这个功能是一套全新的用户交互机制,在使用3D Touch时,ViewController中会有如下三个交互阶段:
3DTouch有两种体现方式,第一种是通过Peek 轻按App icon弹出选项菜单,第二种是在App内的界面上通过Peek 轻按,弹出的预览界面
3、Force Properties
iOS9为我们提供了一个新的交互参数:力度。我们可以检测某一交互的力度值,来做相应的交互处理。例如,我们可以通过力度来控制快进的快慢,音量增加的快慢等。
- 轻按app icon弹出选项菜单 代码实现
在app启动时配置application.shortcutItems,注意版本适配
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[self setupShortcutItems:application];
return YES;
}
- (void)setupShortcutItems:(UIApplication *)application {
// 通过Peek 用力按app icon 弹出
#ifdef __IPHONE_9_0
// 判断该版本xcode的API是否可用,编译时是否需要生成这段代码
// 创建标签中的icon
// 自定义图片的icon
UIApplicationShortcutIcon *icon1 = [UIApplicationShortcutIcon iconWithTemplateImageName:@"fts_search_wechat_icon_46x46_"];
UIApplicationShortcutIcon *icon2 = [UIApplicationShortcutIcon iconWithTemplateImageName:@"fts_websearch_icon_46x46_"];
// 使用系统风格的icon
UIApplicationShortcutIcon *icon3 = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeShare];
UIMutableApplicationShortcutItem *item1 = [[UIMutableApplicationShortcutItem alloc] initWithType:shortcutItemType1Key localizedTitle:@"添加到通讯录" localizedSubtitle:@"点击即可添加到通讯录" icon:icon1 userInfo:nil];
UIMutableApplicationShortcutItem *item2 = [[UIMutableApplicationShortcutItem alloc] initWithType:shortcutItemType2Key localizedTitle:@"搜索" localizedSubtitle:@"在微信中搜索你想要查找的" icon:icon2 userInfo:nil];
UIMutableApplicationShortcutItem *item3 = [[UIMutableApplicationShortcutItem alloc] initWithType:shortcutItemType3Key localizedTitle:@"分享" localizedSubtitle:@"分享此app" icon:icon3 userInfo:nil];
application.shortcutItems = @[item1, item2, item3];;
#endif
}
轻按app icon弹出的菜单,点击菜单的item的回调, 通过shortcutItem的type判断不同的item,处理不同的逻辑
#ifdef __IPHONE_9_0
/// 点击app上标签item后,进入时对应事件的处理,
-(void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
UINavigationController *nav = (UINavigationController *)self.window.rootViewController;
if([shortcutItem.type isEqualToString:shortcutItemType1Key]){
NSLog(@"点击了添加到通讯录");
UIViewController *vc = [[UIViewController alloc] init];
vc.title = @"通讯录";
vc.view.backgroundColor = [UIColor yellowColor];
[nav pushViewController:vc animated:YES];
}
if ([shortcutItem.type isEqualToString:shortcutItemType2Key]){
NSLog(@"点击了搜索");
UIViewController *vc = [[UIViewController alloc] init];
vc.title = @"搜索";
vc.view.backgroundColor = [UIColor redColor];
[nav pushViewController:vc animated:YES];
}
if ([shortcutItem.type isEqualToString:shortcutItemType3Key]) {
NSLog(@"点击了分享");
UIViewController *vc = [[UIViewController alloc] init];
vc.title = @"分享";
vc.view.backgroundColor = [UIColor blueColor];
[nav pushViewController:vc animated:YES];
}
}
#endif
2.App内界面上通过Peek 用户按弹出的预览界面
首先在需要弹出预览的控制器中检测设备是否支持3d touch 功能, 并注册预览代理,实现UIViewControllerPreviewingDelegate方法
@implementation ViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
/// 检测是否有3d touch 功能
if ([self respondsToSelector:@selector(traitCollection)]) {
if ([self.traitCollection respondsToSelector:@selector(forceTouchCapability)]) {
if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
// 支持3D Touch
if ([self respondsToSelector:@selector(registerForPreviewingWithDelegate:sourceView:)]) {
[self registerForPreviewingWithDelegate:self sourceView:self.view];
}
} else {
// 不支持3D Touch
}
}
}
}
#pragma mark - UIViewControllerPreviewingDelegate
#ifdef __IPHONE_9_0
// 弹出预览页面
- (nullable UIViewController *)previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location NS_AVAILABLE_IOS(9_0) {
// previewingContext.sourceView: 触发Peek & Pop操作的视图
// previewingContext.sourceRect: 设置触发操作的视图的不被虚化的区域
MyViewController *myVc = [MyViewController new];
// 预览区域大小(可不设置)
myVc.preferredContentSize = CGSizeMake(0, 320);
return myVc;
}
// 在弹出页面的基础上,再继续用力按下去,就会push预览界面了。(pop功能)
- (void)previewingContext:(id <UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit NS_AVAILABLE_IOS(9_0) {
[self showViewController:viewControllerToCommit sender:self];
}
#endif
@end
如果想要在3D Touch 触发显示预览界面的情况下,设置向上滑动视图,下面出现的菜单,需要在预览视图的控制器中重写previewActionItems,添加UIPreviewAction(弹出的每个按钮)
@implementation MyViewController
// 实现这个方法之后, 3D Touch触发的情况下,向上滑动视图,下面会出现菜单
// 这个方法就是定义向上滑动的菜单的
#ifdef __IPHONE_9_0
- (NSArray<id<UIPreviewActionItem>> *)previewActionItems NS_AVAILABLE_IOS(9_0) {
return @[
[UIPreviewAction actionWithTitle:@"iTem1" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
NSLog(@"点击了iTem1,%@", action);
}],
[UIPreviewAction actionWithTitle:@"iTem2" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
NSLog(@"点击了iTem2,%@", action);
}],
[UIPreviewAction actionWithTitle:@"iTem3" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
NSLog(@"点击了iTem3,%@", action);
}]
];
}
#endif
@end
3.添加widget
- 1.如何为现有的工程添加widget;
在现有的项目的中创建widget
Xcode菜单 -> File -> New -> Target.. -> 选择Today Extension
新建一个target
选择Today Extension
为你的target名称后创建完成,会出现5个文件, 下面也是添加Today Extension之后的项目
如果你想使用存代码,不用storyboard搭建UI ,可以在Widget这个target中删除MainInterface.storyboard,并在info.plist中删除NSExtensionMainStoryboard,并添加NSExtensionPrincipalClass,设置其value为TodayViewController
- 2如何写UI界面
在系统生成的TodayViewController中构建UI界面
- (void)viewDidLoad {
[super viewDidLoad];
// 设置widget展示的视图大小
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 100);
}
- 3.点击widge处调用app
点击Widget布局任何区域都能唤起主应用程序,常用的方式在整个TodayViewController的View增加Tap事件
因为extension和app是两个完全独立的进程,所以它们之间不能直接通信(不能像应用内部点击按钮,跳转到指定页面)。为了实现widget调起app,这里通过openURL的方式来启动app。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
// 设置widget展示的视图大小
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 100);
// 如你所看当用户拉开Widget时,因为Widget是依赖于应用程序在分发时是跟应用程序一块打包的,希望点击Widget布局任何区域都能唤起主应用程序,常用的方式在整个View增加Tap事件订阅处理
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(openApp:)]];
// 但这种方式会额外产生一个问题,如果Widget空白区域没有任何UI元素则无法触发该事件,那这里有一个小技巧可以解决改问题,可以整个Widget增加一个透明的ImageView:
// 创建一个透明的ImageView作为widge整个背景
UIImageView *bgImageView = [UIImageView new];
bgImageView.userInteractionEnabled = YES;
bgImageView.alpha = 0.01;
bgImageView.backgroundColor = [UIColor grayColor];
[self.view addSubview:bgImageView];
bgImageView.frame = self.view.bounds;
}
// 点击整个widget区域的事件
- (void)openApp:(UITapGestureRecognizer *)tap {
[self.extensionContext openURL:[NSURL URLWithString:@"TodayDemo://action=jumpToHomePage"]
completionHandler:^(BOOL success) {
NSLog(@"打开%@", success ? @"成功" : @"失败");
}];
}
如果要完成widget调用app,需要在info.plist中配置URL Types
app需要处理接收跳转,这个在AppDelegate的代理方法中设置
// 接收来自TodayViewController的跳转
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
NSString *prefix = @"TodayDemo://";
if ([[url absoluteString] rangeOfString:prefix].location != NSNotFound) {
NSString *func = [[url absoluteString] substringFromIndex:prefix.length];
if ([func isEqualToString:@"jumpToHomePage"]) {
}
else if([func isEqualToString:@"jumpToOtherPage"]) {
}
}
return YES;
}
- 4.如何与host app共享数据。
在iOS10换出菜单都有分享这个事件,这个事件是系统自动添加的,并且apple在调试的时候不会出现这个分享的事件(不知道这个调试的时候好坑人啊,一直在纠结怎么弄不出来那个分享的事件)
在打包ipa时,可以发现有这个 分享Beta版反馈 的事件,appstore 的包出现 分享“app名称”
# define __IPHONE_9_2 90200
这些宏的作用是判断该版本的xcode是否具有这个宏所定义的系统的API,在xcode中有这个宏就代表有这个宏所定义的系统的API。
#define kCurrentSystemVersion ([[[UIDevice currentDevice] systemVersion] floatValue])
通常这个宏是用来区分手机不同版本的系统应该使用什么版本的API。