3D Touch實(shí)現(xiàn)及無(wú)限入棧bug的解決

一、簡(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)方式有三種:
  1. 主屏交互 (Home Screen Interaction)
  2. 預(yù)覽和跳轉(zhuǎn) (Peek and Pop)
  3. 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)列表。操作效果如下圖所示


HomeScreen展示效果

其中添加快捷操作有兩種

  • 通過(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中的代碼吧~~

PressTouch中.h代碼

APPDelegate直接調(diào)用

添加shortCutItem方法

附:參數(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):

  1. 系統(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)

APP沒(méi)有啟動(dòng),直接通過(guò)3D Touch快捷操作啟動(dòng).jpg

其中這個(gè)調(diào)用方法上圖已經(jīng)給出來(lái)了,

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

就是在這個(gè)方法中調(diào)用的
其中ABGlobaiAPP是自定義的全局的視圖控制轉(zhuǎn)換器,負(fù)責(zé)處理root根視圖的切換

未啟動(dòng)時(shí)視圖跳轉(zhuǎn)-1

未啟動(dòng)時(shí)視圖跳轉(zhuǎn)-2

處理難點(diǎn) : 這個(gè)需要判斷 "跳轉(zhuǎn)" 的界面處于tabBar的第幾個(gè)HomeController
TabBar分布圖

比如我四個(gè)一級(jí)界面分別叫做HomeViewController、MemberHomeViewControllerMessageHomeViewController、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è)方法

程序存活在后臺(tái)時(shí),點(diǎn)擊3D Touch

點(diǎn)擊3D Touch時(shí)因?yàn)檫€存在于后臺(tái),并不會(huì)走didFinishLaunchingWithOptions這個(gè)方法的,所以我采用的是通知,以通知的形式告訴APP我點(diǎn)擊了3D Touch,需要執(zhí)行對(duì)應(yīng)的跳轉(zhuǎn)操作
APP存活時(shí)點(diǎn)擊3D Touch,發(fā)送通知

這里發(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界面的快捷操作菜單,效果如下面幾張圖片:


輕點(diǎn)使當(dāng)前內(nèi)容高亮,并且其他內(nèi)容進(jìn)入一個(gè)模糊虛化的狀態(tài)

再次加大壓力,就能預(yù)覽當(dāng)前內(nèi)容對(duì)應(yīng)的頁(yè)面

稍微再施加一點(diǎn)壓力直至預(yù)覽視圖放大到全屏,可以跳轉(zhuǎn)到其對(duì)應(yīng)的頁(yè)面

在預(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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、通過(guò)CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫(kù)組件 SD...
    陽(yáng)明AI閱讀 16,171評(píng)論 3 119
  • 一、簡(jiǎn)介 3D Touch是指:通過(guò)對(duì)屏幕施加不同程度的壓力來(lái)訪問(wèn)附加功能。應(yīng)用可以通過(guò)顯示菜單、展示其他內(nèi)容和播...
    DamonMok閱讀 20,837評(píng)論 11 93
  • 前言 關(guān)于3D touch蘋果官方文檔是這么開始介紹的: 大意如下:iOS9開始,所有新的手機(jī)都增加了一個(gè)三維的用...
    VV木公子閱讀 2,371評(píng)論 3 39
  • 你已經(jīng)很不錯(cuò)了,畢竟還有人誤叫你做經(jīng)理。我聽(tīng)到之后真的開心了一個(gè)星期,哈哈哈哈哈,但是我還是辭職了。 回想起上一年...
    大漠孤兒閱讀 1,123評(píng)論 0 0
  • 昨夜 我又夢(mèng)見(jiàn)了家鄉(xiāng)的銀杏樹 夢(mèng)見(jiàn)了你 綠的葉子 黃的葉子 飛呀飛呀 飛去了天涯海角 樹根卻賴在老地方 幾百年 幾...
    大智勿小聰閱讀 513評(píng)論 10 29

友情鏈接更多精彩內(nèi)容