├── .gitignore ├── Makefile ├── Others ├── ScreenShots │ ├── Setting-01.jpg │ ├── Setting-02.jpg │ ├── Setting-03.png │ ├── Setting-cheat.jpg │ ├── WeChatPlugin.jpg │ ├── dice.png │ └── jsb.png ├── autoInsertDylib.sh ├── insert_dylib ├── libsubstrate.dylib ├── popup_close_btn.png └── robot.dylib ├── README.md ├── control ├── robot.plist └── src ├── EmoticonGameCheat.h ├── EmoticonGameCheat.m ├── TKChatRoomSensitiveViewController.h ├── TKChatRoomSensitiveViewController.m ├── TKEditViewController.h ├── TKEditViewController.m ├── TKMultiSelectContactsViewController.h ├── TKMultiSelectContactsViewController.m ├── TKRobotConfig.h ├── TKRobotConfig.m ├── TKSettingViewController.h ├── TKSettingViewController.m ├── TKToast.h ├── TKToast.m ├── Tweak.xm ├── UIColor+Extend.h ├── UIColor+Extend.m ├── UIScreen+Extend.h ├── UIScreen+Extend.m ├── UIView+Layout.h ├── UIView+Layout.m ├── WeChatRobot.h ├── zhFullView.h ├── zhFullView.m ├── zhIconLabel.h ├── zhIconLabel.m ├── zhPopupController.h └── zhPopupController.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | .DS_Store 4 | .theos 5 | obj/ 6 | packages/ 7 | 8 | # CocoaPods 9 | # 10 | # We recommend against adding the Pods directory to your .gitignore. However 11 | # you should judge for yourself, the pros and cons are mentioned at: 12 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | THEOS_DEVICE_IP = localhost 2 | THEOS_DEVICE_PORT = 2222 3 | ARCHS = armv7 arm64 4 | TARGET = iphone:latest:7.0 5 | 6 | SRC = $(wildcard src/*.m) 7 | 8 | include $(THEOS)/makefiles/common.mk 9 | 10 | TWEAK_NAME = robot 11 | robot_FILES = $(wildcard src/*.m) src/Tweak.xm 12 | 13 | include $(THEOS_MAKE_PATH)/tweak.mk 14 | 15 | after-install:: 16 | install.exec "killall -9 WeChat" 17 | -------------------------------------------------------------------------------- /Others/ScreenShots/Setting-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doio/TKkk-iOSer-WeChatPlugin-iOS/93ab6f269919d977affb89b2d6002621398ae25f/Others/ScreenShots/Setting-01.jpg -------------------------------------------------------------------------------- /Others/ScreenShots/Setting-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doio/TKkk-iOSer-WeChatPlugin-iOS/93ab6f269919d977affb89b2d6002621398ae25f/Others/ScreenShots/Setting-02.jpg -------------------------------------------------------------------------------- /Others/ScreenShots/Setting-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doio/TKkk-iOSer-WeChatPlugin-iOS/93ab6f269919d977affb89b2d6002621398ae25f/Others/ScreenShots/Setting-03.png -------------------------------------------------------------------------------- /Others/ScreenShots/Setting-cheat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doio/TKkk-iOSer-WeChatPlugin-iOS/93ab6f269919d977affb89b2d6002621398ae25f/Others/ScreenShots/Setting-cheat.jpg -------------------------------------------------------------------------------- /Others/ScreenShots/WeChatPlugin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doio/TKkk-iOSer-WeChatPlugin-iOS/93ab6f269919d977affb89b2d6002621398ae25f/Others/ScreenShots/WeChatPlugin.jpg -------------------------------------------------------------------------------- /Others/ScreenShots/dice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doio/TKkk-iOSer-WeChatPlugin-iOS/93ab6f269919d977affb89b2d6002621398ae25f/Others/ScreenShots/dice.png -------------------------------------------------------------------------------- /Others/ScreenShots/jsb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doio/TKkk-iOSer-WeChatPlugin-iOS/93ab6f269919d977affb89b2d6002621398ae25f/Others/ScreenShots/jsb.png -------------------------------------------------------------------------------- /Others/autoInsertDylib.sh: -------------------------------------------------------------------------------- 1 | # !/bin/bash 2 | # 使用 ./autoInsertDylib.sh ipa文件路径 dylib文件路径 eg: ./autoInsertDylib.sh wechat.ipa robot.dylib 3 | 4 | shell_path="$(dirname "$0")" 5 | 6 | SOURCEIPA="$1" 7 | DYLIB="$2" 8 | LIBSUBSTRATE="${shell_path}/libsubstrate.dylib" 9 | 10 | temp_dir="${shell_path}/tweak-temp-tk" 11 | ipa_bundle_path="${temp_dir}/${SOURCEIPA##*/}" 12 | libsubstrate_path="${temp_dir}/${LIBSUBSTRATE##*/}" 13 | dylib_path="${temp_dir}/${DYLIB##*/}" 14 | 15 | framework_path="${app_bundle_path}/${framework_name}.framework" 16 | rm -rf ${shell_path}/../Products/* 17 | mkdir ${shell_path}/../Products/ 18 | if [ ! -d ${temp_dir} ]; then 19 | # echo "创建 ${temp_dir}" 20 | mkdir ${temp_dir} 21 | fi 22 | 23 | cp "$SOURCEIPA" "$DYLIB" "$LIBSUBSTRATE" ${temp_dir} 24 | 25 | # cd "$shell_path" 26 | 27 | echo "开始注入dylib >>> \n\n\n" 28 | # echo "正将" ${SOURCEIPA##*/} ${DYLIB##*/} ${LIBSUBSTRATE##*/} "拷贝至/tweak-temp-tk" 29 | 30 | otool -L ${dylib_path} > ${temp_dir}/depend.log 31 | grep "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate" ${temp_dir}/depend.log >${temp_dir}/grep_result.log 32 | if [ $? -eq 0 ]; then 33 | # echo "发现有 ${DYLIB##*/} 依赖于 CydiaSubstrate, 正将其替换为 libsubstrate" 34 | install_name_tool -change /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate @loader_path/libsubstrate.dylib ${dylib_path} 35 | 36 | # else 37 | # echo "没有发现依赖于CydiaSubstrate" 38 | fi 39 | 40 | # echo "解压" ${SOURCEIPA##*/} 41 | 42 | unzip -qo "$ipa_bundle_path" -d ${shell_path}/extracted 43 | 44 | APPLICATION=$(ls "${shell_path}/extracted/Payload/") 45 | app_path="${shell_path}/extracted/Payload/${APPLICATION}" 46 | 47 | # cp -R ${app_path} ./ 48 | 49 | # rm -rf ~/Desktop/temp/extracted/Payload/$APPLICATION/*Watch* 50 | cp "${shell_path}/popup_close_btn.png" ${app_path} 51 | cp ${dylib_path} ${libsubstrate_path} ${app_path} 52 | 53 | # echo "删除" ${APPLICATION##*/} "中 watch 相关文件" 54 | 55 | rm -rf ${app_path}/*watch* ${app_path}/*Watch* 56 | 57 | # echo "注入" ${DYLIB##*/} "到" $APPLICATION 58 | ${shell_path}/insert_dylib @executable_path/${DYLIB##*/} ${app_path}/${APPLICATION%.*} > ${temp_dir}/insert_dylib.log 59 | 60 | echo "注入成功 !!!" 61 | 62 | rm -rf ${app_path}/${APPLICATION%.*} 63 | mv ${app_path}/${APPLICATION%.*}_patched ${app_path}/${APPLICATION%.*} 64 | 65 | cp -R ${app_path} ${shell_path}/../Products/${APPLICATION} 66 | 67 | # echo "删除临时文件 >>>" 68 | rm -rf ${shell_path}/extracted ${temp_dir} 69 | 70 | # echo "打开 tweak-temp-tk 文件夹" 71 | open ${shell_path}/../Products/ 72 | # open /Applications/iOS\ App\ Signer.app 73 | -------------------------------------------------------------------------------- /Others/insert_dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doio/TKkk-iOSer-WeChatPlugin-iOS/93ab6f269919d977affb89b2d6002621398ae25f/Others/insert_dylib -------------------------------------------------------------------------------- /Others/libsubstrate.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doio/TKkk-iOSer-WeChatPlugin-iOS/93ab6f269919d977affb89b2d6002621398ae25f/Others/libsubstrate.dylib -------------------------------------------------------------------------------- /Others/popup_close_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doio/TKkk-iOSer-WeChatPlugin-iOS/93ab6f269919d977affb89b2d6002621398ae25f/Others/popup_close_btn.png -------------------------------------------------------------------------------- /Others/robot.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doio/TKkk-iOSer-WeChatPlugin-iOS/93ab6f269919d977affb89b2d6002621398ae25f/Others/robot.dylib -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## WeChatPlugin-iOS 2 | 3 | 微信小助手-iOS版 v1.0.0 4 | 5 | **年底了,微信加强了检测,如有弹窗警告的,请暂停使用** 6 | **年底了,微信加强了检测,如有弹窗警告的,请暂停使用** 7 | **年底了,微信加强了检测,如有弹窗警告的,请暂停使用(重要的事说三遍)** 8 | 9 | 10 | 11 | **mac OS 版请戳→_→ [WeChatPlugin-MacOS](https://github.com/TKkk-iOSer/WeChatPlugin-MacOS)** 12 | 13 | --- 14 | 15 | ### 功能 16 | - [x] 游戏作弊(石头剪刀布、骰子) 17 | - [x] 修改微信运动步数 18 | - [x] 消息防撤回 19 | - [x] 自动通过好友请求 20 | - [x] 请求自动通过后发送欢迎语 21 | - [x] 消息自动回复(完全匹配触发) 22 | - [x] 统一设置群公告 23 | - [x] 敏感词自动踢人 24 | - [x] 入群欢迎语 25 | - [x] 群消息自动回复 26 | 27 | **统一设置群公告、敏感词自动踢人、入群欢迎语 仅对自己创建的群有效** 28 | 29 | --- 30 | 31 | ### 截图 32 | 33 | 设置界面: 34 | 35 | 36 | 37 | 38 | --- 39 | 40 | ### 安装 41 | 42 | ~~详细安装方法可参考[iOS 逆向 - 微信 helloWorld](http://www.tkkk.fun/2017/03/19/%E9%80%86%E5%90%91-%E5%BE%AE%E4%BF%A1helloWorld/)~~ 43 | 44 | #### 0. 准备 45 | 46 | * [ios-app-signer](https://github.com/DanTheMan827/ios-app-signer) (重签名) 47 | * Xcode 或者 PP助手 (安装ipa) 48 | * iOS 证书(可用Xcode生成临时开发证书,然而只能用7天) 49 | * ipa文件(可直接下载下面百度云的app文件,如果重新注入动态库,请于PP助手下载**越狱版**的微信) 50 | * [theos](https://github.com/theos/theos)(编写tweak工具,若不修改源码则不需要该工具) 51 | 52 | 53 | #### 1. 生成临时证书(~~若有证书忽略该步骤~~) 54 | 使用 Xcode 创建一个 iOS 的 Project,选择方框1 的开发者,并用真机运行(~~使证书导入到 iPhone~~)。 55 | ![Xcode.png](http://upload-images.jianshu.io/upload_images/965383-e730b53fe95ab166.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 56 | 57 | #### 2. 生成注入的app文件 58 | 59 | * 可直接通过百度云下载 60 | 61 | 链接: https://pan.baidu.com/s/1o7UBqL8 密码: f71u (微信版本 6.6.0 新版自动回复失效) 62 | 63 | 链接: https://pan.baidu.com/s/1c120oww 密码: 95bx(微信版本为6.5.16 无作弊功能) 64 | 65 | * 若想修改源码,生成新的dylib,可在修改之后执行`make`,之后拷贝生成的dylib(~~路径为`./theos/obj/debug/robot.dylib`~~),最后执行 `./Others/autoInsertDylib.sh ipa文件路径 dylib文件路径` 即可获得注入dylib的app文件。 66 | 67 | #### 3. 使用`iOS App Signer.app` 进行重签名 68 | 69 | ![iOS App Signer.app.png](http://upload-images.jianshu.io/upload_images/965383-c3daf12a77c8204b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 70 | 71 | * `Input File` 选择上面的app文件。 72 | * `Signing Certificate` 选择第一步中的开发者账号(方框3) 73 | * `Provisioning Profile` 选择第一步中的`bundle id`(方框2) 74 | 75 | 点击start获得重签名的`ipa`文件。 76 | 77 | #### 4. 使用 Xcode 安装 ipa 78 | 79 | 打开Xcode-Window-Devices,将重签名的ipa文件拖到方框中,或者点击`+`添加ipa,即可完成。 80 | 81 | ![Device.png](http://upload-images.jianshu.io/upload_images/965383-abb8cf54a6acabbe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 82 | 83 | #### 5. iOS权限设置 84 | 85 | 打开`设置-通用-描述文件与设备管理`,信任证列表中的开发者应用。 86 | 87 | --- 88 | 89 | ### 依赖 90 | * [insert_dylib](https://github.com/gengjf/insert_dylib)(~~已在./Others/~~) 91 | * [ios-app-signer](https://github.com/DanTheMan827/ios-app-signer) (~~文件太大,请自行下载编译~~) 92 | * [theos](https://github.com/theos/theos) 93 | * [zhPopupController](https://github.com/snail-z/zhPopupController) 94 | 95 | --- 96 | 97 | ### 免责声明 98 | 本项目旨在学习 iOS 逆向的一点实践,不可使用于商业和个人其他意图。若使用不当,均由个人承担。 99 | 100 | 101 | --- 102 | 103 | #### 听说你想请我喝下午茶?😏 104 | 105 |     106 | 107 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: com.tk.robot 2 | Name: robot 3 | Depends: mobilesubstrate 4 | Version: 0.0.1 5 | Architecture: iphoneos-arm 6 | Description: An awesome MobileSubstrate tweak! 7 | Maintainer: tk 8 | Author: tk 9 | Section: Tweaks 10 | -------------------------------------------------------------------------------- /robot.plist: -------------------------------------------------------------------------------- 1 | { Filter = { Bundles = ( "com.tencent.xin" ); }; } 2 | -------------------------------------------------------------------------------- /src/EmoticonGameCheat.h: -------------------------------------------------------------------------------- 1 | // 2 | // EmoticonGameCheat.h 3 | // 4 | // Created by lanjuzi on 2017/9/22. 5 | // Copyright © 2017年 lanjuzi. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "zhPopupController.h" 10 | #import "zhFullView.h" 11 | #import "zhIconLabel.h" 12 | 13 | @interface EmoticonGameCheat : NSObject 14 | 15 | + (void)showEoticonCheat:(NSInteger)uiGameType callback:(void (^)(NSInteger random))callback; 16 | 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /src/EmoticonGameCheat.m: -------------------------------------------------------------------------------- 1 | // 2 | // EmoticonGameCheat.m 3 | // 4 | // Created by lanjuzi on 2017/9/22. 5 | // Copyright © 2017年 lanjuzi. All rights reserved. 6 | // 7 | 8 | #import "EmoticonGameCheat.h" 9 | 10 | @implementation EmoticonGameCheat 11 | 12 | + (void)showEoticonCheat:(NSInteger)uiGameType callback:(void (^)(NSInteger random))callback { 13 | NSArray *array; 14 | if (uiGameType == 1) { 15 | array = @[@[@1, @"剪刀"], @[@2, @"石头"], @[@3, @"布"]]; 16 | } else if (uiGameType == 2) { 17 | array = @[@[@4, @"1"], @[@5, @"2"], @[@6, @"3"], @[@7, @"4"], @[@8, @"5"], @[@9, @"6"]]; 18 | } else { 19 | NSLog(@"不支持的 uiGameType 类型: %ld", (long)uiGameType); 20 | return; 21 | } 22 | 23 | CGRect frame = [[UIApplication sharedApplication] keyWindow].rootViewController.view.frame; 24 | zhFullView *fullView = [[zhFullView alloc] initWithFrame:frame andRows:uiGameType]; 25 | 26 | NSMutableArray *models = [NSMutableArray arrayWithCapacity:array.count]; 27 | for (NSArray *arr in array) { 28 | zhIconLabelModel *item = [zhIconLabelModel new]; 29 | 30 | if ([arr[0] intValue] > 0) { 31 | if ([arr[0] intValue] > 3) { 32 | NSString *imagePath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"dice_%@", arr[1]] ofType:@"pic"]; 33 | item.icon = [UIImage imageWithContentsOfFile:imagePath]; 34 | } else { 35 | NSString *jsb; 36 | switch ([arr[0] intValue]) { 37 | case 1: 38 | jsb = @"JSB_J"; 39 | break; 40 | case 2: 41 | jsb = @"JSB_S"; 42 | break; 43 | case 3: 44 | jsb = @"JSB_B"; 45 | break; 46 | default: 47 | jsb = @"JSB_S"; 48 | break; 49 | } 50 | NSString *imagePath = [[NSBundle mainBundle] pathForResource:jsb ofType:@"pic"]; 51 | item.icon = [UIImage imageWithContentsOfFile:imagePath]; 52 | } 53 | } 54 | item.text = arr[1]; 55 | [models addObject:item]; 56 | } 57 | fullView.models = models; 58 | 59 | fullView.didClickFullView = ^(zhFullView * _Nonnull fullView) { 60 | [self.zh_popupController dismiss]; 61 | }; 62 | 63 | fullView.didClickItems = ^(zhFullView *fullView, NSInteger index) { 64 | self.zh_popupController.didDismiss = ^(zhPopupController * _Nonnull popupController) { 65 | callback([array[index][0] intValue]); 66 | }; 67 | 68 | [fullView endAnimationsCompletion:^(zhFullView *fullView) { 69 | [self.zh_popupController dismiss]; 70 | }]; 71 | }; 72 | 73 | self.zh_popupController = [zhPopupController popupControllerWithMaskType:zhPopupMaskTypeWhiteBlur]; 74 | self.zh_popupController.allowPan = YES; 75 | [self.zh_popupController presentContentView:fullView]; 76 | } 77 | 78 | @end 79 | 80 | -------------------------------------------------------------------------------- /src/TKChatRoomSensitiveViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TKChatRoomSensitiveViewController.h 3 | // WeChatRobot 4 | // 5 | // Created by TK on 2017/4/7. 6 | // Copyright © 2017年 TK. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TKChatRoomSensitiveViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /src/TKChatRoomSensitiveViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TKChatRoomSensitiveViewController.m 3 | // WeChatRobot 4 | // 5 | // Created by TK on 2017/4/7. 6 | // Copyright © 2017年 TK. All rights reserved. 7 | // 8 | 9 | #import "TKChatRoomSensitiveViewController.h" 10 | #import "WeChatRobot.h" 11 | 12 | @interface TKChatRoomSensitiveViewController () 13 | 14 | @property (nonatomic, strong) MMTableViewInfo *tableViewInfo; 15 | @property (nonatomic, strong) NSMutableArray *chatRoomSensitiveArray; 16 | 17 | @end 18 | 19 | @implementation TKChatRoomSensitiveViewController 20 | 21 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { 22 | if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { 23 | // 增加对iPhone X的屏幕适配 24 | CGRect winSize = [UIScreen mainScreen].bounds; 25 | if (winSize.size.height == 812) { 26 | winSize.size.height -= 88; 27 | winSize.origin.y = 88; 28 | } 29 | _tableViewInfo = [[objc_getClass("MMTableViewInfo") alloc] initWithFrame:winSize style:UITableViewStyleGrouped]; 30 | _tableViewInfo.delegate = self; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)viewDidLoad { 36 | [super viewDidLoad]; 37 | 38 | [self initData]; 39 | [self reloadTableData]; 40 | [self initTitle]; 41 | MMTableView *tableView = [self.tableViewInfo getTableView]; 42 | 43 | [self.view addSubview:tableView]; 44 | } 45 | 46 | - (void)initData { 47 | self.chatRoomSensitiveArray = ({ 48 | NSMutableArray *array = [[TKRobotConfig sharedConfig] chatRoomSensitiveArray]; 49 | NSMutableArray *copyArray; 50 | if (array == nil) { 51 | copyArray = [NSMutableArray array]; 52 | } else { 53 | copyArray = [NSMutableArray arrayWithArray:array]; 54 | } 55 | 56 | copyArray; 57 | }); 58 | } 59 | 60 | - (void)initTitle { 61 | self.title = @"敏感词名单"; 62 | self.navigationItem.leftBarButtonItem = [objc_getClass("MMUICommonUtil") getBarButtonWithTitle:@"返回" target:self action:@selector(onBack) style:3]; 63 | [self.navigationController.navigationBar setTitleTextAttributes:@{NSFontAttributeName: [UIFont boldSystemFontOfSize:18.0]}]; 64 | } 65 | 66 | - (void)onBack { 67 | [self.navigationController popViewControllerAnimated:YES]; 68 | } 69 | 70 | - (void)reloadTableData { 71 | [self.tableViewInfo clearAllSection]; 72 | [self addHeaderSection]; 73 | if (self.chatRoomSensitiveArray.count > 0) { 74 | [self addSensitiveTextSection]; 75 | } 76 | 77 | MMTableView *tableView = [self.tableViewInfo getTableView]; 78 | [tableView reloadData]; 79 | } 80 | 81 | - (void)addHeaderSection { 82 | MMTableViewSectionInfo *headerSectionInfo = [objc_getClass("MMTableViewSectionInfo") sectionInfoHeader:nil Footer:nil]; 83 | [headerSectionInfo addCell:[self createSensitiveSwitchCell]]; 84 | [headerSectionInfo addCell:[self createNewSensitiveCell]]; 85 | [self.tableViewInfo addSection:headerSectionInfo]; 86 | } 87 | 88 | - (void)addSensitiveTextSection { 89 | MMTableViewSectionInfo *sensitiveTextSection = [objc_getClass("MMTableViewSectionInfo") sectionInfoHeader:@"敏感词" Footer:nil]; 90 | 91 | [self.chatRoomSensitiveArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 92 | [sensitiveTextSection addCell:[self createSensitiveCellWithIndex:idx andText:obj]]; 93 | }]; 94 | 95 | [self.tableViewInfo addSection:sensitiveTextSection]; 96 | } 97 | 98 | - (MMTableViewCellInfo *)createSensitiveSwitchCell { 99 | BOOL chatRoomSensitiveEnable = [[TKRobotConfig sharedConfig] chatRoomSensitiveEnable]; 100 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") switchCellForSel:@selector(settingSensitiveSwitch:)target:self title:@"开启敏感词检测" on:chatRoomSensitiveEnable]; 101 | 102 | return cellInfo; 103 | } 104 | 105 | - (MMTableViewCellInfo *)createNewSensitiveCell { 106 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") normalCellForSel:@selector(addSensitiveAction) target:self title:@"新增敏感词" accessoryType:1]; 107 | 108 | return cellInfo; 109 | } 110 | 111 | - (MMTableViewCellInfo *)createSensitiveCellWithIndex:(int)index andText:(NSString *)text { 112 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") normalCellForSel:@selector(editSensitiveText:) target:self title:text accessoryType:1]; 113 | cellInfo.userInfo = @{@"index" : @(index),@"text":text}; 114 | cellInfo.editStyle = UITableViewCellEditingStyleDelete; 115 | 116 | return cellInfo; 117 | } 118 | 119 | - (void)settingSensitiveSwitch:(UISwitch *)arg { 120 | [[TKRobotConfig sharedConfig] setChatRoomSensitiveEnable:arg.on]; 121 | [self reloadTableData]; 122 | } 123 | 124 | - (void)addSensitiveAction { 125 | TKEditViewController *editVC = [[TKEditViewController alloc] init]; 126 | editVC.title = @"新增敏感词"; 127 | editVC.placeholder = @"当管理的群中有用户发了跟敏感词一致的内容,\n则自动将其提出该群"; 128 | [editVC setEndEditing:^(NSString *text) { 129 | __block BOOL isRepetition = NO; 130 | [self.chatRoomSensitiveArray enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) { 131 | if ([obj isEqualToString:text]) { 132 | isRepetition = YES; 133 | *stop= YES; 134 | } 135 | }]; 136 | 137 | if (isRepetition) { 138 | [TKToast toast:@"敏感词重复"]; 139 | return; 140 | } 141 | 142 | [self.chatRoomSensitiveArray addObject:text]; 143 | [[TKRobotConfig sharedConfig] setChatRoomSensitiveArray:self.chatRoomSensitiveArray]; 144 | [self reloadTableData]; 145 | }]; 146 | 147 | [self.navigationController PushViewController:editVC animated:YES]; 148 | } 149 | 150 | - (void)editSensitiveText:(MMTableViewCellInfo *)agr { 151 | NSInteger index = [agr.userInfo[@"index"] integerValue]; 152 | TKEditViewController *editVC = [[TKEditViewController alloc] init]; 153 | editVC.title = @"编辑敏感词"; 154 | editVC.placeholder = @"当管理的群中有用户发了跟敏感词一致的内容,\n则自动将其提出该群"; 155 | editVC.text = agr.userInfo[@"text"]; 156 | [editVC setEndEditing:^(NSString *text) { 157 | __block BOOL isRepetition = NO; 158 | [self.chatRoomSensitiveArray enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) { 159 | if ([obj isEqualToString:text]) { 160 | isRepetition = YES; 161 | *stop= YES; 162 | } 163 | }]; 164 | 165 | if (isRepetition) { 166 | [TKToast toast:@"敏感词重复"]; 167 | return; 168 | } 169 | [self.chatRoomSensitiveArray replaceObjectAtIndex:index withObject:text]; 170 | [[TKRobotConfig sharedConfig] setChatRoomSensitiveArray:self.chatRoomSensitiveArray]; 171 | [self reloadTableData]; 172 | }]; 173 | 174 | [self.navigationController PushViewController:editVC animated:YES]; 175 | } 176 | 177 | - (void)commitEditingForRowAtIndexPath:(NSIndexPath *)arg1 Cell:(MMTableViewCellInfo *)arg2 { 178 | [self.chatRoomSensitiveArray removeObjectAtIndex:arg1.row]; 179 | [[TKRobotConfig sharedConfig] setChatRoomSensitiveArray:self.chatRoomSensitiveArray]; 180 | MMTableViewSectionInfo *sensitiveTextSection = [self.tableViewInfo getSectionAt:1]; 181 | [sensitiveTextSection removeCellAt:arg1.row]; 182 | [self reloadTableData]; 183 | } 184 | 185 | @end 186 | -------------------------------------------------------------------------------- /src/TKEditViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TKEditViewController.h 3 | // WeChatRobot 4 | // 5 | // Created by TK on 2017/3/31. 6 | // Copyright © 2017年 TK. All rights reserved. 7 | // 8 | 9 | #import "WeChatRobot.h" 10 | 11 | @interface TKEditViewController : UIViewController 12 | 13 | @property (nonatomic, copy) NSString *text; 14 | @property (nonatomic, copy) NSString *placeholder; 15 | @property (nonatomic, copy) void (^endEditing)(NSString *text); 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /src/TKEditViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TKEditViewController.m 3 | // WeChatRobot 4 | // 5 | // Created by TK on 2017/3/31. 6 | // Copyright © 2017年 TK. All rights reserved. 7 | // 8 | 9 | #import "TKEditViewController.h" 10 | 11 | @interface TKEditViewController () 12 | 13 | @property (nonatomic, strong) MMTextView *textView; 14 | 15 | @end 16 | 17 | @implementation TKEditViewController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | 22 | [self initNav]; 23 | [self initSubviews]; 24 | [self setup]; 25 | } 26 | 27 | - (void)initNav { 28 | self.navigationItem.leftBarButtonItem = [objc_getClass("MMUICommonUtil") getBarButtonWithTitle:@"返回" target:self action:@selector(onBack) style:3]; 29 | self.navigationItem.rightBarButtonItem = [objc_getClass("MMUICommonUtil") getBarButtonWithTitle:@"完成" target:self action:@selector(onFinfsh) style:4]; 30 | [self.navigationController.navigationBar setTitleTextAttributes:@{NSFontAttributeName: [UIFont boldSystemFontOfSize:18.0]}]; 31 | } 32 | 33 | - (void)initSubviews { 34 | self.textView = ({ 35 | MMTextView *tv = [[objc_getClass("MMTextView") alloc] initWithFrame:CGRectMake(7, 0, SCREEN_WIDTH - 14, SCREEN_HEIGHT)]; 36 | tv.font = [UIFont systemFontOfSize:16]; 37 | 38 | tv; 39 | }); 40 | 41 | [self.view addSubview:self.textView]; 42 | } 43 | 44 | - (void)setup { 45 | self.textView.text = self.text; 46 | self.textView.placeholder = self.placeholder; 47 | self.view.backgroundColor = [UIColor whiteColor]; 48 | 49 | [self.textView becomeFirstResponder]; 50 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBoardChange:) name:UIKeyboardWillChangeFrameNotification object:nil]; 51 | } 52 | 53 | - (void)keyBoardChange:(NSNotification *)note { 54 | CGRect keyboardFrame = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; 55 | 56 | if (keyboardFrame.origin.y < SCREEN_HEIGHT) { 57 | self.textView.frame = CGRectMake(7, 0, SCREEN_WIDTH - 14, SCREEN_HEIGHT - keyboardFrame.size.height); 58 | } else { 59 | self.textView.frame = CGRectMake(7, 0, SCREEN_WIDTH - 14, SCREEN_HEIGHT); 60 | } 61 | } 62 | 63 | - (void)onBack { 64 | if (self.textView.text.length > 0 && ![self.textView.text isEqualToString:self.text]) { 65 | [self alertControllerWithTitle:@"确定不保存返回么" 66 | message:nil 67 | leftBlock:nil 68 | rightBlock:^() { 69 | [self.view endEditing:YES]; 70 | [self.navigationController popViewControllerAnimated:YES]; 71 | }]; 72 | } else { 73 | [self.view endEditing:YES]; 74 | [self.navigationController popViewControllerAnimated:YES]; 75 | } 76 | } 77 | 78 | - (void)onFinfsh { 79 | [self.view endEditing:YES]; 80 | if (self.endEditing) { 81 | self.endEditing(self.textView.text); 82 | } 83 | [self.navigationController popViewControllerAnimated:YES]; 84 | } 85 | 86 | - (void)alertControllerWithTitle:(NSString *)title message:(NSString *)message leftBlock:(void (^)(void))leftBlk rightBlock:(void (^)(void))rightBlk { 87 | UIAlertController *alertController = ({ 88 | UIAlertController *alert = [UIAlertController 89 | alertControllerWithTitle:title 90 | message:nil 91 | preferredStyle:UIAlertControllerStyleAlert]; 92 | 93 | [alert addAction:[UIAlertAction actionWithTitle:@"取消" 94 | style:UIAlertActionStyleCancel 95 | handler:^(UIAlertAction * _Nonnull action) { 96 | if (leftBlk) { 97 | leftBlk(); 98 | } 99 | }]]; 100 | 101 | [alert addAction:[UIAlertAction actionWithTitle:@"确定" 102 | style:UIAlertActionStyleDefault 103 | handler:^(UIAlertAction * _Nonnull action) { 104 | if (rightBlk) { 105 | rightBlk(); 106 | } 107 | }]]; 108 | 109 | alert; 110 | }); 111 | 112 | [self presentViewController:alertController animated:YES completion:nil]; 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /src/TKMultiSelectContactsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TKMultiSelectContactsViewController.h 3 | // WeChatRobot 4 | // 5 | // Created by TK on 2017/4/5. 6 | // Copyright © 2017年 TK. All rights reserved. 7 | // 8 | 9 | #import "WeChatRobot.h" 10 | 11 | @interface TKMultiSelectContactsViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /src/TKMultiSelectContactsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TKMultiSelectContactsViewController.m 3 | // WeChatRobot 4 | // 5 | // Created by TK on 2017/4/5. 6 | // Copyright © 2017年 TK. All rights reserved. 7 | // 8 | 9 | #import "TKMultiSelectContactsViewController.h" 10 | 11 | @interface TKMultiSelectContactsViewController () 12 | 13 | @property (nonatomic, strong) MMLoadingView *loadingView; 14 | @property (nonatomic, strong) ContactSelectView *selectView; 15 | @property (nonatomic, strong) UIButton *nextBtn; 16 | 17 | @end 18 | 19 | @implementation TKMultiSelectContactsViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | 24 | [self initNav]; 25 | [self initView]; 26 | [self setup]; 27 | } 28 | 29 | - (void)setup { 30 | self.view.backgroundColor = [UIColor whiteColor]; 31 | 32 | [self.loadingView startLoading]; 33 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 34 | [self.loadingView stopLoading]; 35 | [self filterOwnChatRoom]; 36 | [self.selectView setHidden:NO]; 37 | }); 38 | } 39 | 40 | - (void)initNav { 41 | self.navigationItem.leftBarButtonItem = [objc_getClass("MMUICommonUtil") getBarButtonWithTitle:@"返回" target:self action:@selector(onBack) style:3]; 42 | self.navigationItem.rightBarButtonItem = [objc_getClass("MMUICommonUtil") getBarButtonWithTitle:@"全选" target:self action:@selector(onAllSelect) style:4]; 43 | [self.navigationController.navigationBar setTitleTextAttributes:@{NSFontAttributeName: [UIFont boldSystemFontOfSize:18.0]}]; 44 | } 45 | 46 | - (void)initView { 47 | self.loadingView = ({ 48 | MMLoadingView *loadingView = [[objc_getClass("MMLoadingView") alloc] init]; 49 | [loadingView.m_label setText:@"加载中…"]; 50 | [loadingView setM_bIgnoringInteractionEventsWhenLoading:YES]; 51 | [loadingView setFitFrame:1]; 52 | 53 | loadingView; 54 | }); 55 | 56 | self.selectView = ({ 57 | CGRect frame = CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT - 45); 58 | ContactSelectView *selectV = [[objc_getClass("ContactSelectView") alloc] initWithFrame:frame]; 59 | [selectV setM_uiGroupScene:5]; 60 | [selectV setM_bMultiSelect:1]; 61 | [selectV setM_dicExistContact:nil]; 62 | [selectV setM_dicMultiSelect:nil]; 63 | [selectV initData:5]; 64 | [selectV initView]; 65 | [selectV setHidden:YES]; 66 | 67 | selectV; 68 | }); 69 | 70 | self.nextBtn = ({ 71 | UIButton *btn = [[UIButton alloc] init]; 72 | btn.frame = CGRectMake(0, SCREEN_HEIGHT - 45, SCREEN_WIDTH, 45); 73 | [btn setTitle:@"下一步" forState:UIControlStateNormal]; 74 | [btn setBackgroundColor: [UIColor colorWithRed: 0x10/255.0 green:0xc4/255.0 blue:0xd1/255.0 alpha:1]]; 75 | [btn addTarget:self action:@selector(onNext) forControlEvents:UIControlEventTouchUpInside]; 76 | 77 | btn; 78 | }); 79 | 80 | [self.view addSubview:self.selectView]; 81 | [self.view addSubview:self.nextBtn]; 82 | [self.view addSubview:self.loadingView]; 83 | } 84 | 85 | - (void)filterOwnChatRoom { 86 | ContactsDataLogic *contactDataLogic = [self.selectView valueForKey:@"m_contactsDataLogic"]; 87 | NSString *chatRoomKey = [[contactDataLogic getKeysArray] firstObject]; 88 | NSArray *chatRoomArray = [contactDataLogic getContactsArrayWith:chatRoomKey]; 89 | 90 | CContactMgr *contactMgr = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("CContactMgr")]; 91 | CContact *selfContact = [contactMgr getSelfContact]; 92 | NSMutableArray *owmChatRoom = [NSMutableArray array]; 93 | [chatRoomArray enumerateObjectsUsingBlock:^(CContact *contact, NSUInteger idx, BOOL * _Nonnull stop) { 94 | if([contact isChatroom] && [selfContact.m_nsUsrName isEqualToString:contact.m_nsOwner]) { 95 | [owmChatRoom addObject:contact]; 96 | } 97 | }]; 98 | NSMutableDictionary *dicAllContacts = [contactDataLogic valueForKey:@"m_dicAllContacts"]; 99 | dicAllContacts[chatRoomKey] = owmChatRoom; 100 | 101 | MMTableView *tableView = [self.selectView valueForKey:@"m_tableView"]; 102 | [tableView reloadData]; 103 | } 104 | 105 | - (void)onBack { 106 | [self.navigationController popViewControllerAnimated:YES]; 107 | } 108 | 109 | - (void)onAllSelect { 110 | ContactsDataLogic *contactDataLogic = [self.selectView valueForKey:@"m_contactsDataLogic"]; 111 | NSString *chatRoomKey = [[contactDataLogic getKeysArray] firstObject]; 112 | NSArray *chatRoomArray = [contactDataLogic getContactsArrayWith:chatRoomKey]; 113 | [chatRoomArray enumerateObjectsUsingBlock:^(CContact *contact, NSUInteger idx, BOOL * _Nonnull stop) { 114 | [self.selectView addSelect:contact]; 115 | }]; 116 | 117 | MMTableView *tableView = [self.selectView valueForKey:@"m_tableView"]; 118 | [tableView reloadData]; 119 | } 120 | 121 | - (void)onNext { 122 | if (self.selectView.m_dicMultiSelect.allKeys.count == 0) { 123 | [TKToast toast:@"至少选择一个群聊"]; 124 | return; 125 | } 126 | 127 | NSArray *chatRoomContacts = self.selectView.m_dicMultiSelect.allValues; 128 | TKEditViewController *editVC = [[TKEditViewController alloc] init]; 129 | editVC.text = [[TKRobotConfig sharedConfig] allChatRoomDescText]; 130 | editVC.title = @"请输入群公告"; 131 | [editVC setEndEditing:^(NSString *text) { 132 | [[TKRobotConfig sharedConfig] setAllChatRoomDescText:text]; 133 | CContactMgr *contactMgr = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("CContactMgr")]; 134 | CContact *selfContact = [contactMgr getSelfContact]; 135 | [chatRoomContacts enumerateObjectsUsingBlock:^(CContact *contact, NSUInteger idx, BOOL * _Nonnull stop) { 136 | if([contact isChatroom] && [selfContact.m_nsUsrName isEqualToString:contact.m_nsOwner]) { 137 | CGroupMgr *groupMgr = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("CGroupMgr")]; 138 | [groupMgr SetChatRoomDesc:contact.m_nsUsrName Desc:text Flag:1]; 139 | } 140 | }]; 141 | }]; 142 | [self.navigationController PushViewController:editVC animated:YES]; 143 | } 144 | 145 | @end 146 | -------------------------------------------------------------------------------- /src/TKRobotConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // TKRobotConfig.h 3 | // WeChatRobot 4 | // 5 | // Created by TK on 2017/3/27. 6 | // Copyright © 2017年 TK. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TKRobotConfig : NSObject 12 | 13 | + (instancetype)sharedConfig; 14 | 15 | /** 游戏作弊 */ 16 | @property (nonatomic, assign) BOOL preventGameCheatEnable; 17 | 18 | /** 微信撤回 */ 19 | @property (nonatomic, assign) BOOL preventRevokeEnable; 20 | 21 | /** 步数设置 */ 22 | @property (nonatomic, assign) BOOL changeStepEnable; 23 | @property (nonatomic, assign) NSInteger deviceStep; /**< 步数设置 */ 24 | 25 | /** 自动确认好友请求 */ 26 | @property (nonatomic, assign) BOOL autoVerifyEnable; 27 | @property (nonatomic, copy) NSString *autoVerifyKeyword; /**< 自动验证关键字 */ 28 | 29 | /** 确认好友请求以后,自动发送欢迎语 */ 30 | @property (nonatomic, assign) BOOL autoWelcomeEnable; 31 | @property (nonatomic, copy) NSString *autoWelcomeText; /**< 好友通过欢迎语 */ 32 | 33 | /** 特定消息自动回复 */ 34 | @property (nonatomic, assign) BOOL autoReplyEnable; 35 | @property (nonatomic, copy) NSString *autoReplyKeyword; /**< 自动回复关键字 */ 36 | @property (nonatomic, copy) NSString *autoReplyText; /**< 自动回复的内容 */ 37 | 38 | /** 群特定消息自动回复 */ 39 | @property (nonatomic, assign) BOOL autoReplyChatRoomEnable; 40 | @property (nonatomic, copy) NSString *autoReplyChatRoomKeyword; /**< 群自动回复关键字 */ 41 | @property (nonatomic, copy) NSString *autoReplyChatRoomText; /**< 群自动回复的内容 */ 42 | 43 | /** 入群欢迎语 */ 44 | @property (nonatomic, assign) BOOL welcomeJoinChatRoomEnable; 45 | @property (nonatomic, copy) NSString *welcomeJoinChatRoomText; /**< 入群欢迎语 */ 46 | 47 | /** 设置群公告 */ 48 | @property (nonatomic, copy) NSString *allChatRoomDescText; /**< 所有的群公告 */ 49 | 50 | /** 设置敏感词 */ 51 | @property (nonatomic, assign) BOOL chatRoomSensitiveEnable; 52 | @property (nonatomic, strong) NSMutableArray *chatRoomSensitiveArray; /**< 群聊敏感词 */ 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /src/TKRobotConfig.m: -------------------------------------------------------------------------------- 1 | // 2 | // TKRobotConfig.m 3 | // WeChatRobot 4 | // 5 | // Created by TK on 2017/3/27. 6 | // Copyright © 2017年 TK. All rights reserved. 7 | // 8 | 9 | #import "TKRobotConfig.h" 10 | 11 | static NSString * const KTKPreventGameCheatEnableKey = @"KTKPreventGameCheatEnableKey"; 12 | static NSString * const KTKPreventRevokeEnableKey = @"KTKPreventRevokeEnableKey"; 13 | static NSString * const KTKChangeStepEnableKey = @"KTKChangeStepEnableKey"; 14 | static NSString * const kTKDeviceStepKey = @"kTKDeviceStepKey"; 15 | static NSString * const KTKAutoVerifyEnableKey = @"kTKAutoVerifyEnableKey"; 16 | static NSString * const kTKAutoVerifyKeywordKey = @"kTKAutoVerifyKeywordKey"; 17 | static NSString * const KTKAutoWelcomeEnableKey = @"KTKAutoWelcomeEnableKey"; 18 | static NSString * const kTKAutoWelcomeTextKey = @"kTKAutoWelcomeTextKey"; 19 | static NSString * const kTKAutoReplyEnableKey = @"kTKAutoReplyEnableKey"; 20 | static NSString * const kTKAutoReplyKeywordKey = @"kTKAutoReplyKeywordKey"; 21 | static NSString * const kTKAutoReplyTextKey = @"kTKAutoReplyTextKey"; 22 | static NSString * const kTKAutoReplyChatRoomEnableKey = @"kTKAutoReplyChatRoomEnableKey"; 23 | static NSString * const kTKAutoReplyChatRoomKeywordKey = @"kTKAutoReplyChatRoomKeywordKey"; 24 | static NSString * const kTKAutoReplyChatRoomTextKey = @"kTKAutoReplyChatRoomTextKey"; 25 | static NSString * const kTKWelcomeJoinChatRoomEnableKey = @"kTKWelcomeJoinChatRoomEnableKey"; 26 | static NSString * const kTKWelcomeJoinChatRoomTextKey = @"kTKWelcomeJoinChatRoomTextKey"; 27 | static NSString * const kTKAllChatRoomDescTextKey = @"kTKAllChatRoomDescTextKey"; 28 | static NSString * const kTKChatRoomSensitiveEnableKey = @"kTKChatRoomSensitiveEnableKey"; 29 | static NSString * const kTKChatRoomSensitiveArrayKey = @"kTKChatRoomSensitiveArrayKey"; 30 | 31 | @implementation TKRobotConfig 32 | 33 | + (instancetype)sharedConfig { 34 | static TKRobotConfig *config = nil; 35 | static dispatch_once_t onceToken; 36 | dispatch_once(&onceToken, ^{ 37 | config = [[TKRobotConfig alloc] init]; 38 | }); 39 | 40 | return config; 41 | } 42 | 43 | - (instancetype)init 44 | { 45 | self = [super init]; 46 | if (self) { 47 | _preventGameCheatEnable = [[NSUserDefaults standardUserDefaults] boolForKey:KTKPreventGameCheatEnableKey]; 48 | _preventRevokeEnable = [[NSUserDefaults standardUserDefaults] boolForKey:KTKPreventRevokeEnableKey]; 49 | _changeStepEnable = [[NSUserDefaults standardUserDefaults] boolForKey:KTKChangeStepEnableKey]; 50 | _deviceStep = [[[NSUserDefaults standardUserDefaults] objectForKey:kTKDeviceStepKey] intValue]; 51 | _autoVerifyEnable = [[NSUserDefaults standardUserDefaults] boolForKey:KTKAutoVerifyEnableKey]; 52 | _autoVerifyKeyword = [[NSUserDefaults standardUserDefaults] objectForKey:kTKAutoVerifyKeywordKey]; 53 | _autoWelcomeEnable = [[NSUserDefaults standardUserDefaults] boolForKey:KTKAutoWelcomeEnableKey]; 54 | _autoWelcomeText = [[NSUserDefaults standardUserDefaults] objectForKey:kTKAutoWelcomeTextKey]; 55 | _autoReplyEnable = [[NSUserDefaults standardUserDefaults] boolForKey:kTKAutoReplyEnableKey]; 56 | _autoReplyKeyword = [[NSUserDefaults standardUserDefaults] objectForKey:kTKAutoReplyKeywordKey]; 57 | _autoReplyText = [[NSUserDefaults standardUserDefaults] objectForKey:kTKAutoReplyTextKey]; 58 | _autoReplyChatRoomEnable = [[NSUserDefaults standardUserDefaults] boolForKey:kTKAutoReplyChatRoomEnableKey]; 59 | _autoReplyChatRoomKeyword = [[NSUserDefaults standardUserDefaults] objectForKey:kTKAutoReplyChatRoomKeywordKey]; 60 | _autoReplyChatRoomText = [[NSUserDefaults standardUserDefaults] objectForKey:kTKAutoReplyChatRoomTextKey]; 61 | _welcomeJoinChatRoomEnable = [[NSUserDefaults standardUserDefaults] boolForKey:kTKWelcomeJoinChatRoomEnableKey]; 62 | _welcomeJoinChatRoomText = [[NSUserDefaults standardUserDefaults] objectForKey:kTKWelcomeJoinChatRoomTextKey]; 63 | _allChatRoomDescText = [[NSUserDefaults standardUserDefaults] objectForKey:kTKAllChatRoomDescTextKey]; 64 | _chatRoomSensitiveEnable = [[NSUserDefaults standardUserDefaults] boolForKey:kTKChatRoomSensitiveEnableKey]; 65 | _chatRoomSensitiveArray = [[NSUserDefaults standardUserDefaults] objectForKey:kTKChatRoomSensitiveArrayKey]; 66 | } 67 | return self; 68 | } 69 | 70 | - (void)setPreventGameCheatEnable:(BOOL)preventGameCheatEnable { 71 | _preventGameCheatEnable = preventGameCheatEnable; 72 | [[NSUserDefaults standardUserDefaults] setBool:preventGameCheatEnable forKey:KTKPreventGameCheatEnableKey]; 73 | [[NSUserDefaults standardUserDefaults] synchronize]; 74 | } 75 | 76 | - (void)setPreventRevokeEnable:(BOOL)preventRevokeEnable { 77 | _preventRevokeEnable = preventRevokeEnable; 78 | [[NSUserDefaults standardUserDefaults] setBool:preventRevokeEnable forKey:KTKPreventRevokeEnableKey]; 79 | [[NSUserDefaults standardUserDefaults] synchronize]; 80 | } 81 | 82 | - (void)setChangeStepEnable:(BOOL)changeStepEnable { 83 | _changeStepEnable = changeStepEnable; 84 | [[NSUserDefaults standardUserDefaults] setBool:changeStepEnable forKey:KTKChangeStepEnableKey]; 85 | [[NSUserDefaults standardUserDefaults] synchronize]; 86 | } 87 | 88 | - (void)setDeviceStep:(NSInteger)deviceStep { 89 | _deviceStep = deviceStep; 90 | [[NSUserDefaults standardUserDefaults] setObject:@(deviceStep) forKey:kTKDeviceStepKey]; 91 | [[NSUserDefaults standardUserDefaults] synchronize]; 92 | } 93 | 94 | - (void)setAutoVerifyEnable:(BOOL)autoVerifyEnable { 95 | _autoVerifyEnable = autoVerifyEnable; 96 | [[NSUserDefaults standardUserDefaults] setBool:autoVerifyEnable forKey:KTKAutoVerifyEnableKey]; 97 | [[NSUserDefaults standardUserDefaults] synchronize]; 98 | } 99 | 100 | - (void)setAutoVerifyKeyword:(NSString *)autoVerifyKeyword { 101 | _autoVerifyKeyword = autoVerifyKeyword; 102 | [[NSUserDefaults standardUserDefaults] setObject:autoVerifyKeyword forKey:kTKAutoVerifyKeywordKey]; 103 | [[NSUserDefaults standardUserDefaults] synchronize]; 104 | } 105 | 106 | - (void)setAutoWelcomeEnable:(BOOL)autoWelcomeEnable { 107 | _autoWelcomeEnable = autoWelcomeEnable; 108 | [[NSUserDefaults standardUserDefaults] setBool:autoWelcomeEnable forKey:KTKAutoWelcomeEnableKey]; 109 | [[NSUserDefaults standardUserDefaults] synchronize]; 110 | } 111 | 112 | - (void)setAutoWelcomeText:(NSString *)autoWelcomeText { 113 | _autoWelcomeText = autoWelcomeText; 114 | [[NSUserDefaults standardUserDefaults] setObject:autoWelcomeText forKey:kTKAutoWelcomeTextKey]; 115 | [[NSUserDefaults standardUserDefaults] synchronize]; 116 | } 117 | 118 | - (void)setAutoReplyEnable:(BOOL)autoReplyEnable { 119 | _autoReplyEnable = autoReplyEnable; 120 | [[NSUserDefaults standardUserDefaults] setBool:autoReplyEnable forKey:kTKAutoReplyEnableKey]; 121 | [[NSUserDefaults standardUserDefaults] synchronize]; 122 | } 123 | 124 | - (void)setAutoReplyKeyword:(NSString *)autoReplyKeyword { 125 | _autoReplyKeyword = autoReplyKeyword; 126 | [[NSUserDefaults standardUserDefaults] setObject:autoReplyKeyword forKey:kTKAutoReplyKeywordKey]; 127 | [[NSUserDefaults standardUserDefaults] synchronize]; 128 | } 129 | 130 | - (void)setAutoReplyText:(NSString *)autoReplyText { 131 | _autoReplyText = autoReplyText; 132 | [[NSUserDefaults standardUserDefaults] setObject:autoReplyText forKey:kTKAutoReplyTextKey]; 133 | [[NSUserDefaults standardUserDefaults] synchronize]; 134 | } 135 | 136 | - (void)setAutoReplyChatRoomEnable:(BOOL)autoReplyChatRoomEnable { 137 | _autoReplyChatRoomEnable = autoReplyChatRoomEnable; 138 | [[NSUserDefaults standardUserDefaults] setBool:autoReplyChatRoomEnable forKey:kTKAutoReplyChatRoomEnableKey]; 139 | [[NSUserDefaults standardUserDefaults] synchronize]; 140 | } 141 | 142 | - (void)setAutoReplyChatRoomKeyword:(NSString *)autoReplyChatRoomKeyword { 143 | _autoReplyChatRoomKeyword = autoReplyChatRoomKeyword; 144 | [[NSUserDefaults standardUserDefaults] setObject:autoReplyChatRoomKeyword forKey:kTKAutoReplyChatRoomKeywordKey]; 145 | [[NSUserDefaults standardUserDefaults] synchronize]; 146 | } 147 | 148 | - (void)setAutoReplyChatRoomText:(NSString *)autoReplyChatRoomText { 149 | _autoReplyChatRoomText = autoReplyChatRoomText; 150 | [[NSUserDefaults standardUserDefaults] setObject:autoReplyChatRoomText forKey:kTKAutoReplyChatRoomTextKey]; 151 | [[NSUserDefaults standardUserDefaults] synchronize]; 152 | } 153 | 154 | - (void)setWelcomeJoinChatRoomEnable:(BOOL)welcomeJoinChatRoomEnable { 155 | _welcomeJoinChatRoomEnable = welcomeJoinChatRoomEnable; 156 | [[NSUserDefaults standardUserDefaults] setBool:welcomeJoinChatRoomEnable forKey:kTKWelcomeJoinChatRoomEnableKey]; 157 | [[NSUserDefaults standardUserDefaults] synchronize]; 158 | } 159 | 160 | - (void)setWelcomeJoinChatRoomText:(NSString *)welcomeJoinChatRoomText { 161 | _welcomeJoinChatRoomText = welcomeJoinChatRoomText; 162 | [[NSUserDefaults standardUserDefaults] setObject:welcomeJoinChatRoomText forKey:kTKWelcomeJoinChatRoomTextKey]; 163 | [[NSUserDefaults standardUserDefaults] synchronize]; 164 | } 165 | 166 | - (void)setAllChatRoomDescText:(NSString *)allChatRoomDescText { 167 | _allChatRoomDescText = [allChatRoomDescText copy]; 168 | [[NSUserDefaults standardUserDefaults] setObject:_allChatRoomDescText forKey:kTKAllChatRoomDescTextKey]; 169 | [[NSUserDefaults standardUserDefaults] synchronize]; 170 | } 171 | 172 | - (void)setChatRoomSensitiveEnable:(BOOL)chatRoomSensitiveEnable { 173 | _chatRoomSensitiveEnable = chatRoomSensitiveEnable; 174 | [[NSUserDefaults standardUserDefaults] setBool:chatRoomSensitiveEnable forKey:kTKChatRoomSensitiveEnableKey]; 175 | [[NSUserDefaults standardUserDefaults] synchronize]; 176 | } 177 | 178 | - (void)setChatRoomSensitiveArray:(NSMutableArray *)chatRoomSensitiveArray { 179 | _chatRoomSensitiveArray = chatRoomSensitiveArray; 180 | [[NSUserDefaults standardUserDefaults] setObject:chatRoomSensitiveArray forKey:kTKChatRoomSensitiveArrayKey]; 181 | [[NSUserDefaults standardUserDefaults] synchronize]; 182 | } 183 | 184 | @end 185 | -------------------------------------------------------------------------------- /src/TKSettingViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TKSettingViewController.h 3 | // WeChatRobot 4 | // 5 | // Created by TK on 2017/3/27. 6 | // Copyright © 2017年 TK. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TKSettingViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /src/TKSettingViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TKSettingViewController.m 3 | // WeChatRobot 4 | // 5 | // Created by TK on 2017/3/27. 6 | // Copyright © 2017年 TK. All rights reserved. 7 | // 8 | 9 | #import "TKSettingViewController.h" 10 | #import "WeChatRobot.h" 11 | #import "TKMultiSelectContactsViewController.h" 12 | #import "TKChatRoomSensitiveViewController.h" 13 | #import "TKToast.h" 14 | 15 | @interface TKSettingViewController () 16 | 17 | @property (nonatomic, strong) MMTableViewInfo *tableViewInfo; 18 | 19 | @end 20 | 21 | @implementation TKSettingViewController 22 | 23 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { 24 | if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { 25 | // 增加对iPhone X的屏幕适配 26 | CGRect winSize = [UIScreen mainScreen].bounds; 27 | if (winSize.size.height == 812) { // iPhone X 高为812 28 | winSize.size.height -= 88; 29 | winSize.origin.y = 88; 30 | } 31 | _tableViewInfo = [[objc_getClass("MMTableViewInfo") alloc] initWithFrame:winSize style:UITableViewStyleGrouped]; 32 | } 33 | return self; 34 | } 35 | 36 | - (void)viewDidLoad { 37 | [super viewDidLoad]; 38 | 39 | [self reloadTableData]; 40 | [self initTitle]; 41 | MMTableView *tableView = [self.tableViewInfo getTableView]; 42 | [self.view addSubview:tableView]; 43 | } 44 | 45 | - (void)initTitle { 46 | self.title = @"微信小助手"; 47 | [self.navigationController.navigationBar setTitleTextAttributes:@{NSFontAttributeName: [UIFont boldSystemFontOfSize:18.0]}]; 48 | } 49 | 50 | - (void)reloadTableData { 51 | [self.tableViewInfo clearAllSection]; 52 | [self addNiubilitySection]; 53 | [self addContactVerifySection]; 54 | [self addAutoReplySection]; 55 | [self addGroupSettingSection]; 56 | 57 | MMTableView *tableView = [self.tableViewInfo getTableView]; 58 | [tableView reloadData]; 59 | } 60 | 61 | #pragma mark - 设置 TableView 62 | - (void)addNiubilitySection { 63 | MMTableViewSectionInfo *sectionInfo = [objc_getClass("MMTableViewSectionInfo") sectionInfoHeader:@"装逼必备" Footer:nil]; 64 | [sectionInfo addCell:[self createStepSwitchCell]]; 65 | 66 | BOOL changeStepEnable = [[TKRobotConfig sharedConfig] changeStepEnable]; 67 | if (changeStepEnable) { 68 | [sectionInfo addCell:[self createStepCountCell]]; 69 | } 70 | [sectionInfo addCell:[self createRevokeSwitchCell]]; 71 | [sectionInfo addCell:[self createGameCheatSwitchCell]]; 72 | 73 | [self.tableViewInfo addSection:sectionInfo]; 74 | } 75 | 76 | - (void)addContactVerifySection { 77 | MMTableViewSectionInfo *verifySectionInfo = [objc_getClass("MMTableViewSectionInfo") sectionInfoHeader:@"过滤好友请求设置" Footer:nil]; 78 | [verifySectionInfo addCell:[self createVerifySwitchCell]]; 79 | 80 | BOOL autoVerifyEnable = [[TKRobotConfig sharedConfig] autoVerifyEnable]; 81 | if (autoVerifyEnable) { 82 | [verifySectionInfo addCell:[self createAutoVerifyCell]]; 83 | 84 | BOOL autoWelcomeEnable = [[TKRobotConfig sharedConfig] autoWelcomeEnable]; 85 | [verifySectionInfo addCell:[self createWelcomeSwitchCell]]; 86 | if (autoWelcomeEnable) { 87 | [verifySectionInfo addCell:[self createWelcomeCell]]; 88 | } 89 | } 90 | [self.tableViewInfo addSection:verifySectionInfo]; 91 | } 92 | 93 | - (void)addAutoReplySection { 94 | MMTableViewSectionInfo *autoReplySectionInfo = [objc_getClass("MMTableViewSectionInfo") sectionInfoHeader:@"自动回复设置" Footer:nil]; 95 | [autoReplySectionInfo addCell:[self createAutoReplySwitchCell]]; 96 | 97 | BOOL autoReplyEnable = [[TKRobotConfig sharedConfig] autoReplyEnable]; 98 | if (autoReplyEnable) { 99 | [autoReplySectionInfo addCell:[self createAutoReplyKeywordCell]]; 100 | [autoReplySectionInfo addCell:[self createAutoReplyTextCell]]; 101 | } 102 | [self.tableViewInfo addSection:autoReplySectionInfo]; 103 | } 104 | 105 | - (void)addGroupSettingSection { 106 | MMTableViewSectionInfo *groupSectionInfo = [objc_getClass("MMTableViewSectionInfo") sectionInfoHeader:@"群设置" Footer:nil]; 107 | [groupSectionInfo addCell:[self createSetChatRoomDescCell]]; 108 | [groupSectionInfo addCell:[self createAutoDeleteMemberCell]]; 109 | [groupSectionInfo addCell:[self createWelcomeJoinChatRoomSwitchCell]]; 110 | 111 | BOOL welcomeJoinChatRoomEnable = [[TKRobotConfig sharedConfig] welcomeJoinChatRoomEnable]; 112 | if (welcomeJoinChatRoomEnable) { 113 | [groupSectionInfo addCell:[self createWelcomeJoinChatRoomCell]]; 114 | } 115 | [groupSectionInfo addCell:[self createAutoReplyChatRoomSwitchCell]]; 116 | 117 | BOOL autoReplyChatRoomEnable = [[TKRobotConfig sharedConfig] autoReplyChatRoomEnable]; 118 | if (autoReplyChatRoomEnable) { 119 | [groupSectionInfo addCell:[self createAutoReplyChatRoomKeywordCell]]; 120 | [groupSectionInfo addCell:[self createAutoReplyChatRoomTextCell]]; 121 | } 122 | 123 | [self.tableViewInfo addSection:groupSectionInfo]; 124 | } 125 | 126 | #pragma mark - 装逼必备 127 | - (MMTableViewCellInfo *)createStepSwitchCell { 128 | BOOL changeStepEnable = [[TKRobotConfig sharedConfig] changeStepEnable]; 129 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") switchCellForSel:@selector(settingStepSwitch:) target:self title:@"是否修改微信步数" on:changeStepEnable]; 130 | 131 | return cellInfo; 132 | } 133 | 134 | - (MMTableViewCellInfo *)createStepCountCell { 135 | NSInteger deviceStep = [[TKRobotConfig sharedConfig] deviceStep]; 136 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") normalCellForSel:@selector(settingStepCount) target:self title:@"微信运动步数" rightValue:[NSString stringWithFormat:@"%ld", (long)deviceStep] accessoryType:1]; 137 | 138 | return cellInfo; 139 | } 140 | 141 | - (MMTableViewCellInfo *)createRevokeSwitchCell { 142 | BOOL preventRevokeEnable = [[TKRobotConfig sharedConfig] preventRevokeEnable]; 143 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") switchCellForSel:@selector(settingRevokeSwitch:) target:self title:@"拦截撤回消息" on:preventRevokeEnable]; 144 | 145 | return cellInfo; 146 | } 147 | 148 | - (MMTableViewCellInfo *)createGameCheatSwitchCell { 149 | BOOL preventGameCheatEnable = [[TKRobotConfig sharedConfig] preventGameCheatEnable]; 150 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") switchCellForSel:@selector(settingGameCheatSwitch:) target:self title:@"开启游戏作弊" on:preventGameCheatEnable]; 151 | 152 | return cellInfo; 153 | } 154 | 155 | #pragma mark - 添加好友设置 156 | - (MMTableViewCellInfo *)createVerifySwitchCell { 157 | BOOL autoVerifyEnable = [[TKRobotConfig sharedConfig] autoVerifyEnable]; 158 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") switchCellForSel:@selector(settingVerifySwitch:) target:self title:@"开启自动添加好友" on:autoVerifyEnable]; 159 | 160 | return cellInfo; 161 | } 162 | 163 | - (MMTableViewCellInfo *)createAutoVerifyCell { 164 | NSString *verifyText = [[TKRobotConfig sharedConfig] autoVerifyKeyword]; 165 | verifyText = verifyText.length == 0 ? @"请填写" : verifyText; 166 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") normalCellForSel:@selector(settingVerify) target:self title:@"自动通过关键词" rightValue:verifyText accessoryType:1]; 167 | 168 | return cellInfo; 169 | } 170 | 171 | - (MMTableViewCellInfo *)createWelcomeSwitchCell { 172 | BOOL autoVerifyEnable = [[TKRobotConfig sharedConfig] autoWelcomeEnable]; 173 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") switchCellForSel:@selector(settingWelcomeSwitch:) target:self title:@"开启欢迎语" on:autoVerifyEnable]; 174 | 175 | return cellInfo; 176 | } 177 | 178 | - (MMTableViewCellInfo *)createWelcomeCell { 179 | NSString *welcomeText = [[TKRobotConfig sharedConfig] autoWelcomeText]; 180 | welcomeText = welcomeText.length == 0 ? @"请填写" : welcomeText; 181 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") normalCellForSel:@selector(settingWelcome) target:self title:@"欢迎语内容" rightValue:welcomeText accessoryType:1]; 182 | 183 | return cellInfo; 184 | } 185 | 186 | #pragma mark - 自动回复设置 187 | - (MMTableViewCellInfo *)createAutoReplySwitchCell { 188 | BOOL autoReplyEnable = [[TKRobotConfig sharedConfig] autoReplyEnable]; 189 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") switchCellForSel:@selector(settingAutoReplySwitch:)target:self title:@"开启个人消息自动回复" on:autoReplyEnable];; 190 | 191 | return cellInfo; 192 | } 193 | 194 | - (MMTableViewCellInfo *)createAutoReplyKeywordCell { 195 | NSString *autoReplyKeyword = [[TKRobotConfig sharedConfig] autoReplyKeyword]; 196 | autoReplyKeyword = autoReplyKeyword.length == 0 ? @"请填写" : autoReplyKeyword; 197 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") normalCellForSel:@selector(settingAutoReplyKeyword) target:self title:@"特定消息" rightValue:autoReplyKeyword accessoryType:1]; 198 | 199 | return cellInfo; 200 | } 201 | 202 | - (MMTableViewCellInfo *)createAutoReplyTextCell { 203 | NSString *autoReply = [[TKRobotConfig sharedConfig] autoReplyText]; 204 | autoReply = autoReply.length == 0 ? @"请填写" : autoReply; 205 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") normalCellForSel:@selector(settingAutoReply) target:self title:@"自动回复内容" rightValue:autoReply accessoryType:1]; 206 | 207 | return cellInfo; 208 | } 209 | 210 | #pragma mark - 群相关设置 211 | 212 | - (MMTableViewCellInfo *)createSetChatRoomDescCell { 213 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") normalCellForSel:@selector(settingChatRoomDesc) target:self title:@"群公告设置" rightValue:nil accessoryType:1]; 214 | 215 | return cellInfo; 216 | } 217 | 218 | - (MMTableViewCellInfo *)createAutoDeleteMemberCell { 219 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") normalCellForSel:@selector(settingAutoDeleteMember) target:self title:@"自动踢人设置" rightValue:nil accessoryType:1]; 220 | 221 | return cellInfo; 222 | } 223 | 224 | - (MMTableViewCellInfo *)createWelcomeJoinChatRoomSwitchCell { 225 | BOOL welcomeJoinChatRoomEnable = [[TKRobotConfig sharedConfig] welcomeJoinChatRoomEnable]; 226 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") switchCellForSel:@selector(settingWelcomeJoinChatRoomSwitch:)target:self title:@"开启入群欢迎语" on:welcomeJoinChatRoomEnable];; 227 | 228 | return cellInfo; 229 | } 230 | 231 | - (MMTableViewCellInfo *)createWelcomeJoinChatRoomCell { 232 | NSString *welcomeJoinChatRoomText = [[TKRobotConfig sharedConfig] welcomeJoinChatRoomText]; 233 | welcomeJoinChatRoomText = welcomeJoinChatRoomText.length == 0 ? @"请填写" : welcomeJoinChatRoomText; 234 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") normalCellForSel:@selector(settingWelcomeJoinChatRoom) target:self title:@"入群欢迎语" rightValue:welcomeJoinChatRoomText accessoryType:1]; 235 | 236 | return cellInfo; 237 | } 238 | 239 | - (MMTableViewCellInfo *)createAutoReplyChatRoomSwitchCell { 240 | BOOL autoReplyChatRoomEnable = [[TKRobotConfig sharedConfig] autoReplyChatRoomEnable]; 241 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") switchCellForSel:@selector(settingAutoReplyChatRoomSwitch:)target:self title:@"开启群消息自动回复" on:autoReplyChatRoomEnable];; 242 | 243 | return cellInfo; 244 | } 245 | 246 | - (MMTableViewCellInfo *)createAutoReplyChatRoomKeywordCell { 247 | NSString *autoReplyChatRoomKeyword = [[TKRobotConfig sharedConfig] autoReplyChatRoomKeyword]; 248 | autoReplyChatRoomKeyword = autoReplyChatRoomKeyword.length == 0 ? @"请填写" : autoReplyChatRoomKeyword; 249 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") normalCellForSel:@selector(settingAutoReplyChatRoomKeyword) target:self title:@"特定消息" rightValue:autoReplyChatRoomKeyword accessoryType:1]; 250 | 251 | return cellInfo; 252 | } 253 | 254 | - (MMTableViewCellInfo *)createAutoReplyChatRoomTextCell { 255 | NSString *autoReplyChatRoomText = [[TKRobotConfig sharedConfig] autoReplyChatRoomText]; 256 | autoReplyChatRoomText = autoReplyChatRoomText.length == 0 ? @"请填写" : autoReplyChatRoomText; 257 | MMTableViewCellInfo *cellInfo = [objc_getClass("MMTableViewCellInfo") normalCellForSel:@selector(settingAutoReplyChatRoom) target:self title:@"自动回复内容" rightValue:autoReplyChatRoomText accessoryType:1]; 258 | 259 | return cellInfo; 260 | } 261 | 262 | #pragma mark - 设置cell相应的方法 263 | - (void)settingStepSwitch:(UISwitch *)arg { 264 | [[TKRobotConfig sharedConfig] setChangeStepEnable:arg.on]; 265 | [self reloadTableData]; 266 | } 267 | 268 | - (void)settingStepCount { 269 | NSInteger deviceStep = [[TKRobotConfig sharedConfig] deviceStep]; 270 | [self alertControllerWithTitle:@"微信运动设置" 271 | message:@"步数需比之前设置的步数大才能生效,最大值为98800" 272 | content:[NSString stringWithFormat:@"%ld", (long)deviceStep] 273 | placeholder:@"请输入步数" 274 | keyboardType:UIKeyboardTypeNumberPad 275 | blk:^(UITextField *textField) { 276 | [[TKRobotConfig sharedConfig] setDeviceStep:textField.text.integerValue]; 277 | [self reloadTableData]; 278 | }]; 279 | } 280 | 281 | - (void)settingRevokeSwitch:(UISwitch *)arg { 282 | [[TKRobotConfig sharedConfig] setPreventRevokeEnable:arg.on]; 283 | [self reloadTableData]; 284 | } 285 | 286 | - (void)settingGameCheatSwitch:(UISwitch *)arg { 287 | [[TKRobotConfig sharedConfig] setPreventGameCheatEnable:arg.on]; 288 | [self reloadTableData]; 289 | } 290 | 291 | - (void)settingVerifySwitch:(UISwitch *)arg { 292 | [[TKRobotConfig sharedConfig] setAutoVerifyEnable:arg.on]; 293 | [self reloadTableData]; 294 | } 295 | 296 | - (void)settingVerify { 297 | NSString *verifyText = [[TKRobotConfig sharedConfig] autoVerifyKeyword]; 298 | [self alertControllerWithTitle:@"自动通过设置" 299 | message:@"新的好友发送的验证内容与该关键字一致时,则自动通过" 300 | content:verifyText 301 | placeholder:@"请输入好友请求关键字" 302 | blk:^(UITextField *textField) { 303 | [[TKRobotConfig sharedConfig] setAutoVerifyKeyword:textField.text]; 304 | [self reloadTableData]; 305 | CMessageMgr *mgr = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("CMessageMgr")]; 306 | [mgr GetHelloUsers:@"fmessage" Limit:0 OnlyUnread:0]; 307 | }]; 308 | } 309 | 310 | 311 | - (void)settingWelcomeSwitch:(UISwitch *)arg { 312 | [[TKRobotConfig sharedConfig] setAutoWelcomeEnable:arg.on]; 313 | [self reloadTableData]; 314 | } 315 | 316 | - (void)settingWelcome { 317 | TKEditViewController *editVC = [[TKEditViewController alloc] init]; 318 | editVC.text = [[TKRobotConfig sharedConfig] autoWelcomeText]; 319 | [editVC setEndEditing:^(NSString *text) { 320 | [[TKRobotConfig sharedConfig] setAutoWelcomeText:text]; 321 | [self reloadTableData]; 322 | }]; 323 | editVC.title = @"请输入欢迎语内容"; 324 | editVC.placeholder = @"当自动通过好友请求时,则自动发送欢迎语;\n若手动通过,则不发送"; 325 | [self.navigationController PushViewController:editVC animated:YES]; 326 | } 327 | 328 | - (void)settingAutoReplySwitch:(UISwitch *)arg { 329 | [[TKRobotConfig sharedConfig] setAutoReplyEnable:arg.on]; 330 | [self reloadTableData]; 331 | } 332 | 333 | - (void)settingAutoReplyKeyword { 334 | NSString *autoReplyKeyword = [[TKRobotConfig sharedConfig] autoReplyKeyword]; 335 | TKEditViewController *editViewController = [[TKEditViewController alloc] init]; 336 | [editViewController setEndEditing:^(NSString *text) { 337 | [[TKRobotConfig sharedConfig] setAutoReplyKeyword:text]; 338 | [self reloadTableData]; 339 | }]; 340 | editViewController.title = @"个人消息自动回复"; 341 | editViewController.text = autoReplyKeyword; 342 | editViewController.placeholder = @"请输入关键字( ‘*’ 为任何消息都回复,\n‘||’ 为匹配多个关键字)"; 343 | [self.navigationController PushViewController:editViewController animated:YES]; 344 | } 345 | 346 | - (void)settingAutoReply { 347 | TKEditViewController *editViewController = [[TKEditViewController alloc] init]; 348 | editViewController.text = [[TKRobotConfig sharedConfig] autoReplyText]; 349 | [editViewController setEndEditing:^(NSString *text) { 350 | [[TKRobotConfig sharedConfig] setAutoReplyText:text]; 351 | [self reloadTableData]; 352 | }]; 353 | editViewController.title = @"请输入自动回复的内容"; 354 | [self.navigationController PushViewController:editViewController animated:YES]; 355 | } 356 | 357 | - (void)settingAutoReplyChatRoomSwitch:(UISwitch *)arg { 358 | [[TKRobotConfig sharedConfig] setAutoReplyChatRoomEnable:arg.on]; 359 | [self reloadTableData]; 360 | } 361 | 362 | - (void)settingAutoReplyChatRoomKeyword { 363 | NSString *autoReplyChatRoomKeyword = [[TKRobotConfig sharedConfig] autoReplyChatRoomKeyword]; 364 | TKEditViewController *editViewController = [[TKEditViewController alloc] init]; 365 | [editViewController setEndEditing:^(NSString *text) { 366 | [[TKRobotConfig sharedConfig] setAutoReplyChatRoomKeyword:text]; 367 | [self reloadTableData]; 368 | }]; 369 | editViewController.title = @"群消息自动回复"; 370 | editViewController.text = autoReplyChatRoomKeyword; 371 | editViewController.placeholder = @"请输入关键字( ‘*’ 为任何消息都回复,\n‘||’ 为匹配多个关键字)"; 372 | [self.navigationController PushViewController:editViewController animated:YES]; 373 | } 374 | 375 | - (void)settingAutoReplyChatRoom { 376 | TKEditViewController *editViewController = [[TKEditViewController alloc] init]; 377 | editViewController.text = [[TKRobotConfig sharedConfig] autoReplyChatRoomText]; 378 | [editViewController setEndEditing:^(NSString *text) { 379 | [[TKRobotConfig sharedConfig] setAutoReplyChatRoomText:text]; 380 | [self reloadTableData]; 381 | }]; 382 | editViewController.title = @"请输入自动回复的内容"; 383 | [self.navigationController PushViewController:editViewController animated:YES]; 384 | } 385 | 386 | - (void)settingWelcomeJoinChatRoomSwitch:(UISwitch *)arg { 387 | [[TKRobotConfig sharedConfig] setWelcomeJoinChatRoomEnable:arg.on]; 388 | [self reloadTableData]; 389 | } 390 | 391 | - (void)settingWelcomeJoinChatRoom { 392 | TKEditViewController *editVC = [[TKEditViewController alloc] init]; 393 | editVC.text = [[TKRobotConfig sharedConfig] welcomeJoinChatRoomText]; 394 | editVC.title = @"请输入入群欢迎语"; 395 | [editVC setEndEditing:^(NSString *text) { 396 | [[TKRobotConfig sharedConfig] setWelcomeJoinChatRoomText:text]; 397 | [self reloadTableData]; 398 | }]; 399 | [self.navigationController PushViewController:editVC animated:YES]; 400 | } 401 | 402 | - (void)settingChatRoomDesc { 403 | TKMultiSelectContactsViewController *selectVC = [[TKMultiSelectContactsViewController alloc] init]; 404 | selectVC.title = @"我创建的群聊"; 405 | [self.navigationController PushViewController:selectVC animated:YES]; 406 | } 407 | 408 | - (void)settingAutoDeleteMember { 409 | TKChatRoomSensitiveViewController *vc = [[TKChatRoomSensitiveViewController alloc] init]; 410 | vc.title = @"设置敏感词"; 411 | [self.navigationController PushViewController:vc animated:YES]; 412 | } 413 | 414 | // - (void)settingAutoCreateGroup { 415 | // [self alertControllerWithTitle:@"选择联系人" 416 | // message:nil 417 | // placeholder:@"请输入联系人过滤条件" 418 | // blk:^(UITextField *textField) { 419 | // CContactMgr *contactMgr = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("CContactMgr")]; 420 | // 421 | // NSArray *contactArray = [contactMgr getContactList:1 contactType:0]; 422 | // NSPredicate *predicate = [NSPredicate predicateWithFormat:@"((m_nsNickName CONTAINS %@) OR (m_nsRemark CONTAINS %@)) AND isChatroom = 0 AND m_isPlugin == 0 AND m_uiFriendScene != 0", textField.text, textField.text]; 423 | // NSArray *filteredArray = [contactArray filteredArrayUsingPredicate:predicate]; 424 | // NSMutableArray *memberList = [[NSMutableArray alloc] init]; 425 | // [filteredArray enumerateObjectsUsingBlock:^(CContact *contact, NSUInteger idx, BOOL * _Nonnull stop) { 426 | // GroupMember *member = [[objc_getClass("GroupMember") alloc] init]; 427 | // member.m_nsMemberName = contact.m_nsUsrName; 428 | // member.m_uiMemberStatus = 0; 429 | // member.m_nsNickName = contact.m_nsNickName; 430 | // [memberList addObject:member]; 431 | // }]; 432 | // 433 | // [self alertControllerWithTitle:@"群名称" 434 | // message:nil 435 | // placeholder:@"请输入群名称" 436 | // blk:^(UITextField *textField) { 437 | // CGroupMgr *groupMgr = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("CGroupMgr")]; 438 | // [groupMgr CreateGroup:textField.text withMemberList:memberList]; 439 | // 440 | // [TKToast toast:@"建群成功,请在会话列表中查看..."]; 441 | // }]; 442 | // }]; 443 | // } 444 | 445 | - (void)alertControllerWithTitle:(NSString *)title content:(NSString *)content placeholder:(NSString *)placeholder blk:(void (^)(UITextField *))blk { 446 | [self alertControllerWithTitle:title message:nil content:content placeholder:placeholder blk:blk]; 447 | } 448 | 449 | - (void)alertControllerWithTitle:(NSString *)title message:(NSString *)message content:(NSString *)content placeholder:(NSString *)placeholder blk:(void (^)(UITextField *))blk { 450 | [self alertControllerWithTitle:title message:message content:content placeholder:placeholder keyboardType:UIKeyboardTypeDefault blk:blk]; 451 | } 452 | 453 | - (void)alertControllerWithTitle:(NSString *)title message:(NSString *)message content:(NSString *)content placeholder:(NSString *)placeholder keyboardType:(UIKeyboardType)keyboardType blk:(void (^)(UITextField *))blk { 454 | UIAlertController *alertController = ({ 455 | UIAlertController *alert = [UIAlertController 456 | alertControllerWithTitle:title 457 | message:message 458 | preferredStyle:UIAlertControllerStyleAlert]; 459 | [alert addAction:[UIAlertAction actionWithTitle:@"取消" 460 | style:UIAlertActionStyleCancel 461 | handler:nil]]; 462 | [alert addAction:[UIAlertAction actionWithTitle:@"确定" 463 | style:UIAlertActionStyleDestructive 464 | handler:^(UIAlertAction * _Nonnull action) { 465 | if (blk) { 466 | blk(alert.textFields.firstObject); 467 | } 468 | }]]; 469 | 470 | [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { 471 | textField.placeholder = placeholder; 472 | textField.text = content; 473 | textField.keyboardType = keyboardType; 474 | }]; 475 | 476 | alert; 477 | }); 478 | 479 | [self presentViewController:alertController animated:YES completion:nil]; 480 | } 481 | 482 | @end 483 | -------------------------------------------------------------------------------- /src/TKToast.h: -------------------------------------------------------------------------------- 1 | // 2 | // TKToast.h 3 | // WeChatRobot 4 | // 5 | // Created by TK on 2017/4/7. 6 | // Copyright © 2017年 com.feibo.wechatrobot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TKToast : NSObject 12 | 13 | + (void)toast:(NSString *)msg; 14 | + (void)toast:(NSString *)msg delay:(NSTimeInterval)duration; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /src/TKToast.m: -------------------------------------------------------------------------------- 1 | // 2 | // TKToast.m 3 | // WeChatRobot 4 | // 5 | // Created by TK on 2017/4/5. 6 | // Copyright © 2017年 com.feibo.wechatrobot. All rights reserved. 7 | // 8 | 9 | #import "TKToast.h" 10 | #import "WeChatRobot.h" 11 | 12 | #define MARGIN_SIZE FIX_SIZE(10) 13 | 14 | @interface TKToast() 15 | 16 | @property (nonatomic, strong) UILabel *textLabel; 17 | 18 | @end 19 | 20 | @implementation TKToast 21 | 22 | + (void)toast:(NSString *)msg { 23 | [self toast:msg delay:1.5]; 24 | } 25 | 26 | + (void)toast:(NSString *)msg delay:(NSTimeInterval)duration { 27 | CGRect rect = [msg boundingRectWithSize:CGSizeMake(SCREEN_WIDTH - MARGIN_SIZE * 4, 1000) 28 | options:NSStringDrawingUsesLineFragmentOrigin 29 | attributes:[NSDictionary dictionaryWithObjectsAndKeys:TKFont(24), NSFontAttributeName, nil] context:nil]; 30 | UILabel *label = ({ 31 | UILabel *l = [[UILabel alloc] init]; 32 | [l setBounds:CGRectMake(0, 0, rect.size.width + MARGIN_SIZE * 2, rect.size.height + MARGIN_SIZE * 2)]; 33 | [l.layer setCornerRadius:FIX_SIZE(4)]; 34 | [l.layer setMasksToBounds:YES]; 35 | [l setBackgroundColor:RGBA(0, 0, 0, 0.6)]; 36 | [l setAlpha:0.0f]; 37 | [l setTextAlignment:NSTextAlignmentCenter]; 38 | [l setFont:TKFont(24)]; 39 | [l setTextColor:[UIColor whiteColor]]; 40 | [l setNumberOfLines:0]; 41 | [l setText:msg]; 42 | 43 | l; 44 | }); 45 | 46 | UIWindow *window = [UIApplication sharedApplication].keyWindow; 47 | [label setCenter:window.center]; 48 | 49 | id temp = window.subviews.lastObject; 50 | if ([temp isMemberOfClass:[UILabel class]]) { 51 | [temp removeFromSuperview]; 52 | } 53 | 54 | [window addSubview:label]; 55 | 56 | [UIView animateWithDuration:0.2 animations:^{ 57 | [label setAlpha:1.0f]; 58 | } completion:^(BOOL finished) { 59 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 60 | [UIView animateWithDuration:0.4 animations:^{ 61 | [label setAlpha:0.0f]; 62 | } completion:^(BOOL finished) { 63 | [label removeFromSuperview]; 64 | }]; 65 | }); 66 | }]; 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /src/Tweak.xm: -------------------------------------------------------------------------------- 1 | #import "WeChatRobot.h" 2 | #import "TKRobotConfig.h" 3 | #import "TKSettingViewController.h" 4 | #import "EmoticonGameCheat.h" 5 | 6 | %hook CMessageMgr 7 | 8 | - (void)AddEmoticonMsg:(NSString *)msg MsgWrap:(CMessageWrap *)msgWrap { 9 | if ([[TKRobotConfig sharedConfig] preventGameCheatEnable]) { // 是否开启游戏作弊 10 | if ([msgWrap m_uiMessageType] == 47 && ([msgWrap m_uiGameType] == 2|| [msgWrap m_uiGameType] == 1)) { 11 | [EmoticonGameCheat showEoticonCheat:[msgWrap m_uiGameType] callback:^(NSInteger random){ 12 | [msgWrap setM_nsEmoticonMD5:[objc_getClass("GameController") getMD5ByGameContent:random]]; 13 | [msgWrap setM_uiGameContent:random]; 14 | %orig(msg, msgWrap); 15 | }]; 16 | return; 17 | } 18 | } 19 | 20 | %orig(msg, msgWrap); 21 | } 22 | 23 | - (void)MessageReturn:(unsigned int)arg1 MessageInfo:(NSDictionary *)info Event:(unsigned int)arg3 { 24 | %orig; 25 | CMessageWrap *wrap = [info objectForKey:@"18"]; 26 | 27 | if (arg1 == 227) { 28 | NSDate *now = [NSDate date]; 29 | NSTimeInterval nowSecond = now.timeIntervalSince1970; 30 | if (nowSecond - wrap.m_uiCreateTime > 60) { // 若是1分钟前的消息,则不进行处理。 31 | return; 32 | } 33 | CContactMgr *contactMgr = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("CContactMgr")]; 34 | CContact *contact = [contactMgr getContactByName:wrap.m_nsFromUsr]; 35 | if(wrap.m_uiMessageType == 1) { // 收到文本消息 36 | if (contact.m_uiFriendScene == 0 && ![contact isChatroom]) { 37 | // 该消息为公众号 38 | return; 39 | } 40 | if (![contact isChatroom]) { // 是否为群聊 41 | [self autoReplyWithMessageWrap:wrap]; // 自动回复个人消息 42 | } else { 43 | [self removeMemberWithMessageWrap:wrap]; // 自动踢人 44 | [self autoReplyChatRoomWithMessageWrap:wrap]; // 自动回复群消息 45 | } 46 | } else if(wrap.m_uiMessageType == 10000) { // 收到群通知,eg:群邀请了好友;删除了好友。 47 | CContact *selfContact = [contactMgr getSelfContact]; 48 | if([selfContact.m_nsUsrName isEqualToString:contact.m_nsOwner]) { // 只有自己创建的群,才发送群欢迎语 49 | [self welcomeJoinChatRoomWithMessageWrap:wrap]; 50 | } 51 | } 52 | } else if (arg1 == 332) { // 收到添加好友消息 53 | [self addAutoVerifyWithMessageInfo:info]; 54 | } 55 | } 56 | 57 | - (id)GetHelloUsers:(id)arg1 Limit:(unsigned int)arg2 OnlyUnread:(_Bool)arg3 { 58 | id userNameArray = %orig; 59 | if ([arg1 isEqualToString:@"fmessage"] && arg2 == 0 && arg3 == 0) { 60 | [self addAutoVerifyWithArray:userNameArray arrayType:TKArrayTpyeMsgUserName]; 61 | } 62 | 63 | return userNameArray; 64 | } 65 | 66 | - (void)onRevokeMsg:(CMessageWrap *)arg1 { 67 | if ([[TKRobotConfig sharedConfig] preventRevokeEnable]) { 68 | NSString *msgContent = arg1.m_nsContent; 69 | 70 | NSString *(^parseParam)(NSString *, NSString *,NSString *) = ^NSString *(NSString *content, NSString *paramBegin,NSString *paramEnd) { 71 | NSUInteger startIndex = [content rangeOfString:paramBegin].location + paramBegin.length; 72 | NSUInteger endIndex = [content rangeOfString:paramEnd].location; 73 | NSRange range = NSMakeRange(startIndex, endIndex - startIndex); 74 | return [content substringWithRange:range]; 75 | }; 76 | 77 | NSString *session = parseParam(msgContent, @"", @""); 78 | NSString *newmsgid = parseParam(msgContent, @"", @""); 79 | NSString *fromUsrName = parseParam(msgContent, @"(wrap, "m_nsLastDisplayContent"); 127 | NSString *needAutoReplyMsg = [[TKRobotConfig sharedConfig] autoReplyKeyword]; 128 | NSArray * keyWordArray = [needAutoReplyMsg componentsSeparatedByString:@"||"]; 129 | [keyWordArray enumerateObjectsUsingBlock:^(NSString *keyword, NSUInteger idx, BOOL * _Nonnull stop) { 130 | if ([keyword isEqualToString:@"*"] || [content isEqualToString:keyword]) { 131 | [self sendMsg:autoReplyContent toContactUsrName:wrap.m_nsFromUsr]; 132 | } 133 | }]; 134 | } 135 | 136 | %new 137 | - (void)removeMemberWithMessageWrap:(CMessageWrap *)wrap { 138 | BOOL chatRoomSensitiveEnable = [[TKRobotConfig sharedConfig] chatRoomSensitiveEnable]; 139 | if (!chatRoomSensitiveEnable) { 140 | return; 141 | } 142 | 143 | CGroupMgr *groupMgr = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("CGroupMgr")]; 144 | NSString *content = MSHookIvar(wrap, "m_nsLastDisplayContent"); 145 | NSMutableArray *array = [[TKRobotConfig sharedConfig] chatRoomSensitiveArray]; 146 | [array enumerateObjectsUsingBlock:^(NSString *text, NSUInteger idx, BOOL * _Nonnull stop) { 147 | if([content isEqualToString:text]) { 148 | [groupMgr DeleteGroupMember:wrap.m_nsFromUsr withMemberList:@[wrap.m_nsRealChatUsr] scene:3074516140857229312]; 149 | } 150 | }]; 151 | } 152 | 153 | %new 154 | - (void)autoReplyChatRoomWithMessageWrap:(CMessageWrap *)wrap { 155 | BOOL autoReplyChatRoomEnable = [[TKRobotConfig sharedConfig] autoReplyChatRoomEnable]; 156 | NSString *autoReplyChatRoomContent = [[TKRobotConfig sharedConfig] autoReplyChatRoomText]; 157 | if (!autoReplyChatRoomEnable || autoReplyChatRoomContent == nil || [autoReplyChatRoomContent isEqualToString:@""]) { // 是否开启自动回复 158 | return; 159 | } 160 | 161 | NSString * content = MSHookIvar(wrap, "m_nsLastDisplayContent"); 162 | NSString *needAutoReplyChatRoomMsg = [[TKRobotConfig sharedConfig] autoReplyChatRoomKeyword]; 163 | NSArray * keyWordArray = [needAutoReplyChatRoomMsg componentsSeparatedByString:@"||"]; 164 | [keyWordArray enumerateObjectsUsingBlock:^(NSString *keyword, NSUInteger idx, BOOL * _Nonnull stop) { 165 | if ([keyword isEqualToString:@"*"] || [content isEqualToString:keyword]) { 166 | [self sendMsg:autoReplyChatRoomContent toContactUsrName:wrap.m_nsFromUsr]; 167 | } 168 | }]; 169 | } 170 | 171 | %new 172 | - (void)welcomeJoinChatRoomWithMessageWrap:(CMessageWrap *)wrap { 173 | BOOL welcomeJoinChatRoomEnable = [[TKRobotConfig sharedConfig] welcomeJoinChatRoomEnable]; 174 | if (!welcomeJoinChatRoomEnable) return; // 是否开启入群欢迎语 175 | 176 | 177 | 178 | 179 | NSString * content = MSHookIvar(wrap, "m_nsLastDisplayContent"); 180 | NSRange rangeFrom = [content rangeOfString:@"邀请\""]; 181 | NSRange rangeTo = [content rangeOfString:@"\"加入了群聊"]; 182 | NSRange nameRange; 183 | if (rangeFrom.length > 0 && rangeTo.length > 0) { // 通过别人邀请进群 184 | NSInteger nameLocation = rangeFrom.location + rangeFrom.length; 185 | nameRange = NSMakeRange(nameLocation, rangeTo.location - nameLocation); 186 | } else { 187 | NSRange range = [content rangeOfString:@"\"通过扫描\""]; 188 | if (range.length > 0) { // 通过二维码扫描进群 189 | nameRange = NSMakeRange(2, range.location - 2); 190 | } else { 191 | return; 192 | } 193 | } 194 | 195 | NSString *welcomeJoinChatRoomText = [[TKRobotConfig sharedConfig] welcomeJoinChatRoomText]; 196 | [self sendMsg:welcomeJoinChatRoomText toContactUsrName:wrap.m_nsFromUsr]; 197 | } 198 | 199 | %new 200 | - (void)addAutoVerifyWithMessageInfo:(NSDictionary *)info { 201 | BOOL autoVerifyEnable = [[TKRobotConfig sharedConfig] autoVerifyEnable]; 202 | 203 | if (!autoVerifyEnable) 204 | return; 205 | 206 | NSString *keyStr = [info objectForKey:@"5"]; 207 | if ([keyStr isEqualToString:@"fmessage"]) { 208 | NSArray *wrapArray = [info objectForKey:@"27"]; 209 | [self addAutoVerifyWithArray:wrapArray arrayType:TKArrayTpyeMsgWrap]; 210 | } 211 | } 212 | 213 | %new // 自动通过好友请求 214 | - (void)addAutoVerifyWithArray:(NSArray *)ary arrayType:(TKArrayTpye)type { 215 | NSMutableArray *arrHellos = [NSMutableArray array]; 216 | [ary enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 217 | if (type == TKArrayTpyeMsgWrap) { 218 | CPushContact *contact = [%c(SayHelloDataLogic) getContactFrom:obj]; 219 | [arrHellos addObject:contact]; 220 | } else if (type == TKArrayTpyeMsgUserName) { 221 | FriendAsistSessionMgr *asistSessionMgr = [[%c(MMServiceCenter) defaultCenter] getService:%c(FriendAsistSessionMgr)]; 222 | CMessageWrap *wrap = [asistSessionMgr GetLastMessage:@"fmessage" HelloUser:obj OnlyTo:NO]; 223 | CPushContact *contact = [%c(SayHelloDataLogic) getContactFrom:wrap]; 224 | [arrHellos addObject:contact]; 225 | } 226 | }]; 227 | 228 | NSString *autoVerifyKeyword = [[TKRobotConfig sharedConfig] autoVerifyKeyword]; 229 | for (int idx = 0;idx < arrHellos.count;idx++) { 230 | CPushContact *contact = arrHellos[idx]; 231 | if (![contact isMyContact] && [contact.m_nsDes isEqualToString:autoVerifyKeyword]) { 232 | CContactVerifyLogic *verifyLogic = [[%c(CContactVerifyLogic) alloc] init]; 233 | CVerifyContactWrap *wrap = [[%c(CVerifyContactWrap) alloc] init]; 234 | [wrap setM_nsUsrName:contact.m_nsEncodeUserName]; 235 | [wrap setM_uiScene:contact.m_uiFriendScene]; 236 | [wrap setM_nsTicket:contact.m_nsTicket]; 237 | [wrap setM_nsChatRoomUserName:contact.m_nsChatRoomUserName]; 238 | wrap.m_oVerifyContact = contact; 239 | 240 | AutoSetRemarkMgr *mgr = [[%c(MMServiceCenter) defaultCenter] getService:%c(AutoSetRemarkMgr)]; 241 | id attr = [mgr GetStrangerAttribute:contact AttributeName:1001]; 242 | 243 | if([attr boolValue]) { 244 | [wrap setM_uiWCFlag:(wrap.m_uiWCFlag | 1)]; 245 | } 246 | [verifyLogic startWithVerifyContactWrap:[NSArray arrayWithObject:wrap] opCode:3 parentView:[UIView new] fromChatRoom:NO]; 247 | 248 | // 发送欢迎语 249 | BOOL autoWelcomeEnable = [[TKRobotConfig sharedConfig] autoWelcomeEnable]; 250 | NSString *autoWelcomeText = [[TKRobotConfig sharedConfig] autoWelcomeText]; 251 | if (autoWelcomeEnable && autoWelcomeText != nil) { 252 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 253 | [self sendMsg:autoWelcomeText toContactUsrName:contact.m_nsUsrName]; 254 | }); 255 | } 256 | } 257 | } 258 | } 259 | 260 | %new // 发送消息 261 | - (void)sendMsg:(NSString *)msg toContactUsrName:(NSString *)userName { 262 | CMessageWrap *wrap = [[%c(CMessageWrap) alloc] initWithMsgType:1]; 263 | id usrName = [%c(SettingUtil) getLocalUsrName:0]; 264 | [wrap setM_nsFromUsr:usrName]; 265 | [wrap setM_nsContent:msg]; 266 | [wrap setM_nsToUsr:userName]; 267 | MMNewSessionMgr *sessionMgr = [[%c(MMServiceCenter) defaultCenter] getService:%c(MMNewSessionMgr)]; 268 | [wrap setM_uiCreateTime:[sessionMgr GenSendMsgTime]]; 269 | [wrap setM_uiStatus:YES]; 270 | 271 | CMessageMgr *chatMgr = [[%c(MMServiceCenter) defaultCenter] getService:%c(CMessageMgr)]; 272 | [chatMgr AddMsg:userName MsgWrap:wrap]; 273 | } 274 | %end 275 | 276 | %hook NewSettingViewController 277 | - (void)reloadTableData { 278 | %orig; 279 | MMTableViewInfo *tableViewInfo = MSHookIvar(self, "m_tableViewInfo"); 280 | MMTableViewSectionInfo *sectionInfo = [%c(MMTableViewSectionInfo) sectionInfoDefaut]; 281 | MMTableViewCellInfo *settingCell = [%c(MMTableViewCellInfo) normalCellForSel:@selector(setting) target:self title:@"微信小助手" accessoryType:1]; 282 | [sectionInfo addCell:settingCell]; 283 | [tableViewInfo insertSection:sectionInfo At:0]; 284 | MMTableView *tableView = [tableViewInfo getTableView]; 285 | [tableView reloadData]; 286 | } 287 | 288 | %new 289 | - (void)setting { 290 | TKSettingViewController *settingViewController = [[TKSettingViewController alloc] init]; 291 | [self.navigationController PushViewController:settingViewController animated:YES]; 292 | } 293 | %end 294 | 295 | // %hook MicroMessengerAppDelegate 296 | // - (_Bool)application:(id)arg1 didFinishLaunchingWithOptions:(id)arg2 { 297 | // BOOL finish = %orig; 298 | // 299 | // static dispatch_once_t onceToken; 300 | // dispatch_once(&onceToken, ^{ 301 | // Method originalBundleIdentifierMethod = class_getInstanceMethod([NSBundle class], @selector(bundleIdentifier)); 302 | // Method swizzledBundleIdentifierMethod = class_getInstanceMethod([NSBundle class], @selector(tk_bundleIdentifier)); 303 | // if(originalBundleIdentifierMethod && swizzledBundleIdentifierMethod) { 304 | // method_exchangeImplementations(originalBundleIdentifierMethod, swizzledBundleIdentifierMethod); 305 | // } 306 | // 307 | // Method originalInfoDictionaryMethod = class_getInstanceMethod([NSBundle class], @selector(infoDictionary)); 308 | // Method swizzledInfoDictionaryMethod = class_getInstanceMethod([NSBundle class], @selector(tk_infoDictionary)); 309 | // if(originalInfoDictionaryMethod && swizzledInfoDictionaryMethod) { 310 | // method_exchangeImplementations(originalInfoDictionaryMethod, swizzledInfoDictionaryMethod); 311 | // } 312 | // 313 | // }); 314 | // return finish; 315 | // } 316 | // %end 317 | 318 | %hook WCDeviceStepObject 319 | -(NSInteger)m7StepCount { 320 | NSInteger stepCount = %orig; 321 | NSInteger newStepCount = [[TKRobotConfig sharedConfig] deviceStep]; 322 | BOOL changeStepEnable = [[TKRobotConfig sharedConfig] changeStepEnable]; 323 | 324 | return changeStepEnable ? newStepCount : stepCount; 325 | } 326 | 327 | -(NSInteger)hkStepCount { 328 | NSInteger stepCount = %orig; 329 | NSInteger newStepCount = [[TKRobotConfig sharedConfig] deviceStep]; 330 | BOOL changeStepEnable = [[TKRobotConfig sharedConfig] changeStepEnable]; 331 | 332 | return changeStepEnable ? newStepCount : stepCount; 333 | } 334 | 335 | %end 336 | -------------------------------------------------------------------------------- /src/UIColor+Extend.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Extend.h 3 | // categoryKitDemo 4 | // 5 | // Created by zhanghao on 2016/7/23. 6 | // Copyright © 2016年 zhanghao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIColor (Extend) 12 | 13 | + (UIColor *)randomColor; 14 | + (instancetype)r:(uint8_t)r g:(uint8_t)g b:(uint8_t)b alphaComponent:(CGFloat)alpha; 15 | + (instancetype)r:(uint8_t)r g:(uint8_t)g b:(uint8_t)b; 16 | + (instancetype)r:(uint8_t)r g:(uint8_t)g b:(uint8_t)b a:(uint8_t)a; 17 | + (instancetype)rgba:(NSUInteger)rgba; 18 | + (instancetype)colorWithHexString:(NSString*)hexString; 19 | 20 | - (NSUInteger)rgbaValue; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /src/UIColor+Extend.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Extend.m 3 | // categoryKitDemo 4 | // 5 | // Created by zhanghao on 2016/7/23. 6 | // Copyright © 2016年 zhanghao. All rights reserved. 7 | // 8 | 9 | #import "UIColor+Extend.h" 10 | 11 | @implementation UIColor (Extend) 12 | 13 | + (UIColor *)randomColor { 14 | NSInteger aRedValue = arc4random() % 255; 15 | NSInteger aGreenValue = arc4random() % 255; 16 | NSInteger aBlueValue = arc4random() % 255; 17 | UIColor *randColor = [UIColor colorWithRed:aRedValue / 255.0f green:aGreenValue / 255.0f blue:aBlueValue / 255.0f alpha:1.0f]; 18 | return randColor; 19 | } 20 | 21 | + (instancetype)r:(uint8_t)r g:(uint8_t)g b:(uint8_t)b alphaComponent:(CGFloat)alpha { 22 | return [[self r:r g:g b:b] colorWithAlphaComponent:alpha]; 23 | } 24 | 25 | + (instancetype)r:(uint8_t)r g:(uint8_t)g b:(uint8_t)b { 26 | return [self r:r g:g b:b a:0xff]; 27 | } 28 | 29 | + (instancetype)r:(uint8_t)r g:(uint8_t)g b:(uint8_t)b a:(uint8_t)a { 30 | return [self colorWithRed:r / 255. green:g / 255. blue:b / 255. alpha:a / 255.]; 31 | } 32 | 33 | + (instancetype)rgba:(NSUInteger)rgba { 34 | return [self r:(rgba >> 24)&0xFF g:(rgba >> 16)&0xFF b:(rgba >> 8)&0xFF a:rgba&0xFF]; 35 | } 36 | 37 | + (instancetype)colorWithHexString:(NSString *)hexString { 38 | if (!hexString) 39 | return nil; 40 | 41 | NSString* hex = [NSString stringWithString:hexString]; 42 | if ([hex hasPrefix:@"#"]) 43 | hex = [hex substringFromIndex:1]; 44 | 45 | if (hex.length == 6) 46 | hex = [hex stringByAppendingString:@"FF"]; 47 | else if (hex.length != 8) 48 | return nil; 49 | 50 | uint32_t rgba; 51 | NSScanner* scanner = [NSScanner scannerWithString:hex]; 52 | [scanner scanHexInt:&rgba]; 53 | return [UIColor rgba:rgba]; 54 | } 55 | 56 | - (NSUInteger)rgbaValue { 57 | CGFloat r, g, b, a; 58 | if ([self getRed:&r green:&g blue:&b alpha:&a]) { 59 | NSUInteger rr = (NSUInteger)(r * 255 + 0.5); 60 | NSUInteger gg = (NSUInteger)(g * 255 + 0.5); 61 | NSUInteger bb = (NSUInteger)(b * 255 + 0.5); 62 | NSUInteger aa = (NSUInteger)(a * 255 + 0.5); 63 | 64 | return (rr << 24) | (gg << 16) | (bb << 8) | aa; 65 | } else { 66 | return 0; 67 | } 68 | } 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /src/UIScreen+Extend.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIScreen+Extend.h 3 | // categoryKitDemo 4 | // 5 | // Created by zhanghao on 2016/7/23. 6 | // Copyright © 2016年 zhanghao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIScreen (Extend) 12 | 13 | + (CGSize)size; 14 | + (CGSize)sizeSwap; 15 | + (CGFloat)width; 16 | + (CGFloat)height; 17 | + (CGFloat)scale; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /src/UIScreen+Extend.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIScreen+Extend.m 3 | // categoryKitDemo 4 | // 5 | // Created by zhanghao on 2016/7/23. 6 | // Copyright © 2016年 zhanghao. All rights reserved. 7 | // 8 | 9 | #import "UIScreen+Extend.h" 10 | 11 | @implementation UIScreen (Extend) 12 | 13 | + (CGSize)size { 14 | return [[UIScreen mainScreen] bounds].size; 15 | } 16 | 17 | + (CGSize)sizeSwap { 18 | return CGSizeMake([self size].height, [self size].width); 19 | } 20 | 21 | + (CGFloat)width { 22 | return [[UIScreen mainScreen] bounds].size.width; 23 | } 24 | 25 | + (CGFloat)height { 26 | return [[UIScreen mainScreen] bounds].size.height; 27 | } 28 | 29 | + (CGFloat)scale { 30 | return [UIScreen mainScreen].scale; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /src/UIView+Layout.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Layout.h 3 | // categoryKitDemo 4 | // 5 | // Created by zhanghao on 2016/7/23. 6 | // Copyright © 2016年 zhanghao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIView (Layout) 12 | 13 | @property (nonatomic) CGFloat x; 14 | @property (nonatomic) CGFloat y; 15 | @property (nonatomic) CGFloat width; 16 | @property (nonatomic) CGFloat height; 17 | @property (nonatomic) CGFloat right; 18 | @property (nonatomic) CGFloat bottom; 19 | @property (nonatomic) CGFloat centerX; 20 | @property (nonatomic) CGFloat centerY; 21 | @property (nonatomic) CGPoint origin; 22 | @property (nonatomic) CGSize size; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /src/UIView+Layout.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Layout.m 3 | // categoryKitDemo 4 | // 5 | // Created by zhanghao on 2016/7/23. 6 | // Copyright © 2016年 zhanghao. All rights reserved. 7 | // 8 | 9 | #import "UIView+Layout.h" 10 | 11 | @implementation UIView (Layout) 12 | 13 | - (CGFloat)x { 14 | return self.frame.origin.x; 15 | } 16 | 17 | - (void)setX:(CGFloat)x { 18 | self.frame = CGRectMake(x, self.y, self.width, self.height); 19 | } 20 | 21 | - (CGFloat)y { 22 | return self.frame.origin.y; 23 | } 24 | 25 | - (void)setY:(CGFloat)y { 26 | self.frame = CGRectMake(self.x, y, self.width, self.height); 27 | } 28 | 29 | - (CGFloat)width { 30 | return self.frame.size.width; 31 | } 32 | 33 | - (void)setWidth:(CGFloat)width { 34 | self.frame = CGRectMake(self.x, self.y, width, self.height); 35 | } 36 | 37 | - (CGFloat)height { 38 | return self.frame.size.height; 39 | } 40 | 41 | - (void)setHeight:(CGFloat)height { 42 | self.frame = CGRectMake(self.x, self.y, self.width, height); 43 | } 44 | 45 | - (CGFloat)right { 46 | return self.frame.origin.x + self.frame.size.width; 47 | } 48 | 49 | - (void)setRight:(CGFloat)right { 50 | CGRect frame = self.frame; 51 | frame.origin.x = right - frame.size.width; 52 | self.frame = frame; 53 | } 54 | 55 | - (CGFloat)bottom { 56 | return self.frame.origin.y + self.frame.size.height; 57 | } 58 | 59 | - (void)setBottom:(CGFloat)bottom { 60 | CGRect frame = self.frame; 61 | frame.origin.y = bottom - frame.size.height; 62 | self.frame = frame; 63 | } 64 | 65 | - (CGFloat)centerX { 66 | return self.center.x; 67 | } 68 | 69 | - (void)setCenterX:(CGFloat)centerX { 70 | self.center = CGPointMake(centerX, self.center.y); 71 | } 72 | 73 | - (CGFloat)centerY { 74 | return self.center.y; 75 | } 76 | 77 | - (void)setCenterY:(CGFloat)centerY { 78 | self.center = CGPointMake(self.center.x, centerY); 79 | } 80 | 81 | - (CGPoint)origin { 82 | return self.frame.origin; 83 | } 84 | 85 | - (void)setOrigin:(CGPoint)origin { 86 | CGRect frame = self.frame; 87 | frame.origin = origin; 88 | self.frame = frame; 89 | } 90 | 91 | - (CGSize)size { 92 | return self.frame.size; 93 | } 94 | 95 | - (void)setSize:(CGSize)size { 96 | CGRect frame = self.frame; 97 | frame.size = size; 98 | self.frame = frame; 99 | } 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /src/WeChatRobot.h: -------------------------------------------------------------------------------- 1 | // 2 | // WeChatRobot.h 3 | // WeChatRobot 4 | // 5 | // Created by TK on 2017/3/27. 6 | // Copyright © 2017年 TK. All rights reserved. 7 | // 8 | 9 | #import "TKEditViewController.h" 10 | #import "TKRobotConfig.h" 11 | #import 12 | #import 13 | #import "TKToast.h" 14 | 15 | #define SCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width) 16 | #define SCREEN_HEIGHT ([UIScreen mainScreen].bounds.size.height) 17 | //适配IP6和6+的等比放大效果 18 | #define FIX_SIZE(num) ((num) * SCREEN_WIDTH / 320.0) 19 | #define FIX_FONT_SIZE(size) SCREEN_WIDTH < 375 ? ((size + 4.0) / 2.0) : SCREEN_WIDTH == 375 ? ((size + 8.0) / 2.0) : ((size + 12.0) / 2.0) 20 | #define TKFont(size) [UIFont systemFontOfSize:FIX_FONT_SIZE(size)] 21 | #define RGBA(r, g, b, a) [UIColor colorWithRed:(r) / 255.0 green:(g) / 255.0 blue:(b) / 255.0 alpha:(a)] 22 | #define RGB(r, g, b) RGBA(r, g, b, 1) 23 | 24 | typedef NS_ENUM(NSUInteger, TKArrayTpye) { 25 | TKArrayTpyeMsgWrap, 26 | TKArrayTpyeMsgUserName 27 | }; 28 | 29 | @class MMTableViewInfo; 30 | 31 | #pragma mark - MODEL 32 | 33 | @interface CMessageWrap : NSObject 34 | @property(nonatomic, assign) NSInteger m_uiGameType; // 1、猜拳; 2、骰子; 0、自定义表情 35 | @property(nonatomic, assign) unsigned long m_uiGameContent; 36 | @property(nonatomic, strong) NSString *m_nsEmoticonMD5; 37 | @property(nonatomic) long long m_n64MesSvrID; 38 | @property (nonatomic, copy) NSString *m_nsContent; // 内容 39 | @property (nonatomic, copy) NSString *m_nsToUsr; // 接收的用户(微信id) 40 | @property (nonatomic, copy) NSString *m_nsFromUsr; // 发送的用户(微信id) 41 | @property (nonatomic, copy) NSString *m_nsLastDisplayContent; 42 | @property (nonatomic, assign) unsigned int m_uiCreateTime; // 消息生成时间 43 | @property (nonatomic, assign) unsigned int m_uiStatus; // 消息状态 44 | @property (nonatomic, assign) int m_uiMessageType; // 消息类型 45 | @property (nonatomic, copy) NSString *m_nsRealChatUsr; 46 | @property (nonatomic, copy) NSString *m_nsPushContent; 47 | - (id)initWithMsgType:(long long)arg1; 48 | @end 49 | 50 | @interface CBaseContact : NSObject 51 | @property (nonatomic, copy) NSString *m_nsEncodeUserName; // 微信用户名转码 52 | @property (nonatomic, assign) int m_uiFriendScene; // 是否是自己的好友(非订阅号、自己) 53 | @property (nonatomic,assign) BOOL m_isPlugin; // 是否为微信插件 54 | - (BOOL)isChatroom; 55 | @end 56 | 57 | @interface CContact : CBaseContact 58 | @property (nonatomic, copy) NSString *m_nsOwner; // 拥有者 59 | @property (nonatomic, copy) NSString *m_nsNickName; // 用户昵称 60 | @property (nonatomic, copy) NSString *m_nsUsrName; // 微信id 61 | @property (nonatomic, copy) NSString *m_nsMemberName; 62 | @end 63 | 64 | @interface CPushContact : CContact 65 | @property (nonatomic, copy) NSString *m_nsChatRoomUserName; 66 | @property (nonatomic, copy) NSString *m_nsDes; 67 | @property (nonatomic, copy) NSString *m_nsSource; 68 | @property (nonatomic, copy) NSString *m_nsSourceNickName; 69 | @property (nonatomic, copy) NSString *m_nsSourceUserName; 70 | @property (nonatomic, copy) NSString *m_nsTicket; 71 | - (BOOL)isMyContact; 72 | @end 73 | 74 | @interface CVerifyContactWrap : NSObject 75 | @property (nonatomic, copy) NSString *m_nsChatRoomUserName; 76 | @property (nonatomic, copy) NSString *m_nsOriginalUsrName; 77 | @property (nonatomic, copy) NSString *m_nsSourceNickName; 78 | @property (nonatomic, copy) NSString *m_nsSourceUserName; 79 | @property (nonatomic, copy) NSString *m_nsTicket; 80 | @property (nonatomic, copy) NSString *m_nsUsrName; 81 | @property (nonatomic, strong) CContact *m_oVerifyContact; 82 | @property (nonatomic, assign) unsigned int m_uiScene; 83 | @property (nonatomic, assign) unsigned int m_uiWCFlag; 84 | @end 85 | 86 | 87 | @interface GroupMember : NSObject 88 | @property(copy, nonatomic) NSString *m_nsNickName;; // @synthesize m_nsNickName; 89 | @property(nonatomic) unsigned int m_uiMemberStatus; // @synthesize m_uiMemberStatus; 90 | @property(copy, nonatomic) NSString *m_nsMemberName; // @synthesize m_nsMemberName; 91 | @end 92 | 93 | 94 | #pragma mark - Logic 95 | 96 | @interface SayHelloDataLogic : NSObject 97 | @property (nonatomic, strong) NSMutableArray *m_arrHellos; 98 | - (void)loadData:(unsigned int)arg1; 99 | + (id)getContactFrom:(id)arg1; 100 | - (id)getContactForIndex:(unsigned int)arg1; 101 | - (void)onFriendAssistAddMsg:(NSArray *)arg1; 102 | @end 103 | 104 | @interface CContactVerifyLogic : NSObject 105 | - (void)startWithVerifyContactWrap:(id)arg1 106 | opCode:(unsigned int)arg2 107 | parentView:(id)arg3 108 | fromChatRoom:(BOOL)arg4; 109 | @end 110 | 111 | @interface ContactsDataLogic : NSObject 112 | - (id)getKeysArray; 113 | - (BOOL)reloadContacts; 114 | - (BOOL)hasHistoryGroupContacts; 115 | - (id)getContactsArrayWith:(id)arg1; 116 | - (id)initWithScene:(unsigned int)arg1 delegate:(id)arg2 sort:(BOOL)arg3; 117 | @end 118 | 119 | @interface SKBuiltinString_t : NSObject 120 | // Remaining properties 121 | @property(copy, nonatomic) NSString *string; // @dynamic string; 122 | @end 123 | 124 | @interface CreateChatRoomResponse : NSObject 125 | @property(strong, nonatomic) SKBuiltinString_t *chatRoomName; // @dynamic chatRoomName; 126 | @end 127 | 128 | #pragma mark - Manager 129 | 130 | @interface MMNewSessionMgr : NSObject 131 | - (unsigned int)GenSendMsgTime; 132 | @end 133 | 134 | @interface CMessageMgr : NSObject 135 | - (id)GetMsg:(id)arg1 n64SvrID:(long long)arg2; 136 | - (void)onRevokeMsg:(id)msg; 137 | - (void)AddMsg:(id)arg1 MsgWrap:(id)arg2; 138 | - (void)AddLocalMsg:(id)arg1 MsgWrap:(id)arg2 fixTime:(_Bool)arg3 NewMsgArriveNotify:(_Bool)arg4; 139 | - (void)AsyncOnSpecialSession:(id)arg1 MsgList:(id)arg2; 140 | - (id)GetHelloUsers:(id)arg1 Limit:(unsigned int)arg2 OnlyUnread:(BOOL)arg3; 141 | - (void)AddEmoticonMsg:(NSString *)msg MsgWrap:(CMessageWrap *)msgWrap; 142 | // new 143 | - (void)addAutoVerifyWithArray:(NSArray *)ary arrayType:(TKArrayTpye)type; 144 | - (void)addAutoVerifyWithMessageInfo:(NSDictionary *)info; 145 | - (void)autoReplyWithMessageWrap:(CMessageWrap *)wrap; 146 | - (void)autoReplyChatRoomWithMessageWrap:(CMessageWrap *)wrap; 147 | - (void)sendMsg:(NSString *)msg toContactUsrName:(NSString *)userName; 148 | - (void)welcomeJoinChatRoomWithMessageWrap:(CMessageWrap *)wrap; 149 | - (void)removeMemberWithMessageWrap:(CMessageWrap *)wrap; 150 | @end 151 | 152 | @interface FriendAsistSessionMgr : NSObject 153 | - (id)GetLastMessage:(id)arg1 HelloUser:(id)arg2 OnlyTo:(BOOL)arg3; 154 | @end 155 | 156 | @interface AutoSetRemarkMgr : NSObject 157 | - (id)GetStrangerAttribute:(id)arg1 AttributeName:(int)arg2; 158 | @end 159 | 160 | @interface CContactMgr : NSObject 161 | - (id)getSelfContact; 162 | - (id)getContactByName:(id)arg1; 163 | - (id)getContactList:(unsigned int)arg1 contactType:(unsigned int)arg2; 164 | @end 165 | 166 | @interface MMServiceCenter : NSObject 167 | + (instancetype)defaultCenter; 168 | - (id)getService:(Class)service; 169 | @end 170 | 171 | @interface CGroupMgr : NSObject 172 | - (BOOL)SetChatRoomDesc:(id)arg1 Desc:(id)arg2 Flag:(unsigned int)arg3; 173 | - (BOOL)CreateGroup:(id)arg1 withMemberList:(id)arg2; 174 | - (_Bool)DeleteGroupMember:(id)arg1 withMemberList:(id)arg2 scene:(unsigned long long)arg3; 175 | @end 176 | 177 | #pragma mark - ViewController 178 | 179 | @interface MMUIViewController : UIViewController 180 | @end 181 | 182 | @interface NewSettingViewController: MMUIViewController 183 | 184 | @property(nonatomic, strong) MMTableViewInfo *m_tableViewInfo; // 185 | - (void)reloadTableData; 186 | @end 187 | 188 | @interface SayHelloViewController : UIViewController 189 | @property (nonatomic, strong) SayHelloDataLogic *m_DataLogic; 190 | - (void)OnSayHelloDataVerifyContactOK:(CPushContact *)arg1; 191 | @end 192 | 193 | #pragma mark - MMTableView 194 | 195 | @interface MMTableViewInfo 196 | - (id)getTableView; 197 | - (void)clearAllSection; 198 | - (void)addSection:(id)arg1; 199 | - (void)insertSection:(id)arg1 At:(unsigned int)arg2; 200 | - (id)getSectionAt:(unsigned int)arg1; 201 | @property(nonatomic,assign) id delegate; 202 | @end 203 | 204 | @interface MMTableViewSectionInfo : NSObject 205 | + (id)sectionInfoDefaut; 206 | + (id)sectionInfoHeader:(id)arg1; 207 | + (id)sectionInfoHeader:(id)arg1 Footer:(id)arg2; 208 | - (void)addCell:(id)arg1; 209 | - (void)removeCellAt:(unsigned long long)arg1; 210 | - (unsigned long long)getCellCount; 211 | @end 212 | 213 | @interface MMTableViewCellInfo 214 | + (id)normalCellForSel:(SEL)arg1 target:(id)arg2 title:(id)arg3 accessoryType:(long long)arg4; 215 | + (id)switchCellForSel:(SEL)arg1 target:(id)arg2 title:(id)arg3 on:(BOOL)arg4; 216 | + (id)normalCellForSel:(SEL)arg1 target:(id)arg2 title:(id)arg3 rightValue:(id)arg4 accessoryType:(long long)arg5; 217 | + (id)editorCellForSel:(SEL)arg1 target:(id)arg2 title:(id)arg3 margin:(double)arg4 tip:(id)arg5 focus:(_Bool)arg6 text:(id)arg7; 218 | + (id)normalCellForTitle:(id)arg1 rightValue:(id)arg2; 219 | + (id)urlCellForTitle:(id)arg1 url:(id)arg2; 220 | @property(nonatomic) long long editStyle; // @synthesize editStyle=_editStyle; 221 | @property(retain, nonatomic) id userInfo; 222 | @end 223 | 224 | @interface MMTableView: UITableView 225 | @end 226 | 227 | #pragma mark - UI 228 | 229 | @interface MMLoadingView : UIView 230 | - (void)setFitFrame:(long long)arg1; 231 | - (void)startLoading; 232 | - (void)stopLoading; 233 | - (void)stopLoadingAndShowError:(id)arg1; 234 | - (void)stopLoadingAndShowOK:(id)arg1; 235 | @property(retain, nonatomic) UILabel *m_label; 236 | @property (assign, nonatomic) BOOL m_bIgnoringInteractionEventsWhenLoading; 237 | @end 238 | 239 | @interface MMTextView : UITextView 240 | @property(retain, nonatomic) NSString *placeholder; 241 | @end 242 | 243 | @interface MMUICommonUtil : NSObject 244 | + (id)getBarButtonWithTitle:(id)arg1 target:(id)arg2 action:(SEL)arg3 style:(int)arg4; 245 | @end 246 | 247 | @interface SettingUtil : NSObject 248 | + (id)getLocalUsrName:(unsigned int)arg1; 249 | @end 250 | 251 | @interface ContactSelectView : UIView 252 | @property(nonatomic) _Bool m_bShowHistoryGroup; // @synthesize m_bShowHistoryGroup; 253 | @property(nonatomic) _Bool m_bShowRadarCreateRoom; // @synthesize m_bShowRadarCreateRoom; 254 | @property(nonatomic) _Bool m_bMultiSelect; // @synthesize m_bMultiSelect; 255 | @property(retain, nonatomic) NSDictionary *m_dicExistContact; // @synthesize m_dicExistContact; 256 | @property(retain, nonatomic) NSMutableDictionary *m_dicMultiSelect; // @synthesize m_dicMultiSelect; 257 | @property(nonatomic) unsigned int m_uiGroupScene; // @synthesize m_uiGroupScene; 258 | - (void)initView; 259 | - (void)initSearchBar; 260 | - (void)initData:(unsigned int)arg1; 261 | - (void)makeGroupCell:(id)arg1 head:(id)arg2 title:(id)arg3; 262 | - (void)addSelect:(id)arg1; 263 | @end 264 | 265 | #pragma mark - UICategory 266 | 267 | @interface UINavigationController (LogicController) 268 | - (void)PushViewController:(id)arg1 animated:(BOOL)arg2; 269 | @end 270 | 271 | @interface GameController : NSObject 272 | + (NSString*)getMD5ByGameContent:(NSInteger) content; 273 | @end 274 | -------------------------------------------------------------------------------- /src/zhFullView.h: -------------------------------------------------------------------------------- 1 | // 2 | // zhFullView.h 3 | // zhPopupControllerDemo 4 | // 5 | // Created by zhanghao on 2016/12/27. 6 | // Copyright © 2017年 zhanghao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class zhIconLabel, zhIconLabelModel; 12 | 13 | @interface zhFullView : UIView 14 | 15 | @property (assign) NSInteger rowCount; // 每行显示数 16 | @property (assign) NSInteger rows; // 每页显示行数 17 | 18 | @property (nonatomic, assign) CGSize itemSize; 19 | @property (nonatomic, strong) NSArray *models; 20 | @property (nonatomic, strong, readonly) NSMutableArray *items; 21 | 22 | @property (nonatomic, copy) void (^didClickFullView)(zhFullView *fullView); 23 | @property (nonatomic, copy) void (^didClickItems)(zhFullView *fullView, NSInteger index); 24 | 25 | - (void)endAnimationsCompletion:(void (^)(zhFullView *fullView))completion; // 动画结束后执行block 26 | 27 | - (instancetype)initWithFrame:(CGRect)frame andRows:(NSInteger)row; 28 | 29 | @end 30 | 31 | -------------------------------------------------------------------------------- /src/zhFullView.m: -------------------------------------------------------------------------------- 1 | // 2 | // zhFullView.m 3 | // zhPopupControllerDemo 4 | // 5 | // Created by zhanghao on 2016/12/27. 6 | // Copyright © 2017年 zhanghao. All rights reserved. 7 | // 8 | 9 | #import "zhFullView.h" 10 | #import "UIColor+Extend.h" 11 | #import "UIView+Layout.h" 12 | #import "UIScreen+Extend.h" 13 | #import "zhIconLabel.h" 14 | 15 | @interface zhFullView () { 16 | CGFloat _gap, _space; 17 | } 18 | @property (nonatomic, strong) UIButton *closeButton; 19 | @property (nonatomic, strong) UIButton *closeIcon; 20 | @property (nonatomic, strong) UIScrollView *scrollContainer; 21 | @property (nonatomic, strong) NSMutableArray *pageViews; 22 | 23 | @end 24 | 25 | @implementation zhFullView 26 | 27 | @synthesize rowCount; 28 | @synthesize rows; 29 | 30 | - (instancetype)initWithFrame:(CGRect)frame andRows:(NSInteger)row { 31 | if (self = [super initWithFrame:frame]) { 32 | self.rowCount = 3; 33 | self.rows = row; 34 | 35 | [self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(fullViewClicked:)]]; 36 | 37 | _closeButton = [UIButton new]; 38 | _closeButton.backgroundColor = [UIColor whiteColor]; 39 | _closeButton.userInteractionEnabled = NO; 40 | [_closeButton addTarget:self action:@selector(closeClicked:) forControlEvents:UIControlEventTouchUpInside]; 41 | [self addSubview:_closeButton]; 42 | 43 | _closeIcon = [UIButton new]; 44 | _closeIcon.userInteractionEnabled = NO; 45 | _closeIcon.imageView.contentMode = UIViewContentModeScaleAspectFit; 46 | [_closeIcon setImage:[UIImage imageNamed:@"popup_close_btn"] forState:UIControlStateNormal]; 47 | [self addSubview:_closeIcon]; 48 | 49 | [self setContent]; 50 | [self commonInitialization]; 51 | } 52 | return self; 53 | } 54 | 55 | - (instancetype)init { 56 | return [self initWithFrame:CGRectZero andRows:1]; 57 | } 58 | 59 | - (instancetype)initWithFrame:(CGRect)frame { 60 | return [self initWithFrame:frame andRows:1]; 61 | } 62 | 63 | - (void)setContent { 64 | _closeButton.size = CGSizeMake([UIScreen width], 44); 65 | _closeButton.bottom = [UIScreen height]; 66 | _closeIcon.size = CGSizeMake(30, 30); 67 | _closeIcon.center = _closeButton.center; 68 | } 69 | 70 | - (void)commonInitialization { 71 | _scrollContainer = [UIScrollView new]; 72 | _scrollContainer.bounces = NO; 73 | _scrollContainer.pagingEnabled = YES; 74 | _scrollContainer.showsHorizontalScrollIndicator = NO; 75 | _scrollContainer.delaysContentTouches = YES; 76 | _scrollContainer.delegate = self; 77 | [self addSubview:_scrollContainer]; 78 | 79 | _itemSize = CGSizeMake(60, 95); 80 | _gap = 15; 81 | _space = ([UIScreen width] - self.rowCount * _itemSize.width) / (self.rowCount + 1); 82 | 83 | _scrollContainer.size = CGSizeMake([UIScreen width], _itemSize.height * self.rows + _gap + 150); 84 | _scrollContainer.bottom = [UIScreen height] - _closeButton.height; 85 | _scrollContainer.contentSize = CGSizeMake([UIScreen width], _scrollContainer.height); 86 | 87 | _pageViews = @[].mutableCopy; 88 | UIImageView *pageView = [UIImageView new]; 89 | pageView.size = _scrollContainer.size; 90 | pageView.x = 0; 91 | pageView.userInteractionEnabled = YES; 92 | [_scrollContainer addSubview:pageView]; 93 | [_pageViews addObject:pageView]; 94 | } 95 | 96 | - (void)setModels:(NSArray *)models { 97 | _items = @[].mutableCopy; 98 | [_pageViews enumerateObjectsUsingBlock:^(UIImageView * _Nonnull imageView, NSUInteger idx, BOOL * _Nonnull stop) { 99 | for (NSInteger i = 0; i < self.rows * self.rowCount; i++) { 100 | NSInteger l = i % self.rowCount; 101 | NSInteger v = i / self.rowCount; 102 | 103 | zhIconLabel *item = [zhIconLabel new]; 104 | [imageView addSubview:item]; 105 | [_items addObject:item]; 106 | item.tag = i + idx * (self.rows *self.rowCount); 107 | if (item.tag < models.count) { 108 | [item addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(itemClicked:)]]; 109 | item.model = [models objectAtIndex:item.tag]; 110 | item.iconView.userInteractionEnabled = NO; 111 | item.textLabel.font = [UIFont systemFontOfSize:14]; 112 | item.textLabel.textColor = [UIColor r:82 g:82 b:82]; 113 | [item updateLayoutBySize:_itemSize finished:^(zhIconLabel *item) { 114 | item.x = _space + (_itemSize.width + _space) * l; 115 | item.y = (_itemSize.height + _gap) * v + _gap + 100; 116 | }]; 117 | } 118 | } 119 | }]; 120 | _models = models; 121 | [self startAnimationsCompletion:NULL]; 122 | } 123 | 124 | - (void)fullViewClicked:(UITapGestureRecognizer *)recognizer { 125 | zhFullView *_self = self; 126 | [self endAnimationsCompletion:^(zhFullView *fullView) { 127 | if (nil != self.didClickFullView) { 128 | _self.didClickFullView((zhFullView *)recognizer.view); 129 | } 130 | }]; 131 | } 132 | 133 | - (void)itemClicked:(UITapGestureRecognizer *)recognizer { 134 | if (nil != self.didClickItems) { 135 | self.didClickItems(self, recognizer.view.tag); 136 | } 137 | } 138 | 139 | - (void)closeClicked:(UIButton *)sender { 140 | [_scrollContainer setContentOffset:CGPointMake(0, 0) animated:YES]; 141 | } 142 | 143 | -(void)scrollViewDidScroll:(UIScrollView *)scrollView { 144 | NSInteger index = scrollView.contentOffset.x /[UIScreen width] + 0.5; 145 | _closeButton.userInteractionEnabled = index > 0; 146 | [_closeIcon setImage:[UIImage imageNamed:@"popup_close_btn"] forState:UIControlStateNormal]; 147 | } 148 | 149 | - (void)startAnimationsCompletion:(void (^ __nullable)(BOOL finished))completion { 150 | 151 | [UIView animateWithDuration:0.5 animations:^{ 152 | _closeIcon.transform = CGAffineTransformMakeRotation(M_PI_4); 153 | } completion:NULL]; 154 | 155 | [_items enumerateObjectsUsingBlock:^(zhIconLabel *item, NSUInteger idx, BOOL * _Nonnull stop) { 156 | item.alpha = 0; 157 | item.transform = CGAffineTransformMakeTranslation(0, self.rows * _itemSize.height); 158 | [UIView animateWithDuration:0.85 159 | delay:idx * 0.035 160 | usingSpringWithDamping:0.6 161 | initialSpringVelocity:0 162 | options:UIViewAnimationOptionCurveLinear 163 | animations:^{ 164 | item.alpha = 1; 165 | item.transform = CGAffineTransformIdentity; 166 | } completion:completion]; 167 | }]; 168 | } 169 | 170 | - (void)endAnimationsCompletion:(void (^)(zhFullView *))completion { 171 | if (!_closeButton.userInteractionEnabled) { 172 | [UIView animateWithDuration:0.35 animations:^{ 173 | _closeIcon.transform = CGAffineTransformIdentity; 174 | } completion:NULL]; 175 | } 176 | 177 | [_items enumerateObjectsUsingBlock:^(zhIconLabel * _Nonnull item, NSUInteger idx, BOOL * _Nonnull stop) { 178 | [UIView animateWithDuration:0.25 179 | delay:0.02f * (_items.count - idx) 180 | options:UIViewAnimationOptionCurveLinear 181 | animations:^{ 182 | 183 | item.alpha = 0; 184 | item.transform = CGAffineTransformMakeTranslation(0, self.rows * _itemSize.height); 185 | } completion:^(BOOL finished) { 186 | if (finished) { 187 | if (idx == _items.count - 1) { 188 | completion(self); 189 | } 190 | } 191 | }]; 192 | }]; 193 | } 194 | 195 | @end 196 | -------------------------------------------------------------------------------- /src/zhIconLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // zhIconLabel.h 3 | // zhPopupControllerDemo 4 | // 5 | // Created by zhanghao on 2016/9/26. 6 | // Copyright © 2017年 zhanghao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class zhIconLabelModel; 12 | 13 | @interface zhIconLabel : UIControl 14 | 15 | @property (nonatomic, strong, readonly) UIImageView *iconView; 16 | @property (nonatomic, strong, readonly) UILabel *textLabel; 17 | 18 | // UIEdgeInsets insets = {top, left, bottom, right} 19 | @property (nonatomic, assign) UIEdgeInsets imageEdgeInsets; // default = UIEdgeInsetsZero 使用insets.bottom或insets.right来调整间距 20 | 21 | @property (nonatomic, assign) BOOL horizontalLayout; // default is NO. 22 | 23 | @property (nonatomic, assign) BOOL autoresizingFlexibleSize; // default is NO. 根据内容适应自身高度 24 | 25 | @property (nonatomic, assign) CGFloat sizeLimit; // textLabel根据文本计算size时,如果纵向布局则限高,横向布局则限宽 26 | 27 | @property (nonatomic, strong) zhIconLabelModel *model; 28 | 29 | - (void)updateLayoutBySize:(CGSize)size finished:(void (^)(zhIconLabel *item))finished; // 属性赋值后需更新布局 30 | 31 | @end 32 | 33 | @interface zhIconLabelModel : NSObject 34 | 35 | @property (nonatomic, strong) UIImage *icon; 36 | @property (nonatomic, strong) NSString *text; 37 | 38 | + (instancetype)modelWithTitle:(NSString *)title image:(UIImage *)image; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /src/zhIconLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // zhIconLabel.m 3 | // zhPopupControllerDemo 4 | // 5 | // Created by zhanghao on 2016/9/26. 6 | // Copyright © 2017年 zhanghao. All rights reserved. 7 | // 8 | 9 | #import "zhIconLabel.h" 10 | 11 | @implementation zhIconLabel 12 | 13 | - (instancetype)init { 14 | return [self initWithFrame:CGRectZero]; 15 | } 16 | 17 | - (instancetype)initWithFrame:(CGRect)frame { 18 | if (self = [super initWithFrame:frame]) { 19 | _iconView = [[UIImageView alloc] init]; 20 | _iconView.userInteractionEnabled = YES; 21 | [self addSubview:_iconView]; 22 | 23 | _textLabel = [[UILabel alloc] init]; 24 | _textLabel.userInteractionEnabled = NO; 25 | _textLabel.numberOfLines = 0; 26 | _textLabel.textColor = [UIColor darkGrayColor]; 27 | _textLabel.font = [UIFont systemFontOfSize:12]; 28 | _textLabel.textAlignment = NSTextAlignmentCenter; 29 | [self addSubview:_textLabel]; 30 | 31 | _horizontalLayout = NO; 32 | _autoresizingFlexibleSize = NO; 33 | } 34 | return self; 35 | } 36 | 37 | /// 水平布局 38 | - (void)horizontalLayoutSubviews { 39 | 40 | CGFloat sideLength = self.frame.size.height - self.imageEdgeInsets.top - self.imageEdgeInsets.bottom; 41 | _iconView.frame = CGRectMake(self.imageEdgeInsets.left, self.imageEdgeInsets.top, sideLength, sideLength); 42 | 43 | if (_textLabel.text.length > 0) { 44 | 45 | CGFloat x = CGRectGetMaxX(_iconView.frame) + self.imageEdgeInsets.right; 46 | CGFloat h = self.frame.size.height; 47 | CGSize size = [_textLabel sizeThatFits:CGSizeMake(MAXFLOAT, h)]; 48 | CGFloat y = (self.frame.size.height - size.height) / 2; 49 | 50 | if (_autoresizingFlexibleSize) { 51 | if (_sizeLimit > 0) { // 限宽 52 | if (size.width > _sizeLimit) size.width = _sizeLimit; 53 | } 54 | _textLabel.frame = CGRectMake(x, y, size.width, size.height); 55 | 56 | CGRect frame = self.frame; 57 | frame.size.width = _textLabel.frame.origin.x + _textLabel.frame.size.width; 58 | self.frame = frame; 59 | } else { 60 | _textLabel.frame = CGRectMake(x, y, size.width, size.height); 61 | } 62 | 63 | } else { 64 | if (_autoresizingFlexibleSize) { 65 | CGRect frame = self.frame; 66 | frame.size.width = frame.size.height; 67 | self.frame = frame; 68 | } 69 | } 70 | } 71 | 72 | /// 纵向布局 73 | - (void)verticalLayoutSubviews { 74 | CGFloat sideLength = self.frame.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right; 75 | _iconView.frame = CGRectMake(self.imageEdgeInsets.left, self.imageEdgeInsets.top, sideLength, sideLength); 76 | 77 | if (_textLabel.text.length > 0) { 78 | 79 | CGFloat y = CGRectGetMaxY(_iconView.frame) + self.imageEdgeInsets.bottom; 80 | CGFloat w = self.frame.size.width; 81 | CGFloat h = self.frame.size.height - y; 82 | 83 | if (!_autoresizingFlexibleSize) { 84 | _textLabel.frame = CGRectMake(0, y, w, h); 85 | } else { 86 | 87 | CGSize size = [_textLabel sizeThatFits:CGSizeMake(w, h)]; 88 | CGFloat x = (self.frame.size.width - size.width) / 2; 89 | if (_sizeLimit > 0) { // 限高 90 | if (size.height > _sizeLimit) size.height = _sizeLimit; 91 | } 92 | _textLabel.frame = CGRectMake(x, y, size.width, size.height); 93 | 94 | CGRect frame = self.frame; 95 | frame.size.height = _textLabel.frame.origin.y + _textLabel.frame.size.height; 96 | self.frame = frame; 97 | } 98 | 99 | } else { 100 | if (_autoresizingFlexibleSize) { 101 | CGRect frame = self.frame; 102 | frame.size.height = frame.size.width; 103 | self.frame = frame; 104 | } 105 | } 106 | } 107 | 108 | - (void)setModel:(zhIconLabelModel *)model { 109 | _textLabel.text = model.text; 110 | _iconView.image = model.icon; 111 | _model = model; 112 | } 113 | 114 | - (void)updateLayoutBySize:(CGSize)size finished:(void (^)(zhIconLabel *))finished { 115 | CGRect frame = self.frame; 116 | frame.size = size; 117 | self.frame = frame; 118 | if (_horizontalLayout) { 119 | [self horizontalLayoutSubviews]; 120 | } else { 121 | [self verticalLayoutSubviews]; 122 | } 123 | finished(self); 124 | } 125 | 126 | @end 127 | 128 | @implementation zhIconLabelModel 129 | 130 | + (instancetype)modelWithTitle:(NSString *)title image:(UIImage *)image { 131 | zhIconLabelModel *model = [zhIconLabelModel new]; 132 | model.text = title; 133 | model.icon = image; 134 | return model; 135 | } 136 | 137 | @end 138 | -------------------------------------------------------------------------------- /src/zhPopupController.h: -------------------------------------------------------------------------------- 1 | // 2 | // zhPopupController.h 3 | // 4 | // 5 | // Created by zhanghao on 2016/11/15. 6 | // Copyright © 2017年 snail-z. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | // Control mask view of type. 14 | typedef NS_ENUM(NSUInteger, zhPopupMaskType) { 15 | zhPopupMaskTypeBlackBlur = 0, 16 | zhPopupMaskTypeWhiteBlur, 17 | zhPopupMaskTypeWhite, 18 | zhPopupMaskTypeClear, 19 | zhPopupMaskTypeBlackTranslucent // default 20 | }; 21 | 22 | // Control content View shows of layout type, when end of the animation. 23 | typedef NS_ENUM(NSUInteger, zhPopupLayoutType) { 24 | zhPopupLayoutTypeTop = 0, 25 | zhPopupLayoutTypeBottom, 26 | zhPopupLayoutTypeLeft, 27 | zhPopupLayoutTypeRight, 28 | zhPopupLayoutTypeCenter // default 29 | }; 30 | 31 | // Control content view from a direction sliding out of style. 32 | typedef NS_ENUM(NSInteger, zhPopupSlideStyle) { 33 | zhPopupSlideStyleFromTop = 0, 34 | zhPopupSlideStyleFromBottom, 35 | zhPopupSlideStyleFromLeft, 36 | zhPopupSlideStyleFromRight, 37 | zhPopupSlideStyleShrinkInOut, 38 | zhPopupSlideStyleFade // default 39 | }; 40 | 41 | @protocol zhPopupControllerDelegate; 42 | 43 | @interface zhPopupController : NSObject 44 | 45 | @property (nonatomic, strong) id delegate; 46 | 47 | /// Convenient to initialize and set maske type. (Through the `- init` initialization, maskType is zhPopupMaskTypeBlackTranslucent) 48 | + (instancetype)popupControllerWithMaskType:(zhPopupMaskType)maskType; 49 | 50 | /// The `popupView` is the parent view of your custom contentView 51 | @property (nonatomic, strong, readonly) UIView *popupView; 52 | 53 | /// Whether contentView is presenting. 54 | @property (nonatomic, assign, readonly) BOOL isPresenting; 55 | 56 | /// Set popup view display position. default is zhPopupLayoutTypeCenter 57 | @property (nonatomic, assign) zhPopupLayoutType layoutType; 58 | 59 | /// Set popup view slide Style. default is zhPopupSlideStyleFade 60 | @property (nonatomic, assign) zhPopupSlideStyle slideStyle; // When `layoutType = zhPopupLayoutTypeCenter` is vaild. 61 | 62 | /// set mask view of transparency, default is 0.5 63 | @property (nonatomic, assign) CGFloat maskAlpha; // When set maskType is zhPopupMaskTypeBlackTranslucent vaild. 64 | 65 | /// default is YES. if NO, Mask view will not respond to events. 66 | @property (nonatomic, assign) BOOL dismissOnMaskTouched; 67 | 68 | /// default is NO. if YES, Popup view disappear from the opposite direction. 69 | @property (nonatomic, assign) BOOL dismissOppositeDirection; // When `layoutType = zhPopupLayoutTypeCenter` is vaild. 70 | 71 | /// Content view whether to allow drag, default is NO 72 | @property (nonatomic, assign) BOOL allowPan; // 1.The view will support dragging when popup view of position is at the center of the screen or at the edge of the screen. 2.The pan gesture will be invalid when the keyboard appears. 73 | 74 | /// You can adjust the spacing relative to the keyboard when the keyboard appears. default is 0 75 | @property (nonatomic, assign) CGFloat offsetSpacingOfKeyboard; 76 | 77 | /// Use drop animation and set the rotation Angle. if set, Will not support drag. 78 | - (void)dropAnimatedWithRotateAngle:(CGFloat)angle; 79 | 80 | /// Block gets called when mask touched. 81 | @property (nonatomic, copy) void (^maskTouched)(zhPopupController *popupController); 82 | 83 | /// - Should implement this block before the presenting. 应在present前实现的block ☟ 84 | /// Block gets called when contentView will present. 85 | @property (nonatomic, copy) void (^willPresent)(zhPopupController *popupController); 86 | 87 | /// Block gets called when contentView did present. 88 | @property (nonatomic, copy) void (^didPresent)(zhPopupController *popupController); 89 | 90 | /// Block gets called when contentView will dismiss. 91 | @property (nonatomic, copy) void (^willDismiss)(zhPopupController *popupController); 92 | 93 | /// Block gets called when contentView did dismiss. 94 | @property (nonatomic, copy) void (^didDismiss)(zhPopupController *popupController); 95 | 96 | /** 97 | present your content view. 98 | @param contentView This is the view that you want to appear in popup. / 弹出自定义的contentView 99 | @param duration Popup animation time. / 弹出动画时长 100 | @param isSpringAnimated if YES, Will use a spring animation. / 是否使用弹性动画 101 | @param sView Displayed on the sView. if nil, Displayed on the window. / 显示在sView上 102 | @param displayTime The view will disappear after `displayTime` seconds. / 视图将在displayTime后消失 103 | */ 104 | - (void)presentContentView:(nullable UIView *)contentView 105 | duration:(NSTimeInterval)duration 106 | springAnimated:(BOOL)isSpringAnimated 107 | inView:(nullable UIView *)sView 108 | displayTime:(NSTimeInterval)displayTime; 109 | 110 | - (void)presentContentView:(nullable UIView *)contentView 111 | duration:(NSTimeInterval)duration 112 | springAnimated:(BOOL)isSpringAnimated 113 | inView:(nullable UIView *)sView; 114 | 115 | - (void)presentContentView:(nullable UIView *)contentView 116 | duration:(NSTimeInterval)duration 117 | springAnimated:(BOOL)isSpringAnimated; 118 | 119 | - (void)presentContentView:(nullable UIView *)contentView displayTime:(NSTimeInterval)displayTime;; 120 | 121 | - (void)presentContentView:(nullable UIView *)contentView; // duration is 0.25 / springAnimated is NO / show in window 122 | 123 | /// dismiss your content view. 124 | - (void)dismissWithDuration:(NSTimeInterval)duration springAnimated:(BOOL)isSpringAnimated; 125 | 126 | - (void)dismiss; // Will use the present parameter values. 127 | 128 | @end 129 | 130 | @protocol zhPopupControllerDelegate 131 | @optional 132 | // - The Delegate method, block is preferred. 133 | - (void)popupControllerWillPresent:(nonnull zhPopupController *)popupController; 134 | - (void)popupControllerDidPresent:(nonnull zhPopupController *)popupController; 135 | - (void)popupControllerWillDismiss:(nonnull zhPopupController *)popupController; 136 | - (void)popupControllerDidDismiss:(nonnull zhPopupController *)popupController; 137 | 138 | @end 139 | 140 | @interface NSObject (zhPopupController) 141 | 142 | @property (nonatomic, strong) zhPopupController *zh_popupController; // Suggested that direct use of zh_popupController. 143 | 144 | @end 145 | 146 | NS_ASSUME_NONNULL_END 147 | -------------------------------------------------------------------------------- /src/zhPopupController.m: -------------------------------------------------------------------------------- 1 | // 2 | // zhPopupController.m 3 | // 4 | // 5 | // Created by zhanghao on 2016/11/15. 6 | // Copyright © 2017年 snail-z. All rights reserved. 7 | // 8 | 9 | #import "zhPopupController.h" 10 | #import 11 | 12 | @interface zhPopupController () 13 | 14 | @property (nonatomic, strong, readonly) UIView *superview; 15 | @property (nonatomic, strong, readonly) UIView *maskView; 16 | @property (nonatomic, strong, readonly) UIView *contentView; 17 | @property (nonatomic, assign, readonly) CGFloat dropAngle; 18 | @property (nonatomic, assign, readonly) CGPoint markerCenter; 19 | @property (nonatomic, assign, readonly) zhPopupMaskType maskType; 20 | 21 | @end 22 | 23 | static void *zhPopupControllerParametersKey = &zhPopupControllerParametersKey; 24 | static void *zhPopupControllerNSTimerKey = &zhPopupControllerNSTimerKey; 25 | 26 | @implementation zhPopupController 27 | 28 | + (instancetype)popupControllerWithMaskType:(zhPopupMaskType)maskType { 29 | return [[self alloc] initWithMaskType:maskType]; 30 | } 31 | 32 | - (instancetype)init { 33 | return [self initWithMaskType:zhPopupMaskTypeBlackTranslucent]; 34 | } 35 | 36 | - (instancetype)initWithMaskType:(zhPopupMaskType)maskType { 37 | if (self = [super init]) { 38 | _isPresenting = NO; 39 | _maskType = maskType; 40 | _layoutType = zhPopupLayoutTypeCenter; 41 | _dismissOnMaskTouched = YES; 42 | 43 | // setter 44 | self.maskAlpha = 0.5f; 45 | self.slideStyle = zhPopupSlideStyleFade; 46 | self.dismissOppositeDirection = NO; 47 | self.allowPan = NO; 48 | 49 | // superview 50 | _superview = [self frontWindow]; 51 | 52 | // maskView 53 | if (maskType == zhPopupMaskTypeBlackBlur || maskType == zhPopupMaskTypeWhiteBlur) { 54 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 55 | _maskView = [[UIView alloc] initWithFrame:_superview.bounds]; 56 | UIVisualEffectView *visualEffectView; 57 | visualEffectView = [[UIVisualEffectView alloc] init]; 58 | visualEffectView.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; 59 | visualEffectView.frame = _superview.bounds; 60 | [_maskView insertSubview:visualEffectView atIndex:0]; 61 | #else 62 | _maskView = [[UIToolbar alloc] initWithFrame:_superview.bounds]; 63 | #endif 64 | } else { 65 | _maskView = [[UIView alloc] initWithFrame:_superview.bounds]; 66 | } 67 | 68 | switch (maskType) { 69 | case zhPopupMaskTypeBlackBlur: { 70 | if ([_maskView isKindOfClass:[UIToolbar class]]) { 71 | [(UIToolbar *)_maskView setBarStyle:UIBarStyleBlack]; 72 | } else { 73 | UIVisualEffectView *effectView = (UIVisualEffectView *)_maskView.subviews.firstObject; 74 | effectView.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; 75 | } 76 | } break; 77 | case zhPopupMaskTypeWhiteBlur: { 78 | if ([_maskView isKindOfClass:[UIToolbar class]]) { 79 | [(UIToolbar *)_maskView setBarStyle:UIBarStyleDefault]; 80 | } else { 81 | UIVisualEffectView *effectView = (UIVisualEffectView *)_maskView.subviews.firstObject; 82 | effectView.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; 83 | } 84 | } break; 85 | case zhPopupMaskTypeWhite: 86 | _maskView.backgroundColor = [UIColor whiteColor]; 87 | break; 88 | case zhPopupMaskTypeClear: 89 | _maskView.backgroundColor = [UIColor clearColor]; 90 | break; 91 | default: // zhPopupMaskTypeBlackTranslucent 92 | _maskView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:_maskAlpha]; 93 | break; 94 | } 95 | 96 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self 97 | action:@selector(handleTap:)]; 98 | tap.delegate = self; 99 | [_maskView addGestureRecognizer:tap]; 100 | 101 | // popupView 102 | _popupView = [[UIView alloc] init]; 103 | _popupView.backgroundColor = [UIColor clearColor]; 104 | 105 | // addSubview 106 | [_maskView addSubview:_popupView]; 107 | [_superview addSubview:_maskView]; 108 | 109 | // Observer statusBar orientation changes. 110 | [self bindNotificationEvent]; 111 | } 112 | return self; 113 | } 114 | 115 | #pragma mark - Setter 116 | 117 | - (void)setDismissOppositeDirection:(BOOL)dismissOppositeDirection { 118 | _dismissOppositeDirection = dismissOppositeDirection; 119 | objc_setAssociatedObject(self, _cmd, @(dismissOppositeDirection), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 120 | } 121 | 122 | - (void)setSlideStyle:(zhPopupSlideStyle)slideStyle { 123 | _slideStyle = slideStyle; 124 | objc_setAssociatedObject(self, _cmd, @(slideStyle), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 125 | } 126 | 127 | - (void)setMaskAlpha:(CGFloat)maskAlpha { 128 | if (_maskType != zhPopupMaskTypeBlackTranslucent) return; 129 | _maskAlpha = maskAlpha; 130 | _maskView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:_maskAlpha]; 131 | } 132 | 133 | - (void)setAllowPan:(BOOL)allowPan { 134 | if (!allowPan) return; 135 | if (_allowPan != allowPan) { 136 | _allowPan = allowPan; 137 | UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; 138 | [_popupView addGestureRecognizer:pan]; 139 | } 140 | } 141 | 142 | #pragma mark - Present 143 | 144 | - (void)presentContentView:(UIView *)contentView { 145 | [self presentContentView:contentView duration:0.25 springAnimated:NO]; 146 | } 147 | 148 | - (void)presentContentView:(UIView *)contentView displayTime:(NSTimeInterval)displayTime { 149 | [self presentContentView:contentView duration:0.25 springAnimated:NO inView:nil displayTime:displayTime]; 150 | } 151 | 152 | - (void)presentContentView:(UIView *)contentView duration:(NSTimeInterval)duration springAnimated:(BOOL)isSpringAnimated { 153 | [self presentContentView:contentView duration:duration springAnimated:isSpringAnimated inView:nil]; 154 | } 155 | 156 | - (void)presentContentView:(UIView *)contentView 157 | duration:(NSTimeInterval)duration 158 | springAnimated:(BOOL)isSpringAnimated 159 | inView:(UIView *)sView { 160 | [self presentContentView:contentView duration:duration springAnimated:isSpringAnimated inView:sView displayTime:0]; 161 | } 162 | 163 | - (void)presentContentView:(UIView *)contentView 164 | duration:(NSTimeInterval)duration 165 | springAnimated:(BOOL)isSpringAnimated 166 | inView:(UIView *)sView 167 | displayTime:(NSTimeInterval)displayTime { 168 | 169 | if (self.isPresenting) return; 170 | NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithCapacity:2]; 171 | [parameters setValue:@(duration) forKey:@"zh_duration"]; 172 | [parameters setValue:@(isSpringAnimated) forKey:@"zh_springAnimated"]; 173 | objc_setAssociatedObject(self, zhPopupControllerParametersKey, parameters, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 174 | 175 | if (nil != self.willPresent) { 176 | self.willPresent(self); 177 | } else { 178 | if ([self.delegate respondsToSelector:@selector(popupControllerWillPresent:)]) { 179 | [self.delegate popupControllerWillPresent:self]; 180 | } 181 | } 182 | 183 | if (nil != sView) { 184 | _superview = sView; 185 | _maskView.frame = _superview.frame; 186 | } 187 | [self addContentView:contentView]; 188 | if (![_superview.subviews containsObject:_maskView]) { 189 | [_superview addSubview:_maskView]; 190 | } 191 | 192 | [self prepareDropAnimated]; 193 | [self prepareBackground]; 194 | _popupView.userInteractionEnabled = NO; 195 | _popupView.center = [self prepareCenter]; 196 | 197 | void (^presentCompletion)(void) = ^() { 198 | _isPresenting = YES; 199 | _popupView.userInteractionEnabled = YES; 200 | if (nil != self.didPresent) { 201 | self.didPresent(self); 202 | } else { 203 | if ([self.delegate respondsToSelector:@selector(popupControllerDidPresent:)]) { 204 | [self.delegate popupControllerDidPresent:self]; 205 | } 206 | } 207 | 208 | if (displayTime) { 209 | NSTimer *timer = [NSTimer timerWithTimeInterval:displayTime target:self selector:@selector(dismiss) userInfo:nil repeats:NO]; 210 | [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 211 | objc_setAssociatedObject(self, zhPopupControllerNSTimerKey, timer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 212 | } 213 | }; 214 | 215 | if (isSpringAnimated) { 216 | [UIView animateWithDuration:duration delay:0.f usingSpringWithDamping:0.6 initialSpringVelocity:0.2 options:UIViewAnimationOptionCurveLinear animations:^{ 217 | 218 | [self finishedDropAnimated]; 219 | [self finishedBackground]; 220 | _popupView.center = [self finishedCenter]; 221 | 222 | } completion:^(BOOL finished) { 223 | 224 | if (finished) presentCompletion(); 225 | 226 | }]; 227 | } else { 228 | [UIView animateWithDuration:duration delay:0.f options:UIViewAnimationOptionCurveLinear animations:^{ 229 | 230 | [self finishedDropAnimated]; 231 | [self finishedBackground]; 232 | _popupView.center = [self finishedCenter]; 233 | 234 | } completion:^(BOOL finished) { 235 | 236 | if (finished) presentCompletion(); 237 | 238 | }]; 239 | } 240 | } 241 | 242 | #pragma mark - Dismiss 243 | 244 | - (void)dismiss { 245 | id object = objc_getAssociatedObject(self, zhPopupControllerParametersKey); 246 | if (object && [object isKindOfClass:[NSDictionary class]]) { 247 | NSTimeInterval duration = 0.0; 248 | NSNumber *durationNumber = [object valueForKey:@"zh_duration"]; 249 | if (nil != durationNumber) duration = durationNumber.doubleValue; 250 | BOOL flag = NO; 251 | NSNumber *flagNumber = [object valueForKey:@"zh_springAnimated"]; 252 | if (nil != flagNumber) flag = flagNumber.boolValue; 253 | [self dismissWithDuration:duration springAnimated:flag]; 254 | } 255 | } 256 | 257 | - (void)dismissWithDuration:(NSTimeInterval)duration springAnimated:(BOOL)isSpringAnimated { 258 | [self destroyTimer]; 259 | 260 | if (!self.isPresenting) return; 261 | 262 | if (nil != self.willDismiss) { 263 | self.willDismiss(self); 264 | } else { 265 | if ([self.delegate respondsToSelector:@selector(popupControllerWillDismiss:)]) { 266 | [self.delegate popupControllerWillDismiss:self]; 267 | } 268 | } 269 | 270 | void (^dismissCompletion)(void) = ^() { 271 | [self removeSubviews]; 272 | _isPresenting = NO; 273 | _popupView.transform = CGAffineTransformIdentity; 274 | if (nil != self.didDismiss) { 275 | self.didDismiss(self); 276 | } else { 277 | if ([self.delegate respondsToSelector:@selector(popupControllerDidDismiss:)]) { 278 | [self.delegate popupControllerDidDismiss:self]; 279 | } 280 | } 281 | }; 282 | 283 | UIViewAnimationOptions (^animOpts)(zhPopupSlideStyle) = ^(zhPopupSlideStyle slide){ 284 | if (slide != zhPopupSlideStyleShrinkInOut) { 285 | return UIViewAnimationOptionCurveLinear; 286 | } 287 | return UIViewAnimationOptionCurveEaseInOut; 288 | }; 289 | 290 | if (isSpringAnimated) { 291 | duration *= 0.75; 292 | NSTimeInterval duration1 = duration * 0.25, duration2 = duration - duration1; 293 | 294 | [UIView animateWithDuration:duration1 delay:0.f options:UIViewAnimationOptionCurveEaseInOut animations:^{ 295 | 296 | [self bufferBackground]; 297 | _popupView.center = [self bufferCenter:30]; 298 | 299 | } completion:^(BOOL finished) { 300 | [UIView animateWithDuration:duration2 delay:0.f options:animOpts(self.slideStyle) animations:^{ 301 | 302 | [self dismissedDropAnimated]; 303 | [self dismissedBackground]; 304 | _popupView.center = [self dismissedCenter]; 305 | 306 | } completion:^(BOOL finished) { 307 | if (finished) dismissCompletion(); 308 | }]; 309 | 310 | }]; 311 | 312 | } else { 313 | [UIView animateWithDuration:duration delay:0.f options:animOpts(self.slideStyle) animations:^{ 314 | 315 | [self dismissedDropAnimated]; 316 | [self dismissedBackground]; 317 | _popupView.center = [self dismissedCenter]; 318 | 319 | } completion:^(BOOL finished) { 320 | if (finished) dismissCompletion(); 321 | }]; 322 | } 323 | } 324 | 325 | #pragma mark - Add contentView 326 | 327 | - (void)addContentView:(UIView *)contentView { 328 | if (!contentView) { 329 | if (nil != _popupView.superview) [_popupView removeFromSuperview]; 330 | return; 331 | } 332 | _contentView = contentView; 333 | if (_contentView.superview != _popupView) { 334 | _contentView.frame = (CGRect){.origin = CGPointZero, .size = contentView.frame.size}; 335 | _popupView.frame = _contentView.frame; 336 | _popupView.backgroundColor = _contentView.backgroundColor; 337 | if (_contentView.layer.cornerRadius) { 338 | _popupView.layer.cornerRadius = _contentView.layer.cornerRadius; 339 | _popupView.clipsToBounds = NO; 340 | } 341 | [_popupView addSubview:_contentView]; 342 | } 343 | } 344 | 345 | - (void)removeSubviews { 346 | if (_popupView.subviews.count > 0) { 347 | [_contentView removeFromSuperview]; 348 | _contentView = nil; 349 | } 350 | [_maskView removeFromSuperview]; 351 | } 352 | 353 | #pragma mark - Drop animated 354 | 355 | - (void)dropAnimatedWithRotateAngle:(CGFloat)angle { 356 | _dropAngle = angle; 357 | _slideStyle = zhPopupSlideStyleFromTop; 358 | } 359 | 360 | - (BOOL)dropSupport { 361 | return (_layoutType == zhPopupLayoutTypeCenter && _slideStyle == zhPopupSlideStyleFromTop); 362 | } 363 | 364 | static CGFloat zh_randomValue(int i, int j) { 365 | if (arc4random() % 2) return i; 366 | return j; 367 | } 368 | 369 | - (void)prepareDropAnimated { 370 | if (_dropAngle && [self dropSupport]) { 371 | _dismissOppositeDirection = YES; 372 | CGFloat ty = (_maskView.bounds.size.height + _popupView.frame.size.height) / 2; 373 | CATransform3D transform = CATransform3DMakeTranslation(0, -ty, 0); 374 | transform = CATransform3DRotate(transform, 375 | zh_randomValue(_dropAngle, -_dropAngle) * M_PI / 180, 376 | 0, 0, 1.0); 377 | _popupView.layer.transform = transform; 378 | } 379 | } 380 | 381 | - (void)finishedDropAnimated { 382 | if (_dropAngle && [self dropSupport]) { 383 | _popupView.layer.transform = CATransform3DIdentity; 384 | } 385 | } 386 | 387 | - (void)dismissedDropAnimated { 388 | if (_dropAngle && [self dropSupport]) { 389 | CGFloat ty = _maskView.bounds.size.height; 390 | CATransform3D transform = CATransform3DMakeTranslation(0, ty, 0); 391 | transform = CATransform3DRotate(transform, 392 | zh_randomValue(_dropAngle, -_dropAngle) * M_PI / 180, 393 | 0, 0, 1.0); 394 | _popupView.layer.transform = transform; 395 | } 396 | } 397 | 398 | #pragma mark - Mask view background 399 | 400 | - (void)prepareBackground { 401 | switch (_maskType) { 402 | case zhPopupMaskTypeBlackBlur: 403 | case zhPopupMaskTypeWhiteBlur: 404 | _maskView.alpha = 1; 405 | break; 406 | default: 407 | _maskView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0]; 408 | break; 409 | } 410 | } 411 | 412 | - (void)finishedBackground { 413 | switch (_maskType) { 414 | case zhPopupMaskTypeBlackTranslucent: 415 | _maskView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:_maskAlpha]; 416 | break; 417 | case zhPopupMaskTypeWhite: 418 | _maskView.backgroundColor = [UIColor whiteColor]; 419 | break; 420 | case zhPopupMaskTypeClear: 421 | _maskView.backgroundColor = [UIColor clearColor]; 422 | break; 423 | default: break; 424 | } 425 | } 426 | 427 | - (void)bufferBackground { 428 | switch (_maskType) { 429 | case zhPopupMaskTypeBlackBlur: 430 | case zhPopupMaskTypeWhiteBlur: break; 431 | case zhPopupMaskTypeBlackTranslucent: 432 | _maskView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:_maskAlpha - _maskAlpha * 0.15]; 433 | break; 434 | default: break; 435 | } 436 | } 437 | 438 | - (void)dismissedBackground { 439 | switch (_maskType) { 440 | case zhPopupMaskTypeBlackBlur: 441 | case zhPopupMaskTypeWhiteBlur: 442 | _maskView.alpha = 0; 443 | break; 444 | default: 445 | _maskView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0]; 446 | break; 447 | } 448 | } 449 | 450 | #pragma mark - Center point 451 | 452 | - (CGPoint)prepareCenterFrom:(NSInteger)type viewRef:(UIView *)viewRef{ 453 | switch (type) { 454 | case 0: // top 455 | return CGPointMake(viewRef.center.x, 456 | -_popupView.bounds.size.height / 2) ; 457 | case 1: // bottom 458 | return CGPointMake(viewRef.center.x, 459 | _maskView.bounds.size.height + _popupView.bounds.size.height / 2); 460 | case 2: // left 461 | return CGPointMake(-_popupView.bounds.size.width / 2, 462 | viewRef.center.y); 463 | case 3: // right 464 | return CGPointMake(_maskView.bounds.size.width + _popupView.bounds.size.width / 2, 465 | viewRef.center.y); 466 | default: // center 467 | return _maskView.center; 468 | } 469 | } 470 | 471 | - (CGPoint)prepareCenter { 472 | if (_layoutType == zhPopupLayoutTypeCenter) { 473 | CGPoint point = _maskView.center; 474 | if (_slideStyle == zhPopupSlideStyleShrinkInOut) { 475 | _popupView.transform = CGAffineTransformMakeScale(0.1, 0.1); 476 | } else if (_slideStyle == zhPopupSlideStyleFade) { 477 | _maskView.alpha = 0; 478 | } else { 479 | point = [self prepareCenterFrom:_slideStyle viewRef:_maskView]; 480 | } 481 | return point; 482 | } 483 | return [self prepareCenterFrom:_layoutType viewRef:_maskView]; 484 | } 485 | 486 | - (CGPoint)finishedCenter { 487 | CGPoint point = _maskView.center; 488 | switch (_layoutType) { 489 | case zhPopupLayoutTypeTop: 490 | return CGPointMake(point.x, 491 | _popupView.bounds.size.height / 2); 492 | case zhPopupLayoutTypeBottom: 493 | return CGPointMake(point.x, 494 | _maskView.bounds.size.height - _popupView.bounds.size.height / 2); 495 | case zhPopupLayoutTypeLeft: 496 | return CGPointMake(_popupView.bounds.size.width / 2, 497 | point.y); 498 | case zhPopupLayoutTypeRight: 499 | return CGPointMake(_maskView.bounds.size.width - _popupView.bounds.size.width / 2, 500 | point.y); 501 | default: // zhPopupLayoutTypeCenter 502 | { 503 | if (_slideStyle == zhPopupSlideStyleShrinkInOut) { 504 | _popupView.transform = CGAffineTransformIdentity; 505 | } else if (_slideStyle == zhPopupSlideStyleFade) { 506 | _maskView.alpha = 1; 507 | } 508 | } 509 | return point; 510 | } 511 | } 512 | 513 | - (CGPoint)dismissedCenter { 514 | if (_layoutType != zhPopupLayoutTypeCenter) { 515 | return [self prepareCenterFrom:_layoutType viewRef:_popupView]; 516 | } 517 | switch (_slideStyle) { 518 | case zhPopupSlideStyleFromTop: 519 | return _dismissOppositeDirection ? 520 | CGPointMake(_popupView.center.x, 521 | _maskView.bounds.size.height + _popupView.bounds.size.height / 2) : 522 | CGPointMake(_popupView.center.x, 523 | -_popupView.bounds.size.height / 2); 524 | 525 | case zhPopupSlideStyleFromBottom: 526 | return _dismissOppositeDirection ? 527 | CGPointMake(_popupView.center.x, 528 | -_popupView.bounds.size.height / 2) : 529 | CGPointMake(_popupView.center.x, 530 | _maskView.bounds.size.height + _popupView.bounds.size.height / 2); 531 | 532 | case zhPopupSlideStyleFromLeft: 533 | return _dismissOppositeDirection ? 534 | CGPointMake(_maskView.bounds.size.width + _popupView.bounds.size.width / 2, 535 | _popupView.center.y) : 536 | CGPointMake(-_popupView.bounds.size.width / 2, 537 | _popupView.center.y); 538 | 539 | case zhPopupSlideStyleFromRight: 540 | return _dismissOppositeDirection ? 541 | CGPointMake(-_popupView.bounds.size.width / 2, 542 | _popupView.center.y) : 543 | CGPointMake(_maskView.bounds.size.width + _popupView.bounds.size.width / 2, 544 | _popupView.center.y); 545 | 546 | case zhPopupSlideStyleShrinkInOut: 547 | _popupView.transform = _dismissOppositeDirection ? 548 | CGAffineTransformMakeScale(1.95, 1.95) : 549 | CGAffineTransformMakeScale(0.05, 0.05); 550 | break; 551 | 552 | case zhPopupSlideStyleFade: 553 | _maskView.alpha = 0; 554 | default: break; 555 | } 556 | return _popupView.center; 557 | } 558 | 559 | #pragma mark - Buffer point 560 | 561 | - (CGPoint)bufferCenter:(CGFloat)move { 562 | CGPoint point = _popupView.center; 563 | switch (_layoutType) { 564 | case zhPopupLayoutTypeTop: 565 | point.y += move; 566 | break; 567 | case zhPopupLayoutTypeBottom: 568 | point.y -= move; 569 | break; 570 | case zhPopupLayoutTypeLeft: 571 | point.x += move; 572 | break; 573 | case zhPopupLayoutTypeRight: 574 | point.x -= move; 575 | break; 576 | case zhPopupLayoutTypeCenter: { 577 | switch (_slideStyle) { 578 | case zhPopupSlideStyleFromTop: 579 | point.y += _dismissOppositeDirection ? -move : move; 580 | break; 581 | case zhPopupSlideStyleFromBottom: 582 | point.y += _dismissOppositeDirection ? move : -move; 583 | break; 584 | case zhPopupSlideStyleFromLeft: 585 | point.x += _dismissOppositeDirection ? -move : move; 586 | break; 587 | case zhPopupSlideStyleFromRight: 588 | point.x += _dismissOppositeDirection ? move : -move; 589 | break; 590 | case zhPopupSlideStyleShrinkInOut: 591 | _popupView.transform = _dismissOppositeDirection ? 592 | CGAffineTransformMakeScale(0.95, 0.95) : 593 | CGAffineTransformMakeScale(1.05, 1.05); 594 | break; 595 | default: break; 596 | } 597 | } break; 598 | default: break; 599 | } 600 | return point; 601 | } 602 | 603 | #pragma mark - Destroy timer 604 | 605 | - (void)destroyTimer { 606 | id value = objc_getAssociatedObject(self, zhPopupControllerNSTimerKey); 607 | if (value) { 608 | [(NSTimer *)value invalidate]; 609 | objc_setAssociatedObject(self, zhPopupControllerNSTimerKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 610 | } 611 | } 612 | 613 | #pragma mark - FrontWindow 614 | 615 | - (UIWindow *)frontWindow { 616 | NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator]; 617 | for (UIWindow *window in frontToBackWindows) { 618 | BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen; 619 | BOOL windowIsVisible = !window.hidden && window.alpha > 0; 620 | BOOL windowLevelSupported = (window.windowLevel >= UIWindowLevelNormal && window.windowLevel <= UIWindowLevelNormal); 621 | BOOL windowKeyWindow = window.isKeyWindow; 622 | 623 | if(windowOnMainScreen && windowIsVisible && windowLevelSupported && windowKeyWindow) { 624 | return window; 625 | } 626 | } 627 | NSLog(@" ** zhPopupController ** Window is nil!"); 628 | return nil; 629 | } 630 | 631 | #pragma mark - Notifications 632 | 633 | - (void)bindNotificationEvent { 634 | [self unbindNotificationEvent]; 635 | 636 | [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; 637 | [[NSNotificationCenter defaultCenter] addObserver:self 638 | selector:@selector(willChangeStatusBarOrientation) 639 | name:UIApplicationWillChangeStatusBarOrientationNotification 640 | object:nil]; 641 | 642 | [[NSNotificationCenter defaultCenter] addObserver:self 643 | selector:@selector(didChangeStatusBarOrientation) 644 | name:UIApplicationDidChangeStatusBarOrientationNotification 645 | object:nil]; 646 | 647 | [[NSNotificationCenter defaultCenter] addObserver:self 648 | selector:@selector(keyboardWillChangeFrame:) 649 | name:UIKeyboardWillChangeFrameNotification 650 | object:nil]; 651 | } 652 | 653 | - (void)unbindNotificationEvent { 654 | [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; 655 | 656 | [[NSNotificationCenter defaultCenter]removeObserver:self 657 | name:UIApplicationWillChangeStatusBarOrientationNotification 658 | object:nil]; 659 | 660 | [[NSNotificationCenter defaultCenter]removeObserver:self 661 | name:UIApplicationDidChangeStatusBarOrientationNotification 662 | object:nil]; 663 | 664 | [[NSNotificationCenter defaultCenter] removeObserver:self 665 | name:UIKeyboardWillChangeFrameNotification 666 | object:nil]; 667 | } 668 | 669 | #pragma mark - Observing 670 | 671 | - (void)keyboardWillChangeFrame:(NSNotification*)notification { 672 | 673 | _allowPan = NO; // The pan gesture will be invalid when the keyboard appears. 674 | 675 | NSDictionary *userInfo = notification.userInfo; 676 | CGRect keyboardRect = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; 677 | keyboardRect = [_maskView convertRect:keyboardRect fromView:nil]; 678 | CGFloat keyboardHeight = CGRectGetHeight(_maskView.bounds) - CGRectGetMinY(keyboardRect); 679 | 680 | UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; 681 | UIViewAnimationOptions options = curve << 16; 682 | 683 | NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; 684 | 685 | [UIView animateWithDuration:duration delay:0 options:options animations:^{ 686 | if (keyboardHeight > 0) { 687 | 688 | CGFloat offsetSpacing = self.offsetSpacingOfKeyboard, changeHeight = 0; 689 | 690 | switch (_layoutType) { 691 | case zhPopupLayoutTypeTop: 692 | break; 693 | case zhPopupLayoutTypeBottom: 694 | changeHeight = keyboardHeight + offsetSpacing; 695 | break; 696 | default: 697 | changeHeight = (keyboardHeight / 2) + offsetSpacing; 698 | break; 699 | } 700 | 701 | if (!CGPointEqualToPoint(CGPointZero, _markerCenter)) { 702 | _popupView.center = CGPointMake(_markerCenter.x, _markerCenter.y - changeHeight); 703 | } else { 704 | _popupView.center = CGPointMake(_popupView.center.x, _popupView.center.y - changeHeight); 705 | } 706 | 707 | } else { 708 | if (self.isPresenting) { 709 | _popupView.center = [self finishedCenter]; 710 | } 711 | } 712 | } completion:^(BOOL finished) { 713 | _markerCenter = [self finishedCenter]; 714 | }]; 715 | } 716 | 717 | - (void)willChangeStatusBarOrientation { 718 | _maskView.frame = _superview.bounds; 719 | _popupView.center = [self finishedCenter]; 720 | [self dismiss]; 721 | } 722 | 723 | - (void)didChangeStatusBarOrientation { 724 | if ([[UIDevice currentDevice].systemVersion compare:@"8.0" options:NSNumericSearch] == NSOrderedAscending) { // must manually fix orientation prior to iOS 8 725 | CGFloat angle; 726 | switch ([UIApplication sharedApplication].statusBarOrientation) 727 | { 728 | case UIInterfaceOrientationPortraitUpsideDown: 729 | angle = M_PI; 730 | break; 731 | case UIInterfaceOrientationLandscapeLeft: 732 | angle = -M_PI_2; 733 | break; 734 | case UIInterfaceOrientationLandscapeRight: 735 | angle = M_PI_2; 736 | break; 737 | default: // as UIInterfaceOrientationPortrait 738 | angle = 0.0; 739 | break; 740 | } 741 | _popupView.transform = CGAffineTransformMakeRotation(angle); 742 | } 743 | _maskView.frame = _superview.bounds; 744 | _popupView.center = [self finishedCenter]; 745 | } 746 | 747 | #pragma mark - Gesture Recognizer 748 | 749 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { 750 | if ([touch.view isDescendantOfView:_popupView]) return NO; 751 | return YES; 752 | } 753 | 754 | - (void)handleTap:(UITapGestureRecognizer *)g { 755 | if (_dismissOnMaskTouched) { 756 | if (!_dropAngle) { 757 | id object = objc_getAssociatedObject(self, @selector(setSlideStyle:)); 758 | _slideStyle = [object integerValue]; 759 | id obj = objc_getAssociatedObject(self, @selector(setDismissOppositeDirection:)); 760 | _dismissOppositeDirection = [obj boolValue]; 761 | } 762 | if (nil != self.maskTouched) { 763 | self.maskTouched(self); 764 | } else { 765 | [self dismiss]; 766 | } 767 | } 768 | } 769 | 770 | - (void)handlePan:(UIPanGestureRecognizer *)g { 771 | if (!_allowPan || !_isPresenting || _dropAngle) { 772 | return; 773 | } 774 | CGPoint translation = [g translationInView:_maskView]; 775 | switch (g.state) { 776 | case UIGestureRecognizerStateBegan: 777 | break; 778 | case UIGestureRecognizerStateChanged: { 779 | switch (_layoutType) { 780 | case zhPopupLayoutTypeCenter: { 781 | 782 | BOOL isTransformationVertical = NO; 783 | switch (_slideStyle) { 784 | case zhPopupSlideStyleFromLeft: 785 | case zhPopupSlideStyleFromRight: break; 786 | default: 787 | isTransformationVertical = YES; 788 | break; 789 | } 790 | 791 | NSInteger factor = 4; // set screen ratio `_maskView.bounds.size.height / factor` 792 | CGFloat changeValue; 793 | if (isTransformationVertical) { 794 | g.view.center = CGPointMake(g.view.center.x, g.view.center.y + translation.y); 795 | changeValue = g.view.center.y / (_maskView.bounds.size.height / factor); 796 | } else { 797 | g.view.center = CGPointMake(g.view.center.x + translation.x, g.view.center.y); 798 | changeValue = g.view.center.x / (_maskView.bounds.size.width / factor); 799 | } 800 | CGFloat alpha = factor / 2 - fabs(changeValue - factor / 2); 801 | [UIView animateWithDuration:0.15 animations:^{ 802 | _maskView.alpha = alpha; 803 | } completion:NULL]; 804 | 805 | } break; 806 | case zhPopupLayoutTypeBottom: { 807 | if (g.view.frame.origin.y + translation.y > _maskView.bounds.size.height - g.view.bounds.size.height) { 808 | g.view.center = CGPointMake(g.view.center.x, g.view.center.y + translation.y); 809 | } 810 | } break; 811 | case zhPopupLayoutTypeTop: { 812 | if (g.view.frame.origin.y + g.view.frame.size.height + translation.y < g.view.bounds.size.height) { 813 | g.view.center = CGPointMake(g.view.center.x, g.view.center.y + translation.y); 814 | } 815 | } break; 816 | case zhPopupLayoutTypeLeft: { 817 | if (g.view.frame.origin.x + g.view.frame.size.width + translation.x < g.view.bounds.size.width) { 818 | g.view.center = CGPointMake(g.view.center.x + translation.x, g.view.center.y); 819 | } 820 | } break; 821 | case zhPopupLayoutTypeRight: { 822 | if (g.view.frame.origin.x + translation.x > _maskView.bounds.size.width - g.view.bounds.size.width) { 823 | g.view.center = CGPointMake(g.view.center.x + translation.x, g.view.center.y); 824 | } 825 | } break; 826 | default: break; 827 | } 828 | [g setTranslation:CGPointZero inView:_maskView]; 829 | } break; 830 | case UIGestureRecognizerStateEnded: { 831 | 832 | BOOL isWillDismiss = YES, isStyleCentered = NO; 833 | switch (_layoutType) { 834 | case zhPopupLayoutTypeCenter: { 835 | isStyleCentered = YES; 836 | if (g.view.center.y != _maskView.center.y) { 837 | if (g.view.center.y > _maskView.bounds.size.height * 0.25 && 838 | g.view.center.y < _maskView.bounds.size.height * 0.75) { 839 | isWillDismiss = NO; 840 | } 841 | } else { 842 | if (g.view.center.x > _maskView.bounds.size.width * 0.25 && 843 | g.view.center.x < _maskView.bounds.size.width * 0.75) { 844 | isWillDismiss = NO; 845 | } 846 | } 847 | } break; 848 | case zhPopupLayoutTypeBottom: 849 | isWillDismiss = g.view.frame.origin.y > _maskView.bounds.size.height - g.view.frame.size.height * 0.75; 850 | break; 851 | case zhPopupLayoutTypeTop: 852 | isWillDismiss = g.view.frame.origin.y + g.view.frame.size.height < g.view.frame.size.height * 0.75; 853 | break; 854 | case zhPopupLayoutTypeLeft: 855 | isWillDismiss = g.view.frame.origin.x + g.view.frame.size.width < g.view.frame.size.width * 0.75; 856 | break; 857 | case zhPopupLayoutTypeRight: 858 | isWillDismiss = g.view.frame.origin.x > _maskView.bounds.size.width - g.view.frame.size.width * 0.75; 859 | break; 860 | default: break; 861 | } 862 | if (isWillDismiss) { 863 | if (isStyleCentered) { 864 | switch (_slideStyle) { 865 | case zhPopupSlideStyleShrinkInOut: 866 | case zhPopupSlideStyleFade: { 867 | if (g.view.center.y < _maskView.bounds.size.height * 0.25) { 868 | _slideStyle = zhPopupSlideStyleFromTop; 869 | } else { 870 | if (g.view.center.y > _maskView.bounds.size.height * 0.75) { 871 | _slideStyle = zhPopupSlideStyleFromBottom; 872 | } 873 | } 874 | _dismissOppositeDirection = NO; 875 | } break; 876 | case zhPopupSlideStyleFromTop: 877 | _dismissOppositeDirection = !(g.view.center.y < _maskView.bounds.size.height * 0.25); 878 | break; 879 | case zhPopupSlideStyleFromBottom: 880 | _dismissOppositeDirection = g.view.center.y < _maskView.bounds.size.height * 0.25; 881 | break; 882 | case zhPopupSlideStyleFromLeft: 883 | _dismissOppositeDirection = !(g.view.center.x < _maskView.bounds.size.width * 0.25); 884 | break; 885 | case zhPopupSlideStyleFromRight: 886 | _dismissOppositeDirection = g.view.center.x < _maskView.bounds.size.width * 0.25; 887 | break; 888 | default: break; 889 | } 890 | } 891 | 892 | [self dismissWithDuration:0.25f springAnimated:NO]; 893 | 894 | } else { 895 | // restore view location. 896 | id object = objc_getAssociatedObject(self, zhPopupControllerParametersKey); 897 | NSNumber *flagNumber = [object valueForKey:@"zh_springAnimated"]; 898 | BOOL flag = NO; 899 | if (nil != flagNumber) { 900 | flag = flagNumber.boolValue; 901 | } 902 | NSTimeInterval duration = 0.25; 903 | NSNumber* durationNumber = [object valueForKey:@"zh_duration"]; 904 | if (nil != durationNumber) { 905 | duration = durationNumber.doubleValue; 906 | } 907 | if (flag) { 908 | [UIView animateWithDuration:duration delay:0.0 usingSpringWithDamping:0.6f initialSpringVelocity:0.2 options:UIViewAnimationOptionCurveLinear animations:^{ 909 | g.view.center = [self finishedCenter]; 910 | } completion:NULL]; 911 | } else { 912 | [UIView animateWithDuration:duration delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 913 | g.view.center = [self finishedCenter]; 914 | } completion:NULL]; 915 | } 916 | } 917 | 918 | } break; 919 | case UIGestureRecognizerStateCancelled: 920 | break; 921 | default: break; 922 | } 923 | } 924 | 925 | - (void)dealloc { 926 | [super dealloc]; 927 | [self unbindNotificationEvent]; 928 | [self removeSubviews]; 929 | } 930 | 931 | @end 932 | 933 | @implementation NSObject (zhPopupController) 934 | 935 | - (zhPopupController *)zh_popupController { 936 | id popupController = objc_getAssociatedObject(self, _cmd); 937 | if (nil == popupController) { 938 | popupController = [[zhPopupController alloc] init]; 939 | self.zh_popupController = popupController; 940 | } 941 | return popupController; 942 | } 943 | 944 | - (void)setZh_popupController:(zhPopupController *)zh_popupController { 945 | objc_setAssociatedObject(self, @selector(zh_popupController), zh_popupController, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 946 | } 947 | 948 | @end 949 | --------------------------------------------------------------------------------