21-自动抢红包UI
前言
本篇文章将使用前面所讲的工具和语法
,做一个实战演练,修改微信的【设置】
页面 👉 增加2个cell
,包含是否启用自动抢红包功能的开关,以及抢红包时的手速设置,最终效果图如下👇
当然,在做这个之前,需要使用MokeyDev
对WeChat
进行重签名并安装
。请参考15-Hook原理(二)反Hook防护 & MokeyDev
一、UI分析
-
使用
Cycript
附加进程。具体请参考19-Cycript -
使用
pvcs()
找到设置页的控制器👇🏻
- 打印控制器
View
下的所有视图
,从中找到UITableView
,并找到对应的数据源👇🏻
- 接着我们
dump
出微信App所有的头文件👇🏻
./class-dump -H WeChat -o ./header/
打开WCTableViewManager.h
文件,找到数据源和关键方法👇🏻
接下来就是对这些方法进行Hook
了。
重签名后微信无法登录的问题
以上的方式,必须利用MokeyDev
对微信的ipa进行重签名,在运行项目,登录进入微信的页面,但是,我自己尝试登录的时候,发现👇🏻
于是没法进入微信的设置页面
,此时怎么办呢?
-
将手机
越狱
,可借助爱思助手
,也可使用越狱工具👇🏻 -
手机
越狱
后安装Cycript
👉🏻 Cycript 基本使用 -
使用
OpenSSH
连接手机,使用cy
指令查看微信的页面(即ViewController
)信息- 有封装的比较好的
cy文件
👉🏻 mjcript.cy
- 有封装的比较好的
- 接下来就跟上面一样,搜索出
WCTableView
二、定位
接下来,我们要对【设置】页面进行分析,找到具体的注入代码
的位置
。
- 对
WCTableViewManager
中的numberOfSectionsInTableView
方法进行HOOK
,打印数据源count
和Section
个数👇🏻
#import <UIKit/UIKit.h>
@interface WCTableViewManager : NSObject
@property(retain, nonatomic) NSMutableArray *sections;
@end
%hook WCTableViewManager
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSLog(@"数据源:%ld,Sections:%ld", (long)self.sections.count, (long)[tableView numberOfSections]);
return %orig;
}
%end
- 真机运行项目,查看HOOK之后的打印结果👇🏻
从上图中可以看出,数据源的数目和Section的数目也打印了很多条,说明UITableView
是一个通用的控件,不止一个VC在使用,证明WCTableViewManager
在项目中是通用的,所以,要精准定位Hook,必须在【设置页】,【设置页】上面我们打印过,是NewSettingViewController
👇🏻
- 修改代码,增加判断
NewSettingViewController
的条件👇🏻
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)]){
NSLog(@"数据源:%ld,Sections:%ld", (long)self.sections.count, (long)[tableView numberOfSections]);
}
return %orig;
}
真机运行项目,查看HOOK之后的打印结果👇🏻
三、注入修改
接下来,注入代码修改,实现抢红包的UI。
- 增加Section,我们在单独的分组中添加2个cell👇🏻
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)]){
return %orig+1;
}
return %orig;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)] && (section==[self numberOfSectionsInTableView:tableView]-1)){
return 2;
}
return %orig;
}
⚠️注意:需要在
WCTableViewManager
中,声明numberOfSectionsInTableView:
方法,否则编译会报错。
@interface WCTableViewManager : NSObject <UITextFieldDelegate>
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
@end
- 自定义Cell的实现
- 行高 👉🏻 固定60pt
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)] && (indexPath.section==[self numberOfSectionsInTableView:tableView]-1)){
return 60;
}
return %orig;
}
- cell初始化(先设置背景色,看看效果)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)] && (indexPath.section==[self numberOfSectionsInTableView:tableView]-1)){
NSString *strIdentifier=[NSString stringWithFormat:@"HookCell_%i",(int)indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:strIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:strIdentifier];
}
if(indexPath.row==0){
cell.backgroundColor=[UIColor redColor];
}
else{
cell.backgroundColor=[UIColor blueColor];
}
return cell;
}
return %orig;
}
真机运行👇🏻
增加了自定义Cell成功!🍺🍺🍺🍺🍺🍺
- 完善cell
- 需要导入图片资源,将图片copy到app包中👇🏻
- 完善
tableView:cellForRowAtIndexPath:
方法👇🏻
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if([tableView.nextResponder.nextResponder isKindOfClass:%c(NewSettingViewController)] && (indexPath.section==[self numberOfSectionsInTableView:tableView]-1)){
NSString *strIdentifier=[NSString stringWithFormat:@"HookCell_%i",(int)indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:strIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:strIdentifier];
}
cell.backgroundColor = [UIColor whiteColor];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if(indexPath.row==0){
BOOL isAutoEnable = NO;
cell.imageView.image = [UIImage imageNamed:(isAutoEnable ? @"hook_auto_en" : @"hook_auto_dis")];
cell.textLabel.text = @"自动抢红包";
UISwitch *switchAuto = [[UISwitch alloc] init];
[switchAuto addTarget:self action:@selector(hookAutoAction:) forControlEvents:UIControlEventValueChanged];
switchAuto.on=isAutoEnable;
cell.accessoryView = switchAuto;
}
else{
cell.imageView.image = [UIImage imageNamed:@"hook_wait"];
cell.textLabel.text = @"等待时间(秒)";
UITextField *txtWait=[[UITextField alloc] initWithFrame:CGRectMake(0, 0, 150, 40)];
txtWait.borderStyle = UITextBorderStyleRoundedRect;
txtWait.backgroundColor = [UIColor whiteColor];
txtWait.keyboardType = UIKeyboardTypeNumberPad;
txtWait.returnKeyType = UIReturnKeyDone;
cell.accessoryView = txtWait;
}
return cell;
}
return %orig;
}
- 增加
UISwitch
切换时,触发的hookAutoAction:
方法👇🏻
%new
-(void)hookAutoAction:(UISwitch *)sender{
NSLog(@"自动抢红包:%@", (sender.isOn ? @"启用" : @"禁用"));
}
- 运行👇🏻
四、优化
优化一:❤️图标切换
switch开关的状态,应该关联❤️图标,比如开关开启时👇🏻
- 自动抢红包功能的
启用/禁用
标识,以及抢红包时的手速设置
,都要进行NSUserDefaults
本地化保存
- 增加宏定义 👉🏻 缓存的key 👇🏻
#define HOOKAUTOVALUE @"HookAutoValue"
- 实现UISwitch切换的逻辑👇🏻
%new
-(void)hookAutoAction:(UISwitch *)sender{
[[NSUserDefaults standardUserDefaults] setBool:sender.isOn forKey:HOOKAUTOVALUE];
[[NSUserDefaults standardUserDefaults] synchronize];
[MSHookIvar<UITableView *>(self,"_tableView") reloadData];
}
- 修改cell的初始化,关联开关状态
BOOL isAutoEnable = [[NSUserDefaults standardUserDefaults] boolForKey:HOOKAUTOVALUE];
cell.imageView.image = [UIImage imageNamed:(isAutoEnable ? @"hook_auto_en" : @"hook_auto_dis")];
cell.textLabel.text = @"自动抢红包";
UISwitch *switchAuto = [[UISwitch alloc] init];
[switchAuto addTarget:self action:@selector(hookAutoAction:) >forControlEvents:UIControlEventValueChanged];
switchAuto.on=isAutoEnable;
cell.accessoryView = switchAuto;
优化二:手速设置逻辑
- 同样需要本地缓存,增加宏定义👇🏻
#define HOOKWAITVALUE @"HookWaitValue"
- 添加
UITextFieldDelegate
@interface WCTableViewManager : NSObject <UITextFieldDelegate>
@property(retain, nonatomic) NSMutableArray *sections;
@end
- 在输入时间的同时,delegate出
输入完成
的时机
%new
-(void)textFieldDidEndEditing:(UITextField *)textField {
[[NSUserDefaults standardUserDefaults] setObject:textField.text forKey:HOOKWAITVALUE];
[[NSUserDefaults standardUserDefaults] synchronize];
}
缓存手速时间
。
- 同时识别`输入回车后,放弃焦点,收起键盘
%new
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if ([string isEqualToString:@"\n"]) {
[textField resignFirstResponder];
return NO;
}
return YES;
}
- 完善cell初始化,将
UI
和上述交互功能
关联
cell.imageView.image = [UIImage imageNamed:@"hook_wait"];
cell.textLabel.text = @"等待时间(秒)";
UITextField *txtWait=[[UITextField alloc] initWithFrame:CGRectMake(0, 0, 150, 40)];
txtWait.borderStyle = UITextBorderStyleRoundedRect;
txtWait.backgroundColor = [UIColor whiteColor];
txtWait.keyboardType = UIKeyboardTypeNumberPad;
txtWait.returnKeyType = UIReturnKeyDone;
txtWait.delegate = self;
txtWait.text = [[NSUserDefaults standardUserDefaults] objectForKey:HOOKWAITVALUE];
cell.accessoryView = txtWait;
优化三:键盘遮挡
现在整体的界面和功能都已经完成,但体验时仍有2个小问题👇🏻
- 键盘弹出时,会遮挡底部的功能区域
- 设置页的列表滑动时,键盘无法自动收起
遮挡问题优化
- 对
NewSettingViewController
进行HOOK
,对键盘的通知
进行监听和销毁
%hook NewSettingViewController
- (void)viewDidLoad{
%orig;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
%end
- 键盘弹出
%new
- (void)keyboardWillShow:(NSNotification *)notification {
CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
CGSize viewSize = self.view.frame.size;
self.view.frame = CGRectMake(0, -keyboardSize.height, viewSize.width, viewSize.height);
}
- 键盘收起
%new
- (void)keyboardWillHide:(NSNotification *)notification {
CGSize viewSize = self.view.frame.size;
self.view.frame = CGRectMake(0, 0, viewSize.width, viewSize.height);
}
列表滚动收起键盘
viewDidLoad
增加UITableView.keyboardDismissMode
属性的设置👇🏻
- (void)viewDidLoad{
%orig;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
WCTableViewManager *m_tableViewMgr = MSHookIvar<WCTableViewManager *>(self, "m_tableViewMgr");
[MSHookIvar<UITableView *>(m_tableViewMgr, "_tableView") setKeyboardDismissMode:UIScrollViewKeyboardDismissModeOnDrag];
}
最终的效果👇🏻