前言
下图为目前市面上一种常见的聊天会话Cell结构图,如果模糊可以点击放大。
其中主要的自定义媒体区域为CustomContentView,涵盖大部分媒体类型的展示。

曾经做IM以及朋友圈类似功能的时候,遇到过一个最疑惑的细节问题:
如何实现UIMenuController的指向视图保持选中高亮状态
如下图对比效果:


虽然这只是一个细节,但是我们常说:细节决定成败。
过程
一切问题都是会则不难,难则不会。思考这个问题的过程中,走了不少弯路,曾经想过多种可能解决的方法:
- 利用cell的selected状态,在selected方法中做操作。(尝试失败)
- 系统自带tableViewcell触发menu高亮效果,来实现这个操作。(尝试失败)
- 联系腾讯的相关iOS开发人员问一问。(没去试,谁有门路吗- -)
- 其他忘了。
这个问题搁置了许久,虽然暂时没有好的思路,但是也一直没有放弃这个问题。
一次机缘巧合,鼠标点了一下这个东西:

原来是可以拿到这个customContentView的!
不过UIMenuController里没有直接提供接口,如图:

但是iOS开发者都知道,经常需要用到KVC来取值。没错,我们能看到有这个targetView就可以尝试用 valueForKey:来获取这个视图。
注意,这种获取_targetView的方法在iOS11之前会导致崩溃,所以可以在控制器触发onLongPressCell:里用一个全局变量记录这个View。
思路理清以后,事实证明这种方法是可行的,终于解决了这个困扰许久的问题。
实现
1. 对cell添加长按事件,可以用delegate或者block返回事件传给ViewController。
- (void)longGesturePress:(UIGestureRecognizer*)gestureRecognizer {
if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] &&
gestureRecognizer.state == UIGestureRecognizerStateBegan) {
if (_messageDelegate && [_messageDelegate respondsToSelector:@selector(onLongPressCell:inView:)]) {
// 实现来自ViewController的messageDelegate 并将 CustomContentView传递
[_messageDelegate onLongPressCell:self.model.message inView:_customContentView];
}
}
}
接下来,ViewController立刻来响应这个代理方法。 侍从,来我身边!
- (void)onLongPressCell:(NIMMessage *)message inView:(UIView *)view {
NSArray *items = [self menusItems:message];
if ([items count] && [self becomeFirstResponder]) {
UIMenuController *controller = [UIMenuController sharedMenuController];
controller.menuItems = items;
self.messageForMenu = message;
self.menuTargetView = view; // 记录该被点击的视图
[controller setTargetRect:view.bounds inView:view]; // 这一步很关键
[controller setMenuVisible:YES animated:YES];
}
}
2. ViewController添加监听UIMenuController的通知。
// 这5种NSNotificationName都是UIMenuController的系统自带监听方案
UIKIT_EXTERN NSNotificationName const UIMenuControllerWillShowMenuNotification __TVOS_PROHIBITED;
UIKIT_EXTERN NSNotificationName const UIMenuControllerDidShowMenuNotification __TVOS_PROHIBITED;
UIKIT_EXTERN NSNotificationName const UIMenuControllerWillHideMenuNotification __TVOS_PROHIBITED;
UIKIT_EXTERN NSNotificationName const UIMenuControllerDidHideMenuNotification __TVOS_PROHIBITED;
UIKIT_EXTERN NSNotificationName const UIMenuControllerMenuFrameDidChangeNotification __TVOS_PROHIBITED;
我们根据需要添加一种Show以及一种Hide即可。Show:设置高亮,Hide:恢复正常。
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(menuWillShow:)
name:UIMenuControllerWillShowMenuNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(menuDidHide:)
name:UIMenuControllerDidHideMenuNotification
object:nil];
3. 在通知触发方法中,对targetView进行变更高亮/正常处理操作。
// 菜单将要出现
- (void)menuWillShow:(NSNotification *)notification {
[self showMenuControllerChangeTargetView:YES notification:notification];
}
// 菜单已经隐藏
- (void)menuDidHide:(NSNotification *)notification {
[UIMenuController sharedMenuController].menuItems = nil;
[self showMenuControllerChangeTargetView:NO notification:notification];
}
// 设置图片方法
- (void)showMenuControllerChangeTargetView:(BOOL)isHighlight notification:(NSNotification *)noti {
UIControlState state = isHighlight ? UIControlStateHighlighted : UIControlStateNormal;
// id base = [noti.object valueForKey:@"_targetView"];
// 拿到MenuController指向的targetView
id base = self.menuTargetView;
if ([base isKindOfClass:[IM_Session_ContentView_BaseView class]]) {
NIMMessageModel * model = [base model];
// 此方法是根据1.消息来源 2.选中状态 来拿到bubbleImage(resizable)
UIImage *image = [base chatBubbleImageForState:state outgoing:model.message.isOutgoingMsg];
[[base bubbleImageView] setImage:image]; // 设置图片样式
}
}
我把IM_Session_ContentView_BaseView里这个方法拿出来供参考。BaseView是个基类视图,其他类型的contentView继承该父类即可避免多处写入该方法。
- (UIImage *)chatBubbleImageForState:(UIControlState)state outgoing:(BOOL)outgoing{
NSString * imageName;
if (outgoing) { // 发出的消息
if (state == UIControlStateNormal) {
imageName = @"im_msgbg_white_right";
} else if (state == UIControlStateHighlighted) {
imageName = @"im_msgbg_white_right_click";
}
} else { // 收到的消息
if (state == UIControlStateNormal) {
imageName = @"im_msgbg_white_left";
} else if (state == UIControlStateHighlighted) {
imageName = @"im_msgbg_white_left_click";
}
}
if (imageName) {
UIImage *image = [UIImage imageNamed:imageName];
return [image resizableImageWithCapInsets:UIEdgeInsetsMake(18,25,17,25)
resizingMode:UIImageResizingModeStretch];
}
return nil;
}
最后
如有疑问或者有其他iOS问题可以留言,一起学习进步。
也可以联系我,邮箱xingjl@outlook.com、 qq12087014。
网友评论