近期寫了個小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 {
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);
}
}
複製程式碼