iOS 聊天氣泡保持選中高亮狀態(tài)

前言

下圖為目前市面上一種常見的聊天會話Cell結(jié)構圖,如果模糊可以點擊放大。
其中主要的自定義媒體區(qū)域為CustomContentView,涵蓋大部分媒體類型的展示。

會話Cell結(jié)構.png

曾經(jīng)做IM以及朋友圈類似功能的時候,遇到過一個最疑惑的細節(jié)問題:

如何實現(xiàn)UIMenuController的指向視圖保持選中高亮狀態(tài)

如下圖對比效果:

1. 選中后無狀態(tài)
2. 選中后保持高亮狀態(tài)

雖然這只是一個細節(jié),但是我們常說:細節(jié)決定成敗。

過程

一切問題都是會則不難,難則不會。思考這個問題的過程中,走了不少彎路,曾經(jīng)想過多種可能解決的方法:

  1. 利用cell的selected狀態(tài),在selected方法中做操作。(嘗試失敗)
  2. 系統(tǒng)自帶tableViewcell觸發(fā)menu高亮效果,來實現(xiàn)這個操作。(嘗試失?。?/li>
  3. 聯(lián)系騰訊的相關iOS開發(fā)人員問一問。(沒去試,誰有門路嗎- -)
  4. 其他忘了。

這個問題擱置了許久,雖然暫時沒有好的思路,但是也一直沒有放棄這個問題。
一次機緣巧合,鼠標點了一下這個東西:

獲取targetView

原來是可以拿到這個customContentView的!
不過UIMenuController里沒有直接提供接口,如圖:

系統(tǒng)提供的API

但是iOS開發(fā)者都知道,經(jīng)常需要用到KVC來取值。沒錯,我們能看到有這個targetView就可以嘗試用 valueForKey:來獲取這個視圖。
注意,這種獲取_targetView的方法在iOS11之前會導致崩潰,所以可以在控制器觸發(fā)onLongPressCell:里用一個全局變量記錄這個View。
思路理清以后,事實證明這種方法是可行的,終于解決了這個困擾許久的問題。

實現(xiàn)

1. 對cell添加長按事件,可以用delegate或者block返回事件傳給ViewController。
- (void)longGesturePress:(UIGestureRecognizer*)gestureRecognizer {
    if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] &&
        gestureRecognizer.state == UIGestureRecognizerStateBegan) {
        if (_messageDelegate && [_messageDelegate respondsToSelector:@selector(onLongPressCell:inView:)]) {
            // 實現(xiàn)來自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添加監(jiān)聽UIMenuController的通知。
// 這5種NSNotificationName都是UIMenuController的系統(tǒng)自帶監(jiān)聽方案
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;

我們根據(jù)需要添加一種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. 在通知觸發(fā)方法中,對targetView進行變更高亮/正常處理操作。
// 菜單將要出現(xiàn)
- (void)menuWillShow:(NSNotification *)notification {
    [self showMenuControllerChangeTargetView:YES notification:notification];
}

// 菜單已經(jīng)隱藏
- (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];
        // 此方法是根據(jù)1.消息來源 2.選中狀態(tài) 來拿到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) {  // 發(fā)出的消息
        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問題可以留言,一起學習進步。
也可以聯(lián)系我,郵箱xingjl@outlook.com、 qq12087014。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容