黑魔法到底有多强大?
第一篇 各种第三方SDK私有控制器的导航条
TIPS:我们可能都看过无数的文章,讲解在OC中如何实现Method Swizzling 交换方法,但是在实际应用中,好多人可能想不到这一点。所以我准备写一系列利用黑魔法才能实现的需求的文章。
我们在集成一些第三方SDK时,尤其是带有UI界面的第三方。它的导航条风格跟我们App中的不一样,我们当然希望以我们App本身的导航条风格为主了。甚至包括一些系统的界面,比如 UIImagePickerController 等。
现在我就以 融云SDK 和 UIImagePickerController 作为实例来实现这个需求。
说到这里,不得不提一下 UIAppearance 协议,这个协议的作用是设置一些全局属性,在App生命周期内,所有当前类的对象都会被设置某属性,举例说明:
[UIButton appearance].exclusiveTouch = YES;
[UITextField appearance].tintColor = kMainColor();
这表示,App内所有的按钮都不能同时点击,所有的文本输入框,光标的渲染颜色为kMainColor()
那么可能就有人说了,直接利用 [UINavigationBar appearance] 来设置不就好了?这样并不完美,这样的话所有的导航条都会变成你所设置的风格。那如果App内有多种导航条风格的话,这个方式就不合适了。
那么我们将通过黑魔法来实现这个需求,其实非常简单。
1、创建 UIViewController 的 Category ,这里我命名为 UIViewController+Swizzling
2、在 .m 中,重写 +load 方法,来 hook -viewWillAppear: 方法,为什么不hook viewDidLoad呢?有些控制器会在 appear 的时候对导航条进行修改,所以我们最终也在其本身的 -viewWillAppear: 执行完毕之后,才操作我们的导航条。上代码~
#import "UIViewController+Swizzling.h"
@implementation UIViewController (Swizzling)
+ (void)load {
[super load];
//原本的willAppear方法
Method willAppearOriginal = class_getInstanceMethod([self class], @selector(viewWillAppear:));
//我们的willAppear方法
Method willAppearNew = class_getInstanceMethod([self class], @selector(swizzlingViewWillAppear:));
//交换
if (!class_addMethod([self class], @selector(viewWillAppear:), method_getImplementation(willAppearNew), method_getTypeEncoding(willAppearNew))) {
method_exchangeImplementations(willAppearOriginal, willAppearNew);
}
}
- (void)swizzlingViewWillAppear:(BOOL)animated {
[self swizzlingViewWillAppear:animated];
if ([self isMemberOfClass:NSClassFromString(@"RCPhotosPickerController")]) {
[self configureRongCloudNavigation];
UICollectionView *collectionView = self.view.subviews.firstObject;
collectionView.contentInset = UIEdgeInsetsMake(kNavHeight(), 0, 0, 0);
}
if ([self isMemberOfClass:NSClassFromString(@"RCAlumListTableViewController")] || [self isMemberOfClass:NSClassFromString(@"RCLocationPickerViewController")] || [self isMemberOfClass:NSClassFromString(@"RCLocationViewController")] || [self isMemberOfClass:NSClassFromString(@"PUPhotoPickerHostViewController")]) {
[self configureRongCloudNavigation];
}
}
- (void)configureRongCloudNavigation {
self.navigationController.navigationBar.shadowImage = [[UIImage alloc] init];
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:kNavImageName(@"nav_bg")] forBarMetrics:UIBarMetricsDefault];
self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
[self.navigationController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor whiteColor], NSFontAttributeName:[UIFont boldSystemFontOfSize:kFontSize(18)]}];
}
这里我的项目中集成了融云SDK,但是我需要将它里面的一些界面的导航条风格跟我App本身的保持一致。
PS:那些控制器的类型如何获取?可以再hook -viewDidAppear: 方法,来NSLog类名就ok了。
这里使用 NSClassFromString() 来显示类名,因为融云本身并没有这些私有类的头文件。
里面获取到 UICollectionView 的原理跟获取类型的方式是一样的,打印所有子视图。
到这里,我们的需求就完成了,是不是非常简单呢~