一、簡介
3D Touch是指:通過對屏幕施加不同程度的壓力來訪問附加功能。應用可以通過顯示菜單、展示其他內容和播放動畫等形式來表現(xiàn)3D Touch,該功能從6s及其以上機型開始得到支持。
3D Touch的主要提現(xiàn)方式有三種:
- 主屏交互 (Home Screen Interaction)
- 預覽和跳轉 (Peek and Pop)
- LivePhoto
二、提綱
1.主屏交互 (Home Screen Interaction)
- 靜態(tài)添加快捷操作
- 動態(tài)添加快捷操作
2.預覽和跳轉 (Peek and Pop)
- Peek
1.注冊3D Touch
2.通過代理實現(xiàn)功能 - Pop
1.通過代理實現(xiàn)功能
三、實現(xiàn)
1.主屏操作
3D Touch在主屏交互的表現(xiàn)形式:當用戶點擊APP的同時并施加一定壓力的時候,程序會在適當?shù)奈恢谜故境鲆粋€菜單選項列表。操作效果如下圖所示

其中添加快捷操作有兩種
- 通過 "靜態(tài)" 的方式添加快捷操作
- 通過 "動態(tài)" 的方式添加快捷操作
其中 "靜態(tài)" 快捷操作主要是在項目的info.plist文件中添加相關的屬性,這個網(wǎng)上資料很多,我就不介紹了。我們重點介紹動態(tài)快捷操作
1.1. 動態(tài)快捷操作
通過動態(tài)的方式添加快捷操作:這種方式主要通過代碼的形式把UIApplicationShortcutItem對象數(shù)組傳給UIApplication單例對象。我們可以在APP啟動方法
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
里面添加我們的代碼
PS: 我是為AppDelegate添加了一個叫PressTouch的分類,把所有關于3DTouch的代碼全部寫在了這個分類里,APPdelegate只需要調用.h中暴露的方法就行,算是簡化了APPDelegate中的代碼吧~~



附:參數(shù)對象說明
UIApplicationShortcutItem 可以看作是3D Touch點擊后,彈出菜單每行對應的模型,一行對應一個UIApplicationShortcutItem對象。
實例化方法:
- (instancetype)initWithType:(NSString *)type localizedTitle:(NSString *)localizedTitle localizedSubtitle:(nullable NSString *)localizedSubtitle icon:(nullable UIApplicationShortcutIcon *)icon userInfo:(nullable NSDictionary *)userInfo NS_DESIGNATED_INITIALIZER;
type : 對應UIApplicationShortcutItem對象的唯一標識符。
localizedTitle : 對應UIApplicationShortcutItem對象的主標題
localizedSubtitle : 對應UIApplicationShortcutItem對象的副標題
icon : 對應要顯示的圖標,有兩種圖標:
- 系統(tǒng)自帶的類型,代碼如下:
+ (instancetype)iconWithType:(UIApplicationShortcutIconType)type;
2.用戶自定義的圖片,代碼如下
+ (instancetype)iconWithTemplateImageName:(NSString *)templateImageName;
要注意的是,如果通過第二種自定義方式創(chuàng)建圖標,必須使用指定格式的圖片,不然顯示出來的是一片黑色。開發(fā)文檔規(guī)定格式如下:
The provided image named will be loaded from the app's bundle and will be masked to conform to the system-defined icon style.
userInfo : 主要是用來提供APP的版本信息
至此主屏幕icon上的快捷標簽創(chuàng)建就介紹完了,而他們點擊進入頁面的實現(xiàn)就有點類似消息通知的實現(xiàn)方式了,只要增加兩處代碼就好:首次啟動APP和APP沒被殺死從后臺啟動
1.2. APP沒有啟動的情況下點擊3DTouch快速啟動

其中這個調用方法上圖已經給出來了,
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
就是在這個方法中調用的
其中ABGlobaiAPP是自定義的全局的視圖控制轉換器,負責處理root根視圖的切換


處理難點 : 這個需要判斷 "跳轉" 的界面處于
tabBar的第幾個HomeController
比如我四個一級界面分別叫做
HomeViewController、MemberHomeViewController 、MessageHomeViewController、MineHomeViewController四個一級界面。
因為APP默認啟動時都是展示HomeViewController,如果選擇的是HomeViewController的子ViewController,則此時的NavigationController剛好是以HomeViewController為根視圖的,可以直接用self.navigation跳轉,但是如果點擊的是MineHomeViewController的子ViewController,則跳轉之前需要先切換tabBar的selectedViewController,找到以MineHomeViewController為根視圖的nav,然后再執(zhí)行跳轉
ps:因為這只是在程序未啟動的時候才走這個方法,所以并不存在無限壓棧的情況,所以這種解決起來也比較簡單。
1.3. APP沒被殺死,還存在于后臺,點擊3D Touch對應的跳轉
如果程序沒有被殺死,只是存活在后臺,點擊3D Touch會執(zhí)行這個方法

