Swift 仿QQ的长按手势Menu效果

本文参考:原Objective-C作者:si1ence

demo.gif

1.满足的条件:

  • 1.左侧和右侧展示的按钮不一样(比如右侧自己发送的消息有撤回)
  • 2.不同类型的消息展示的按钮不一样(比如文本可以复制,文件类型的消息可以进行下载)
  • 3.MenuView 要根据targetRect(即文本框的frame)自动计算出自己合适的frame,靠上还是靠下,特别长的文本要显示在中间
  • 4.tableView 滑动,当前页面消失、点击 MenuView 的按钮,该控件都要从父View 移除(发送对应的通知)
  • 5.点击每个按钮要响应对应的事件(通过代理方法来实现)

作者:si1ence
链接:http://www.jianshu.com/p/ea2c238c8907

2.实现思路:

  • 1.监听cell的长按手势
  • 2.自定义View,添加到keyWindow
  • 3.通过设置代理,实现代理的方法,监听具体按钮的点击
  • 4.监听tableView 滑动,移除MenuView
  • 5.手指长按cell时,计算出气泡相对于keyWindowrect,找出最合适的menu位置

3.所用到的知识点:

  • 1.代理的用法(代码示例)
@objc protocol MenuViewDelegate : NSObjectProtocol {
   @objc optional func menuToThumbup()
   @objc optional func menuToCopy()
   @objc optional func menutoDelete()
   @objc optional func menuToTransmit()
   @objc optional func menuToDowanload()
   @objc optional func menuToPreview()
}

open weak var delegate : MenuViewDelegate?

// MARK: Private Method
extension MenuView {
    
    @objc func thumbupButtonTapped(){
        
        if let delegate = delegate,delegate.responds(to: #selector(delegate.menuToThumbup)) {
            delegate.menuToThumbup!()
        }
        self.removeFromSuperview()
    }
}
  • 2.结构体的用法(代码示例)
struct MenuItemType : OptionSet {
    
    public var rawValue = 0  // 因为RawRepresentable的要求
    public static var copys = MenuItemType(rawValue : 1 << 0)

    public static var transmit = MenuItemType(rawValue : 1 << 1)

    public static var collect = MenuItemType(rawValue : 1 << 2)

    public static var delete = MenuItemType(rawValue : 1 << 3)

    public static var revoke = MenuItemType(rawValue : 1 << 4)

    public static var download = MenuItemType(rawValue : 1 << 5)
}

// 用法示例
if message?.msgDirection == .incoming {
      customMenu.itemType = [.copys,.transmit,.collect,.delete]
}else{
      customMenu.itemType = [.copys,.transmit,.collect,.revoke,.delete]
}
  • 3.UIStackView用法(代码示例)
lazy var containerView : UIStackView = {
      let containerView = UIStackView()
      containerView.alignment = .fill
      containerView.isUserInteractionEnabled = true
       return containerView
}()

containerView.addArrangedSubview(copyingButton)
containerView.addArrangedSubview(transmitButton)
containerView.addArrangedSubview(collectButton)
  • 4.UIButton 分类,实现自定义button图片和文字相对位置
enum ImagePosition {
    case left
    case right
    case top
    case bottom
}

extension UIButton {
    
    /**
     *  利用UIButton的titleEdgeInsets和imageEdgeInsets来实现文字和图片的自由排列
     *  注意:这个方法需要在设置图片和文字之后才可以调用,且button的大小要大于 图片大小+文字大小+spacing
     *
     *  @param spacing 图片和文字的间隔
     */
     func setImagePosition(position : ImagePosition,spacing: CGFloat) {
        
        let imageWith : CGFloat = imageView!.image!.size.width
        let imageHeight : CGFloat = imageView!.image!.size.height
        
        let attrs = [NSAttributedStringKey.font:titleLabel!.font!]
        let labelWidth : CGFloat = titleLabel!.text!.size(withAttributes: attrs).width
        let labelHeight : CGFloat = titleLabel!.text!.size(withAttributes: attrs).height
        
        //image中心移动的x距离
        let imageOffsetX : CGFloat = (imageWith + labelWidth) / 2 - imageWith / 2
        //image中心移动的x距离
        let imageOffsetY : CGFloat = imageHeight / 2 + spacing / 2
        //label中心移动的x距离
        let labelOffsetX : CGFloat = (imageWith + labelWidth / 2) - (imageWith + labelWidth) / 2
        //label中心移动的y距离
        let labelOffsetY : CGFloat = labelHeight / 2 + spacing / 2
        
        switch position {
        case .left:
            imageEdgeInsets = UIEdgeInsetsMake(0, -spacing/2, 0, spacing/2)
            titleEdgeInsets = UIEdgeInsetsMake(0, spacing/2, 0, -spacing/2)
        case .right:
            imageEdgeInsets = UIEdgeInsetsMake(0, labelWidth + spacing/2, 0, -(labelWidth + spacing/2))
            titleEdgeInsets = UIEdgeInsetsMake(0, -(imageHeight + spacing/2), 0, imageHeight + spacing/2)
        case .top:
            imageEdgeInsets = UIEdgeInsetsMake(-imageOffsetY, imageOffsetX, imageOffsetY, -imageOffsetX)
            titleEdgeInsets = UIEdgeInsetsMake(labelOffsetY, -labelOffsetX, -labelOffsetY, labelOffsetX)
        case .bottom:
            imageEdgeInsets = UIEdgeInsetsMake(imageOffsetY, imageOffsetX, -imageOffsetY, -imageOffsetX)
            titleEdgeInsets = UIEdgeInsetsMake(-labelOffsetY, -labelOffsetX, labelOffsetY, labelOffsetX)
         }
    }
}
  • 5.SnapKit的简单用法
avatarHeaderView.snp.makeConstraints({ (make) in
    make.top.equalToSuperview()
    make.width.height.equalTo(kAvatarSize)
    make.leading.equalToSuperview().offset(kAvatarMarginH)
})

bubbleView.snp.makeConstraints({ (make) in
    make.width.lessThanOrEqualToSuperview()
    make.left.equalToSuperview().offset(kAvatarSize + 18)
    make.top.equalTo(contentView)
    make.right.lessThanOrEqualTo(contentView).offset(-73)
    make.bottom.equalTo(contentView).offset(-20).priority(.low)
})
  • 6.枚举成员的初始值用法
enum MessageType : String {
    case text = "text"
    case image = "image"  
    case voice = "voice"
}
var identifier : String {
    get{
        let rawIdentifier = self.msgDirection == .incoming ? kCellIdentifierLeft : kCellIdentifierRight
        return String.init(format: "%@%@", rawIdentifier,self.msgType.rawValue)
    }
}

GitHub下载链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 14,696评论 4 61
  • 伤心了,就默默的睡一觉。 难过了,就悄悄的哭一场; 人生, 哪有事事如意的, 伤心过,也哭过, 日子不还得继续过,...
    四月芳华阅读 1,414评论 0 0
  • 世界那么大,你是选择走走,还是看看。 总想着能有一场说走就走的旅行,一群陪你看星星看月亮的老友。都说初心易得,始终...
    麋梦阅读 1,310评论 1 0
  • 花生400g洗净,温水浸泡约1小时 花生擦干,揉搓去皮 干辣椒剪段,倒入去皮花生,加花椒、香叶、白砂糖、辣椒粉、食...
    健康人生888阅读 5,257评论 2 0
  • 终有一天你会变得很棒,当然,我也是。
    大满满阅读 1,000评论 0 0

友情链接更多精彩内容