转载

仿QQ的长按手势MenuController效果

近期写了个小Demo,遂分享出来,文末附Github地址

仿QQ的长按手势MenuController效果

先上效果图

先讲下该控件需要满足的条件

  1. 左侧和右侧展示的按钮不一样(比如右侧自己发送的消息有撤回)

  2. 不同类型的消息展示的按钮不一样(比如文本可以复制,文件类型的消息可以进行下载)

  3. MenuController 要根据targetRect(即文本框的frame)自动计算出自己合适的frame,靠上还是靠下,特别长的文本要显示在中间

  4. tableView 滑动,当前页面消失、点击 MenuController 的按钮,该控件都要从父View 移除(发送对应的通知)

  5. 点击每个按钮要响应对应的事件(通过代理方法来实现)

接下来谈一下大概的实现思路

  • 首先满足前两个要求意味着 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);
    }
}

最后附上Github地址

正文到此结束
Loading...