點擊3D Touch時因為還存在于后臺,并不會走
didFinishLaunchingWithOptions這個方法的,所以我采用的是通知,以通知的形式告訴APP我點擊了3D Touch,需要執(zhí)行對應的跳轉操作
這里發(fā)送完通知后,需要手動調用completionHandler,告訴系統(tǒng)你已經執(zhí)行完此次操作。
這里我選擇的是在HomeViewController也就是首頁接受3D Touch的通知
- (void)pressTouchAction:(NSNotification *)notifi {
if ([UserAccountModel userLogin]) { // 如果已經登錄,跳轉到對應的VC
// 將要push的VC的name
NSString *destinationVCName = [notifi.userInfo objectForKey:@"VCName"];
if ([destinationVCName isEqualToString:@"MCSettingViewController"]) { // 點擊的是個人中心的設置
// 先將之前的其他tabBar的跳轉到首頁(如果不這樣做,有可能上一次執(zhí)行這個方法時,首頁已經跳轉到二級界面,那個你點擊下面的tabBar時,他跳轉的是二級界面,而不是首頁)
BaseNavigationController *homeNav = [self.tabBarController.viewControllers firstObject];
[homeNav popToRootViewControllerAnimated:NO]; // 靜默跳轉,否則肉眼可見跳轉動畫
[self.tabBarController setSelectedIndex:3]; //先跳轉tabBar
// 取出以MCMineHomeViewController為根視圖的nav,以后就用這個nav去實現(xiàn)跳轉
BaseNavigationController *nav = [self.tabBarController.viewControllers lastObject];
// 當前navigationVC下的topviewController的name
NSString *currentVCName = NSStringFromClass([nav.topViewController class]);
if ([currentVCName isEqualToString:@"MCMineHomeViewController"]) {
// 當前的navigationVC下的topViewController是rootVC, 可以直接push,不存在無限入棧的情況
BaseViewController *destinationVC = [NSClassFromString(destinationVCName) new];
[nav pushViewController:destinationVC animated:YES];
}else { // 如果當前的navigationVC下的topViewController不是rootVC,pop到rootVC
[nav popToRootViewControllerAnimated:NO];
BaseViewController *destinationVC = [NSClassFromString(destinationVCName) new];
[nav pushViewController:destinationVC animated:YES];
}
}else { //點擊的是首頁中的幾個選項
// 先將之前的其他tabBar的跳轉到首頁
BaseNavigationController *mineNav = [self.tabBarController.viewControllers lastObject];
[mineNav popToRootViewControllerAnimated:NO];
[self.tabBarController setSelectedIndex:0];
// 取出以HomeViewController為根視圖的nav,以后就用這個nav去實現(xiàn)跳轉
BaseNavigationController *nav = [self.tabBarController.viewControllers firstObject];
// 當前navigationVC下的topviewController的name
NSString *currentVCName = NSStringFromClass([nav.topViewController class]);
if ([currentVCName isEqualToString:NSStringFromClass([HomeViewController class])]) {
// 當前的navigationVC下的topViewController是rootVC, 可以直接push,不存在無限入棧的情況
if ([destinationVCName isEqualToString:@"QRCodeViewController"]) {
QRCodeViewController *qrCodeVC = [[QRCodeViewController alloc] init];
qrCodeVC.qrcodeType = MemberDetaill;
[nav pushViewController:qrCodeVC animated:YES];
}else {
BaseViewController *destinationVC = [NSClassFromString(destinationVCName) new];
[nav pushViewController:destinationVC animated:YES];
}
}else { // 如果當前的navigationVC下的topViewController不是rootVC,pop到rootVC
[nav popToRootViewControllerAnimated:NO];
if ([destinationVCName isEqualToString:@"QRCodeViewController"]) { //如果是QRCodeViewController
QRCodeViewController *qrCodeVC = [[QRCodeViewController alloc] init];
qrCodeVC.qrcodeType = MemberDetaill;
[nav pushViewController:qrCodeVC animated:YES];
}else {
BaseViewController *destinationVC = [NSClassFromString(destinationVCName) new];
[nav pushViewController:destinationVC animated:YES];
}
}
}
}else {
// 如果沒有登錄,跳轉到登錄界面
[[ABGlobaiAPP sharedInstance] gotoSiginViewController];
}
}
處理難點
- 跳轉時需要考慮其他界面是否處于一級界面,如果沒有則需要將其他界面先跳轉到一級界面。
- 需要考慮到當前界面 是否是rootVC,然后處理無限入棧的問題。
2.Peek and Pop
Peek and Pop主要是通過3D Touch,使用戶可以在當前視圖預覽頁面、鏈接或者文件。如果當前頁面控制器注冊了3D Touch,我們只需要點擊相應的內容并施加一點壓力,就能使當前內容高亮,并且其他內容進入一個模糊虛化的狀態(tài);當我們再施加一點壓力,就能預覽當前內容對應的頁面;如果需要進入到該內容對應的頁面,我們只需要稍微再施加一點壓力直至預覽視圖放大到全屏,就可以跳轉到其對應的頁面。另外,如果我們在預覽頁面的同時,往上拖拽就可以顯示出一個類似UIActionsheet界面的快捷操作菜單,效果如下面幾張圖片:




