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

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



附:參數(shù)對(duì)象說(shuō)明
UIApplicationShortcutItem 可以看作是3D Touch點(diǎn)擊后,彈出菜單每行對(duì)應(yīng)的模型,一行對(duì)應(yīng)一個(gè)UIApplicationShortcutItem對(duì)象。
實(shí)例化方法:
- (instancetype)initWithType:(NSString *)type localizedTitle:(NSString *)localizedTitle localizedSubtitle:(nullable NSString *)localizedSubtitle icon:(nullable UIApplicationShortcutIcon *)icon userInfo:(nullable NSDictionary *)userInfo NS_DESIGNATED_INITIALIZER;
type : 對(duì)應(yīng)UIApplicationShortcutItem對(duì)象的唯一標(biāo)識(shí)符。
localizedTitle : 對(duì)應(yīng)UIApplicationShortcutItem對(duì)象的主標(biāo)題
localizedSubtitle : 對(duì)應(yīng)UIApplicationShortcutItem對(duì)象的副標(biāo)題
icon : 對(duì)應(yīng)要顯示的圖標(biāo),有兩種圖標(biāo):
- 系統(tǒng)自帶的類型,代碼如下:
+ (instancetype)iconWithType:(UIApplicationShortcutIconType)type;
2.用戶自定義的圖片,代碼如下
+ (instancetype)iconWithTemplateImageName:(NSString *)templateImageName;
要注意的是,如果通過(guò)第二種自定義方式創(chuàng)建圖標(biāo),必須使用指定格式的圖片,不然顯示出來(lái)的是一片黑色。開發(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 : 主要是用來(lái)提供APP的版本信息
至此主屏幕icon上的快捷標(biāo)簽創(chuàng)建就介紹完了,而他們點(diǎn)擊進(jìn)入頁(yè)面的實(shí)現(xiàn)就有點(diǎn)類似消息通知的實(shí)現(xiàn)方式了,只要增加兩處代碼就好:首次啟動(dòng)APP和APP沒(méi)被殺死從后臺(tái)啟動(dòng)
1.2. APP沒(méi)有啟動(dòng)的情況下點(diǎn)擊3DTouch快速啟動(dòng)

其中這個(gè)調(diào)用方法上圖已經(jīng)給出來(lái)了,
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
就是在這個(gè)方法中調(diào)用的
其中ABGlobaiAPP是自定義的全局的視圖控制轉(zhuǎn)換器,負(fù)責(zé)處理root根視圖的切換


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

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




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