Swift 仿QQ的长按手势Menu效果
2017-11-29 本文已影响141人
船长_
本文参考:原Objective-C作者:si1ence

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时,计算出气泡相对于
keyWindow
的rect
,找出最合适的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)
}
}