2.1. 實現(xiàn)VC1商品評論列表快速預覽VC2商品評論詳情的功能(peek)
2.1.1. 要使用3D Touch,先向要響應3D Touch功能的視圖控制器注冊3D Touch,并指定接收手勢的源視圖。
毫無疑問,要響應的視圖是TableView中的Cell。我們在Cell的初始化方法中加入以下代碼
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ProductCommentTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ProductCommentTableViewCell" forIndexPath:indexPath];
cell.model = [self.dataSource objectAtIndex:indexPath.row];
// 注冊3D Touch
/**
從iOS9開始,我們可以通過這個類來判斷運行程序對應的設備是否支持3D Touch功能。
UIForceTouchCapabilityUnknown = 0, //未知
UIForceTouchCapabilityUnavailable = 1, //不可用
UIForceTouchCapabilityAvailable = 2 // 可用
*/
if ([self respondsToSelector:@selector(traitCollection)]) {
if ([self.traitCollection respondsToSelector:@selector(forceTouchCapability)]) {
if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
[self registerForPreviewingWithDelegate:(id)self sourceView:cell];
}
}
}
return cell;
}
因為只有在6s及其以上的設備才支持3D Touch,我們可以通過UITraitCollection這個類的UITraitEnvironment協(xié)議屬性來判斷設備是否支持3D Touch。
UITraitEnvironment是UIViewController所遵守的其中一個協(xié)議,不僅包含了UI界面環(huán)境特征,而且包含了3D Touch的特征描述。
2.1.2 VC1 中實現(xiàn)UIViewControllerPreviewingDelegate代理,監(jiān)聽3D Touch手勢的觸發(fā)
示例代碼如下:
#pragma mark - UIViewControllerPreviewingDelegate
// 3D Touch時預覽的界面
- (nullable UIViewController *)previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {
// 找到點擊的是哪個Cell
NSIndexPath *indexPath = [self.tableView indexPathForCell:(ProductCommentTableViewCell *)[previewingContext sourceView]];
// 創(chuàng)建要預覽的控制器
GoodCommentDetailViewController *commentDetailVC = [[GoodCommentDetailViewController alloc] init];
commentDetailVC.model = [self.dataSource objectAtIndex:indexPath.row];
// 指定當前上下文視圖rect
CGRect rect = CGRectMake(0, 0, kScreenWidth, 300);
previewingContext.sourceRect = rect;
return commentDetailVC;
}
2.2. 實現(xiàn)從VC1跳轉到VC2的功能(Pop)
// 深度按壓之后跳轉的界面
- (void)previewingContext:(id<UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit {
[self showViewController:viewControllerToCommit sender:self];
}
2.3. 快捷功能菜單的生成
如果我們需要在VC1快速預覽視圖出現(xiàn)時,向上拖拽得到一個快捷功能菜單,需要在VC2中實現(xiàn)以下代理方法:
// 預覽界面時需要實現(xiàn)的功能
- (NSArray<id<UIPreviewActionItem>> *)previewActionItems {
UIPreviewAction *action1 = [UIPreviewAction actionWithTitle:@"選項一" style:UIPreviewActionStyleDefault handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
}];
UIPreviewAction *action2 = [UIPreviewAction actionWithTitle:@"使用自己名字替換用戶名字" style:UIPreviewActionStyleSelected handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
kWeakSelf;
weakSelf.model.userName = @"濤昇依舊";
[weakSelf.dataSource replaceObjectAtIndex:weakSelf.indexPath withObject:weakSelf.model];
[ZYNotification postNotificationName:@"changeCommentUserName" object:nil];
}];
UIPreviewAction *action3 = [UIPreviewAction actionWithTitle:@"選項三" style:UIPreviewActionStyleDestructive handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
}];
return @[action1, action2, action3];
}
當然了,既然發(fā)送通知,我們就需要在商品評論列表界面接收通知,刷新界面
[ZYNotification addObserver:self selector:@selector(reloadTableViewDataIfChangedData) name:@"changeCommentUserName" object:nil];
- (void)reloadTableViewDataIfChangedData {
[self.tableView reloadData];
}
實現(xiàn)了這個代理,我們就可以在VC1中快速預覽往上拖拽得到一個快捷功能菜單。而且,我們不需要進入VC2,直接通過點擊快捷菜單的【替換該元素】這個選項,就能調用VC2替換元素的方法。應用場景:iPhone在短信列表頁面,通過快捷功能菜單快速回短信。
本文參考
- iOS 3D Touch超詳細入門 作者:DamonMok