近期寫了個小Demo,遂分享出來,文末附Github地址
先上效果圖
先講下該控件需要滿足的條件
左側和右側展示的按鈕不一樣(比如右側自己發送的消息有撤回)
不同類型的消息展示的按鈕不一樣(比如文本可以復制,文件類型的消息可以進行下載)
MenuController 要根據targetRect(即文本框的frame)自動計算出自己合適的frame,靠上還是靠下,特別長的文本要顯示在中間
tableView 滑動,當前頁面消失、點擊 MenuController 的按鈕,該控件都要從父View 移除(發送對應的通知)
點擊每個按鈕要響應對應的事件(通過代理方法來實現)
接下來談一下大概的實現思路
首先滿足前兩個要求意味著 MenuController 內部的按鈕元素可以自由組合,在這裡是采用“按位或”的寫法進行實現,根據二進制的特性完美實現各種按鈕類似“插排”效果的隨意組合
// 在 MenuController 頭文件中的聲明 typedef NS_ENUM(NSUInteger, MenuItemType) { MenuItemTypeCopy = 1 << 0, // 復制 MenuItemTypeTransmit = 1 << 1, // 轉發 MenuItemTypeCollect = 1 << 2, // 收藏 MenuItemTypeDelete = 1 << 3, // 刪除 MenuItemTypeRevoke = 1 << 4, // 撤回 MenuItemTypeDownload = 1 << 5, // 下載 };
以下是 Cell 文本框的長按手勢響應的方法
- (void)longPressOnBubble:(UILongPressGestureRecognizer *)gesture { if (gesture.state == UIGestureRecognizerStateBegan) { #warning 實現:收到別人發送的消息不包含撤回,自己發送的消息包含撤回 if (self.message.msgDirection == WPFMessageDirectionIncoming) { // 在其 set 方法中,根據傳入的類型,添加對應的按鈕 [self.custormMenu setItemType:MenuItemTypeCopy | MenuItemTypeTransmit | MenuItemTypeCollect | MenuItemTypeDelete]; } else { [self.custormMenu setItemType:MenuItemTypeCopy | MenuItemTypeTransmit | MenuItemTypeCollect | MenuItemTypeRevoke | MenuItemTypeDelete]; } // 發送通知,隱藏別的 cell 的 MenuController [[NSNotificationCenter defaultCenter] postNotificationName:WPFMenuControllerWillHideMenuNoti object:nil]; UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; [keyWindow addSubview:self.custormMenu]; #warning 計算frame封裝在控件內部,使用的時候只要傳一個 targetRect 參數即可 CGRect targetRectInWindow = [self.contentView convertRect:self.bubbleView.frame toView:keyWindow]; [self.custormMenu setTargetRect:targetRectInWindow]; // 增加監聽 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hideMenuNotiAction) name:WPFMenuControllerWillHideMenuNoti object:nil]; } }
以下是控件內部計算Frame的方法
#warning 最開始想采用取巧的方式,先創建一個原生的UIMenuController,再把frame賦值給自定的控件。 // 但是那樣的操作需要當前 [cell becomeFirstResponsder],就會導致鍵盤收起,遂放棄 - (void)setTargetRect:(CGRect)targetRect { 5 CGFloat screenW = [UIScreen mainScreen].bounds.size.width; CGFloat screenH = [UIScreen mainScreen].bounds.size.height; CGFloat itemW = 50; // 保證箭頭在targetRect中心 CGFloat targetCenterX = targetRect.origin.x + targetRect.size.width/2; CGFloat menuW = self.itemCount * itemW; CGFloat menuH = 58; CGFloat menuX = targetCenterX - menuW/2 > 0 ? targetCenterX - menuW/2 : 0; menuX = menuX + menuW > screenW ? screenW - menuW : menuX; CGFloat menuY = targetRect.origin.y - menuH; // 避免 MenuController 過於靠上 menuY = menuY < 20 ? targetRect.origin.y + targetRect.size.height : menuY; // 適配特別長的文本,直接顯示在屏幕中間 menuY = menuY > screenH-menuH-30 ? screenH / 2 : menuY; CGRect frame = CGRectMake(menuX, menuY, menuW, menuH); [self setFrame:frame]; CGFloat arrowH = 8; CGFloat arrowW = 12; CGFloat arrowX = targetRect.origin.x-frame.origin.x+0.5*targetRect.size.width-arrowW/2; if (frame.origin.y > targetRect.origin.y) { // 箭頭向上 self.backgroundImageView.frame = CGRectMake(0, arrowH, menuW, menuH-arrowH); self.arrowImageView.image = [UIImage imageNamed:@"longpress_up_arrow"]; self.arrowImageView.frame = CGRectMake(arrowX, 0, arrowW, arrowH); } else { // 箭頭向下 self.backgroundImageView.frame = CGRectMake(0, 0, menuW, menuH-arrowH); self.arrowImageView.image = [UIImage imageNamed:@"longpress_down_arrow"]; self.arrowImageView.frame = CGRectMake(arrowX, menuH-arrowH, arrowW, arrowH); } }
最後附上Github地址
作者:si1ence
鏈接:http://www.jianshu.com/p/ea2c238c8907
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。