有時候,知識小集羣裏討論的技術問題,比較有價值,咱們會把有價值的內容整理出來供你們查閱。但爲了保護羣友隱私,須要把暱稱和頭像都打碼,若是碰到幾百條聊天記錄,這樣作簡直要吐血。並且也不能截一張長圖,只能一張一張截取,而後拼接起來。羣聊記錄只能在微信內分享,這也限制了傳播的渠道。爲了提升小集成員工做效率,想着能不能給微信作個插件,解決這些問題。咱們一直在追求如何更有效率開展咱們的工做,好比使用腳本自動整理每週小集內容,使用微信小程序給讀者更好閱讀體驗。(呀,還有腳本,若是你還不知道,那確定沒有點 star 吧,傳送門)node
經過以上痛點,能夠肯定咱們要解決的問題主要有:git
下面這張圖是聊天記錄頁面,點擊導航右邊按鈕,會彈出 ActionSheet。從圖中能夠看出,添加截圖功能,在 ActionSheet 上添加是不錯的選擇。github
咱們下面主要的工做是:小程序
本文使用 MonkeyDev 工具開發(無需越獄),重點是教你如何開發一個微信插件,並不打算介紹工具。關注咱們的朋友應該都知道,以往的 #iOS知識小集 中我已經介紹了三個工具的使用。這三個工具在下面會用到。微信小程序
使用 Reveal 工具查看聊天記錄頁面對應的 VC,ActionSheet 對應的類名。如何使用 Reveal 調試第三方 APP,網上有不少教程。使用MonkeyDev無需越獄。bash
經過上圖能夠看到聊天記錄所對應的VC是 MsgRecordDetailViewController
,使用 MMTableView
展現聊天內容。彈出的 ActionSheet 對應的類爲 WCActionSheet
, 能夠發現它是一個 UIWindow
。 那麼咱們看看這幾個類中的內容吧。微信
使用 class-dump 查看第三方 APP 的頭文件。在 #iOS知識小集 中已經介紹過這個工具的使用。app
在MsgRecordDetailViewController
的頭文件中發現有一個 WCActionSheet *favImgLongPressAction;
咱們能夠判定出 WCActionSheet
就是咱們要找的 ActionSheet。好了,接下來主要就是看 WCActionSheet
的頭文件,挖掘有用的信息。工具
WCActionSheet頭文件ui
@property(strong, nonatomic) NSMutableArray *buttonTitleList;
- (void)showInView:(id)arg1;
- (long long)addButtonWithItem:(id)arg1 atIndex:(unsigned long long)arg2;
- (long long)addButtonWithTitle:(id)arg1 atIndex:(unsigned long long)arg2;
- (long long)addButtonWithTitle:(id)arg1;
複製代碼
咱們的目標是給 WCActionSheet
添加3個菜單。下面這些方法彷佛對咱們有用。目前想到有兩種方案:
1.在 buttonTitleList 中添加一個對象
咱們所關心的最主要的問題是 buttonTitleList
中存放的的對象是什麼?須要使用Cycript工具,這個工具在以往的 #iOS知識小集 介紹過,想了解的朋友能夠在知識小集小程序中搜索 Cycript調試第三方APP。
經過 Cycript 能夠看到 buttonTitleList
中存放的對象是 WCActionSheetItem
。咱們看看 WCActionSheetItem
的頭文件,發現其實就是一個 Model 對象,用來表示菜單的標題,顏色等等。
@interface WCActionSheetItem : NSObject
@property(copy, nonatomic) NSString *titleColor;
@property(copy, nonatomic) NSString *title;
- (id)initWithTitle:(id)arg1 fontSize:(long long)arg2 fontColor:(id)arg3 WithDesc:(id)arg4 descFontSize:(long long)arg5 descFontColor:(id)arg6 enable:(_Bool)arg7;
- (id)initWithTitle:(id)arg1;
複製代碼
看到這裏,咱們能夠直接在 buttonTitleList
中添加 WCActionSheetItem
實例便可。
2.直接調用 addButtonWithTitle:
方法
從上圖能夠看出直接調用 addButtonWithTitle:
這個方法,返回一個 Index 爲 3,說明能夠直接調用這個方法。
接下來主要的問題是,找到添加菜單的時機。第一想到的是在 WCActionSheetDelegate
的代理中添加菜單,果斷在 MsgRecordDetailViewController
中 Hook 下面這3個代理方法,可是經過實驗證實,發現最後兩個方法並無被調用,由於 MsgRecordDetailViewController
並無實現這兩個代理,只好放棄了這種思路。
- (void)actionSheet:(id)arg1 clickedButtonAtIndex:(long long)arg2;
- (void)didPresentActionSheet:(WCActionSheet *)arg1;
- (void)willPresentActionSheet:(WCActionSheet *)arg1;
複製代碼
無奈之下看到 WCActionSheet
中有個showInView:
方法, 能夠直接 Hook 這個方法。但這樣致使全部的 WCActionSheet
都會被添加了額外的菜單。而咱們的目的只是在聊天記錄頁中的 WCActionSheet
顯示截圖菜單。因此用 [WeChatSaveData defaultSaveData].isNeedAddMenu
加了一個判斷,isNeedAddMenu 在 MsgRecordDetailViewController
頁面將要出現的時候,設置成 YES,在頁面將要消失的時候,設置成 NO。因此須要 Hook MsgRecordDetailViewController
的viewWillAppear:
和viewWillDisappear:
方法。
CHOptimizedMethod1(self, void, WCActionSheet, showInView, UIView *, view){
if ([WeChatSaveData defaultSaveData].isNeedAddMenu) {
// 方案一
[self addButtonWithTitle:@""]; // 填坑
[self addButtonWithTitle:kScreenshotTitle];
[self addButtonWithTitle:kScreenshotTitleMask];
// 方案二
WCActionSheetItem *shotItem = [[objc_getClass("WCActionSheetItem") alloc] initWithTitle:kScreenshotTitle];
WCActionSheetItem *shotItem2 = [[objc_getClass("WCActionSheetItem") alloc] initWithTitle:kScreenshotTitleMask];
[self.buttonTitleList addObject:shotItem];
[self.buttonTitleList addObject:shotItem2];
}
CHSuper1(WCActionSheet, showInView, view);
}
複製代碼
執行結果以下圖:
MsgRecordDetailViewController頭文件
@interface MsgRecordDetailViewController: UIViewController
{
MMTableView *m_tableView;
}
- (void)viewWillAppear:(_Bool)arg1;
- (void)viewWillDisappear:(_Bool)arg1;
- (void)actionSheet:(id)arg1 clickedButtonAtIndex:(long long)arg2;
- (UITableViewCell *)tableView:(id)arg1 cellForRowAtIndexPath:(id)arg2;
@end
複製代碼
經過對 MsgRecordDetailViewController
頭文件分析,能夠達到截圖功能,只須要截取 TableView 爲一張長圖便可。
獲取到 MsgRecordDetailViewController
實例,使用 KVC 的方式便可獲取到 MMTableView
MMTableView *tableView = [viewController valueForKeyPath:@"m_tableView"];
首先須要 Hook actionSheet: clickedButtonAtIndex:
捕獲菜單的點擊事件,作截圖功能。
CHOptimizedMethod2(self, void, MsgRecordDetailViewController, actionSheet, WCActionSheet*, sheet, clickedButtonAtIndex, int, index){
CHSuper2(MsgRecordDetailViewController, actionSheet, sheet, clickedButtonAtIndex, index);
[WeChatCapture saveCaptureImageWithSheet:sheet index:index viewController:self];
}
複製代碼
saveCaptureImageWithSheet
這個方法中主要獲取到 MMTableView 並截圖保存到相冊。有興趣能夠看源碼。
爲了保護用戶的隱私,須要對用戶的頭像和暱稱作保護,那麼咱們能夠在 TableView 的代理中獲取頭像和暱稱對應的 View,而後替換 View 的內容便可。須要 Hook cellForRowAtIndexPath
這個方法。
CHOptimizedMethod2(self, UITableViewCell *, MsgRecordDetailViewController, tableView, MMTableView *, tableViewArg, cellForRowAtIndexPath, NSIndexPath, *indexPath){
UITableViewCell *cell = CHSuper2(MsgRecordDetailViewController, tableView, tableViewArg, cellForRowAtIndexPath, indexPath);
[WeChatCapture updateCellDataWithCell:cell indexPath:indexPath];
return cell;
}
複製代碼
獲取到 Cell 若是當前是要打碼截圖,須要對頭像和暱稱的內容作處理。這裏作一個特殊的處理,頭像和暱稱咱們換成三國人物的頭像的名字。
+ (void)updateCellDataWithCell:(UITableViewCell *)cell indexPath:(NSIndexPath *)indexPath
{
if ([WeChatSaveData defaultSaveData].maskType == WeChatSaveDataMaskTypeMast || [WeChatSaveData defaultSaveData].maskType == WeChatSaveDataMaskTypePreview) {
NSArray *subviews = [cell.contentView subviews];
FavRecordBaseNodeView *nodeView = [subviews lastObject];
if ([NSStringFromClass([nodeView class]) hasSuffix:@"NodeView"]) {
UILabel *nickNameLabel = [nodeView valueForKey:@"m_srcTitleLabel"];
if (nickNameLabel) {
CGRect tempFrame = nickNameLabel.frame;
tempFrame.size.width = 120;
nickNameLabel.frame = tempFrame;
}
MMHeadImageView *imageView = [nodeView valueForKeyPath:@"m_headImg"];
NSString *nickName = [imageView valueForKey:@"_nsUsrName"];
WeChatUser *aUser = [[WeChatSaveData defaultSaveData].userNameDict objectForKey:nickName?:@""];
if (!aUser) {
aUser = [WeChatUser user];
[[WeChatSaveData defaultSaveData].userNameDict setObject:aUser forKey:nickName?:@""];
}
nickNameLabel.text = aUser.nickname ?: @"";
if (imageView) {
[imageView updateUsrName:aUser.nickname withHeadImgUrl:aUser.icon];
}
}
}
}
複製代碼
給繪製的長圖添加一個小集的版權 by公衆號 知識小集。
最終效果(伴隨着咔嚓一聲,一張被打碼的照片保存到了相冊,你能夠在任意的渠道分享了):
WCActionSheet的代理方法Index不對
添加額外的菜單後,WCActionSheet 的代理方法 actionSheet: clickedButtonAtIndex:
中的 Index 點擊取消或空白區域總爲 2,也就是我添加菜單第一個的 Index。致使每次點擊取消或空白區域時都會聽到咔嚓一聲截圖。解決方法,就是加一個沒有標題的菜單,而且高度爲 0。
收藏的聊天記錄也須要有截圖功能
按着這個思路給收藏中的聊天記錄添加截圖功能,這就是你爲何會在源碼中看到 FavRecordDetailViewController
的 Hook。
逆向的整個流程能夠歸結爲:
而本文也是按這幾個步驟逐漸實現的:
本插件以開源 前往查看 ,若是想安裝在本身的手機上,須要安裝 MonkeyDev,把圖中的文件導入便可
知識小集是一個團隊公衆號,每週都會有原創文章分享,咱們的文章都會在公衆號首發。知識小集微信羣,短短几周時間,目前羣友已經300+人,很快就要達到上限(抓住機會哦),關注公衆號獲取加羣方式。