黑魔法到底有多强大?
第二篇 融云会话列表Cell标题显示富文本、添加图片
TIPS:我们可能都看过无数的文章,讲解在OC中如何实现Method Swizzling 交换方法,但是在实际应用中,好多人可能想不到这一点。所以我准备写一系列利用黑魔法才能实现的需求的文章。
我的工程里接入了融云SDK,并使用了其界面,这样便于快速开发。
现在因为用户群体的区分,我们要求在昵称的后面,添加一个图片标识。
于是按照融云的教程,在控制器的
-willDisplayConversationTableCell:atIndexPath: 方法中,修改 cell 的 conversationTitle 这个 Label。
但是细心的人肯定会发现,第一次走这个方法的时候,conversationTitle 是没有 frame 或者 text 为空的。
之后我甚至给融云提交了工单,问他们,他们的回答是可以通过自定义会话列表的cell来实现我的需求。后来我想想,会话还有好多状态,比如聊天置顶,消息免打扰等等,很麻烦。于是我就打印了 RCConversationCell 的方法列表。于是就有了下文。
在 RCConversationCell 里面,除了 -setDataModel: 会修改conversationTitle 的 text 之外,还有 -onUserInfoUpdate: 这个通知的方法会修改。如此一来,我们只需要 hook 上面两个方法,再加上我们自己的判断,来确定是否添加图片就好了。
1、创建 RCConversationCell 的 Category,我这里命名为 RCConversationCell+Swizzling
2、重写 +load 方法,hook 上述两个方法,并在这两个方法执行完毕之后,添加我们的代码,上代码~
#import "RCConversationCell+Swizzling.h"
@implementation RCConversationCell (Swizzling)
+ (void)load {
[super load];
Method setDataOriginal = class_getInstanceMethod([self class], @selector(setDataModel:));
Method setDataNew = class_getInstanceMethod([self class], @selector(swizzlingSetDataModel:));
if (!class_addMethod([self class], @selector(setDataModel:), method_getImplementation(setDataNew), method_getTypeEncoding(setDataNew))) {
method_exchangeImplementations(setDataOriginal, setDataNew);
}
Method userInfoOriginal = class_getInstanceMethod([self class], NSSelectorFromString(@"onUserInfoUpdate:"));
Method userInfoNew = class_getInstanceMethod([self class], @selector(swizzlingOnUserInfoUpdate:));
if (!class_addMethod([self class], NSSelectorFromString(@"onUserInfoUpdate:"), method_getImplementation(userInfoNew), method_getTypeEncoding(userInfoNew))) {
method_exchangeImplementations(userInfoOriginal, userInfoNew);
}
}
- (void)swizzlingOnUserInfoUpdate:(id)userInfo {
[self swizzlingOnUserInfoUpdate:userInfo];
dispatch_main_async_safe((^{
[self configureConversationTitleLabel];
}))
}
- (void)swizzlingSetDataModel:(RCConversationModel *)model {
[self swizzlingSetDataModel:model];
[self configureConversationTitleLabel];
}
- (void)configureConversationTitleLabel {
NSString *originTitle = self.conversationTitle.text;
if (!originTitle) {
return;
}
self.conversationTitle.font = [UIFont systemFontOfSize:kFontSize(16)];
self.conversationTitle.textColor = kBlackTextColor();
[SCUserTool getUserInfoWithID:self.model.targetId refresh:NO complete:^(SCUser *user) {
if (!user) {
return;
}
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:user.displayName];
if (user.isCustomer) {
self.conversationTitle.textColor = [UIColor colorWithHexString:@"#9013FE" alpha:1.0];
NSTextAttachment *att = [[NSTextAttachment alloc] init];
att.bounds = CGRectMake(0, kRealValue(-2), kRealValue(16), kRealValue(16));
att.image = [UIImage imageNamed:kChatImageName(@"chat_service")];
NSAttributedString *img = [NSAttributedString attributedStringWithAttachment:att];
[str appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]];
[str appendAttributedString:img];
}
if ([user.userId hasPrefix:@"agent"]) {
self.conversationTitle.textColor = [UIColor colorWithHexString:@"#FFC700" alpha:1.0];
NSTextAttachment *att = [[NSTextAttachment alloc] init];
att.bounds = CGRectMake(0, kRealValue(-2), kRealValue(16), kRealValue(16));
att.image = [UIImage imageNamed:kChatImageName(@"chat_delegate")];
NSAttributedString *img = [NSAttributedString attributedStringWithAttachment:att];
[str appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]];
[str appendAttributedString:img];
}
self.conversationTitle.attributedText = str;
}];
}
PS:使用 NSSelectorFromString 是因为 -onUserInfoUpdate 是私有方法。我打印了 RCConversationCell 的所有方法名,感觉有可能会修改 conversationTitle 的 text 的方法,然后慢慢试出来的。也不容易哈~
如此一来,就实现了回话列表中会话标题显示富文本的需求~
顺便提一下,在会话页面,导航标题也需要用富文本的话,可以通过设置 self.navigationItem.titleView 来实现。因为是 navigationItem ,所以不会影响下一个 push 或 pop 回去的控制器。
说到 UINavigationItem ,可以移步这里:
关于UINavigationBar 和 UINavigationItem
里面简单介绍了一下它的作用。