JCChatViewCell.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. //
  2. // JCChatViewCell.swift
  3. // JChat
  4. //
  5. // Created by deng on 2017/2/28.
  6. // Copyright © 2017年 HXHG. All rights reserved.
  7. //
  8. import UIKit
  9. import CocoaLumberjack
  10. open class JCChatViewCell: UICollectionViewCell, UIGestureRecognizerDelegate {
  11. weak var delegate: JCMessageDelegate?
  12. public override init(frame: CGRect) {
  13. super.init(frame: frame)
  14. _commonInit()
  15. }
  16. public required init?(coder aDecoder: NSCoder) {
  17. super.init(coder: aDecoder)
  18. _commonInit()
  19. }
  20. deinit {
  21. guard let observer = _menuNotifyObserver else{
  22. return
  23. }
  24. NotificationCenter.default.removeObserver(observer)
  25. }
  26. func updateView() {
  27. guard let message = _layoutAttributes?.message else {
  28. return
  29. }
  30. _tipsView?.apply(message)
  31. let tipsView = _tipsView as? JCMessageTipsView
  32. if tipsView != nil {
  33. tipsView?.delegate = self.delegate
  34. }
  35. let avatarView = _avatarView as? JCMessageAvatarView
  36. if avatarView != nil {
  37. avatarView?.delegate = self.delegate
  38. }
  39. _contentView?.apply(message)
  40. }
  41. open override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
  42. super.apply(layoutAttributes)
  43. guard let _ = layoutAttributes as? JCChatViewLayoutAttributes else {
  44. return
  45. }
  46. _updateViews()
  47. _updateViewLayouts()
  48. _updateViewValues()
  49. }
  50. open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
  51. guard let rect = _layoutAttributes?.info?.layoutedBoxRect(with: .all) else {
  52. return false
  53. }
  54. return rect.contains(point)
  55. }
  56. open class var cardViewClass: JCMessageContentViewType.Type {
  57. return JCMessageCardView.self
  58. }
  59. open class var tipsViewClass: JCMessageContentViewType.Type {
  60. return JCMessageTipsView.self
  61. }
  62. open class var avatarViewClass: JCMessageContentViewType.Type {
  63. return JCMessageAvatarView.self
  64. }
  65. private lazy var send_nor = UIImage.loadImage("chat_bubble_send_nor")!.resizableImage(withCapInsets: UIEdgeInsets(top: 25, left: 25, bottom: 25, right: 25))
  66. private lazy var send_press = UIImage.loadImage("chat_bubble_send_press")!.resizableImage(withCapInsets: UIEdgeInsets(top: 25, left: 25, bottom: 25, right: 25))
  67. private lazy var recive_nor = UIImage.loadImage("chat_bubble_recive_nor")!.resizableImage(withCapInsets: UIEdgeInsets(top: 25, left: 25, bottom: 25, right: 25))
  68. private lazy var recive_press = UIImage.loadImage("chat_bubble_recive_press")!.resizableImage(withCapInsets: UIEdgeInsets(top: 25, left: 25, bottom: 25, right: 25))
  69. private func _updateViews() {
  70. guard let message = _layoutAttributes?.message else {
  71. return
  72. }
  73. let options = message.options
  74. if options.showsBubble {
  75. if _bubbleView == nil {
  76. _bubbleView = UIImageView()
  77. }
  78. if let view = _bubbleView, view.superview == nil {
  79. insertSubview(view, belowSubview: contentView)
  80. }
  81. } else {
  82. if let view = _bubbleView {
  83. view.removeFromSuperview()
  84. }
  85. _bubbleView = nil
  86. }
  87. if options.showsCard {
  88. if _cardView == nil {
  89. _cardView = type(of: self).cardViewClass._init()
  90. }
  91. if let view = _cardView as? UIView, view.superview == nil {
  92. contentView.addSubview(view)
  93. }
  94. } else {
  95. if let view = _cardView as? UIView {
  96. view.removeFromSuperview()
  97. }
  98. _cardView = nil
  99. }
  100. if options.showsTips {
  101. if _tipsView == nil {
  102. _tipsView = type(of: self).tipsViewClass._init()
  103. }
  104. if let view = _tipsView as? UIView, view.superview == nil {
  105. contentView.addSubview(view)
  106. }
  107. } else {
  108. if let view = _tipsView as? UIView {
  109. view.removeFromSuperview()
  110. }
  111. _tipsView = nil
  112. }
  113. if options.showsAvatar {
  114. if _avatarView == nil {
  115. _avatarView = type(of: self).avatarViewClass._init()
  116. }
  117. if let view = _avatarView as? UIView, view.superview == nil {
  118. contentView.addSubview(view)
  119. }
  120. } else {
  121. if let view = _avatarView as? UIView {
  122. view.removeFromSuperview()
  123. }
  124. _avatarView = nil
  125. }
  126. if _contentView == nil {
  127. // create
  128. _contentView = type(of: message.content).viewType._init()
  129. // move
  130. if let view = _contentView as? UIView, view.superview == nil {
  131. contentView.addSubview(view)
  132. }
  133. }
  134. }
  135. private func _updateViewLayouts() {
  136. // prepare
  137. guard let layoutInfo = _layoutAttributes?.info else {
  138. return
  139. }
  140. // update bubble view layout
  141. if let view = _bubbleView {
  142. view.frame = layoutInfo.layoutedRect(with: .bubble)
  143. }
  144. // update visit card view layout
  145. if let view = _cardView as? UIView {
  146. view.frame = layoutInfo.layoutedRect(with: .card)
  147. }
  148. if let view = _tipsView as? UIView {
  149. let frame = layoutInfo.layoutedRect(with: .tips)
  150. view.frame = frame
  151. }
  152. // update avatar view layout
  153. if let view = _avatarView as? UIView {
  154. view.frame = layoutInfo.layoutedRect(with: .avatar)
  155. }
  156. // update content view layout
  157. if let view = _contentView as? UIView {
  158. view.frame = layoutInfo.layoutedRect(with: .content)
  159. }
  160. }
  161. private func _updateViewValues() {
  162. guard let message = _layoutAttributes?.message else {
  163. return
  164. }
  165. let options = message.options
  166. _cardView?.apply(message)
  167. _tipsView?.apply(message)
  168. _avatarView?.apply(message)
  169. let avatarView = _avatarView as? JCMessageAvatarView
  170. if avatarView != nil {
  171. avatarView?.delegate = self.delegate
  172. let user = message.sender?.username
  173. DDLogDebug("更新头像,发送者头像:\(user ?? "")")
  174. let urlstr = AppDelegate.o2Collect.generateURLWithAppContextKey(ContactContext.contactsContextKeyV2, query: ContactContext.personIconByNameQueryV2, parameter: ["##name##":user as AnyObject])
  175. let url = URL(string: urlstr!)
  176. avatarView?.hnk_setImageFromURL(url!)
  177. }
  178. _contentView?.apply(message)
  179. if let view = _bubbleView {
  180. switch options.alignment {
  181. case .left:
  182. view.image = recive_nor
  183. view.highlightedImage = recive_press
  184. case .right:
  185. view.image = send_nor
  186. view.highlightedImage = send_press
  187. case .center:
  188. break
  189. }
  190. }
  191. }
  192. open override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
  193. guard gestureRecognizer == _menuGesture else {
  194. return super.gestureRecognizerShouldBegin(gestureRecognizer)
  195. }
  196. guard let rect = _layoutAttributes?.info?.layoutedBoxRect(with: .content) else {
  197. return false
  198. }
  199. guard rect.contains(gestureRecognizer.location(in: contentView)) else {
  200. return false
  201. }
  202. return true
  203. }
  204. func copyMessage(_ sender: Any) {}
  205. func deleteMessage(_ sender: Any) {}
  206. func forwardMessage(_ sender: Any) {}
  207. func withdrawMessage(_ sender: Any) {}
  208. private dynamic func _handleMenuGesture(_ sender: UILongPressGestureRecognizer) {
  209. guard sender.state == .began else {
  210. return
  211. }
  212. guard let view = _contentView as? UIView,
  213. let content = _layoutAttributes?.message?.content,
  214. let info = _layoutAttributes?.info else {
  215. return
  216. }
  217. let rect = info.layoutedRect(with: .content).inset(by: -content.layoutMargins)
  218. let menuController = UIMenuController.shared
  219. // set responder
  220. NSClassFromString("UICalloutBar")?.setValue(self, forKeyPath: "sharedCalloutBar.responderTarget")
  221. menuController.menuItems = [
  222. UIMenuItem(title: "复制", action: #selector(copyMessage(_:))),
  223. UIMenuItem(title: "转发", action: #selector(forwardMessage(_:))),
  224. UIMenuItem(title: "撤回", action: #selector(withdrawMessage(_:))),
  225. UIMenuItem(title: "删除", action: #selector(deleteMessage(_:)))
  226. ]
  227. // set menu display position
  228. menuController.setTargetRect(convert(rect, to: view), in: view)
  229. menuController.setMenuVisible(true, animated: true)
  230. // really show?
  231. guard menuController.isMenuVisible else {
  232. return
  233. }
  234. // set selected
  235. self.isHighlighted = true
  236. self._menuNotifyObserver = NotificationCenter.default.addObserver(forName: UIMenuController.willHideMenuNotification, object: nil, queue: nil) { [weak self] notification in
  237. // is release?
  238. guard let observer = self?._menuNotifyObserver else {
  239. return
  240. }
  241. NotificationCenter.default.removeObserver(observer)
  242. // cancel select
  243. self?.isHighlighted = false
  244. self?._menuNotifyObserver = nil
  245. }
  246. }
  247. open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
  248. // menu bar only process
  249. guard sender is UIMenuController else {
  250. // other is default process
  251. return super.canPerformAction(action, withSender: sender)
  252. }
  253. // check collectionView and attributes
  254. guard let view = _collectionView, let indexPath = _layoutAttributes?.indexPath else {
  255. return false
  256. }
  257. // forward to collectionView
  258. guard let result = view.delegate?.collectionView?(view, canPerformAction: action, forItemAt: indexPath, withSender: sender) else {
  259. return false
  260. }
  261. return result
  262. }
  263. open override func perform(_ action: Selector!, with sender: Any!) -> Unmanaged<AnyObject>! {
  264. // menu bar only process
  265. guard sender is UIMenuController else {
  266. // other is default process
  267. return super.perform(action, with: sender)
  268. }
  269. // check collectionView and attributes
  270. guard let view = _collectionView, let indexPath = _layoutAttributes?.indexPath else {
  271. return nil
  272. }
  273. // forward to collectionView
  274. view.delegate?.collectionView?(view, performAction: action, forItemAt: indexPath, withSender: sender)
  275. return nil
  276. }
  277. private func _commonInit() {
  278. }
  279. fileprivate var _bubbleView: UIImageView?
  280. fileprivate var _cardView: JCMessageContentViewType?
  281. fileprivate var _avatarView: JCMessageContentViewType?
  282. fileprivate var _contentView: JCMessageContentViewType?
  283. fileprivate var _tipsView: JCMessageContentViewType?
  284. fileprivate var _menuNotifyObserver: Any?
  285. fileprivate var _menuGesture: UILongPressGestureRecognizer? {
  286. return value(forKeyPath: "_menuGesture") as? UILongPressGestureRecognizer
  287. }
  288. @NSManaged fileprivate var _collectionView: UICollectionView?
  289. @NSManaged fileprivate var _layoutAttributes: JCChatViewLayoutAttributes?
  290. }
  291. fileprivate extension JCMessageContentViewType {
  292. // 如果是NSObject对象, 直接使用self.init()会导致无法释放内存
  293. // 解决方案是转为显式类型再调用cls.init()
  294. fileprivate static func _init() -> JCMessageContentViewType {
  295. guard let cls = self as? NSObject.Type else {
  296. return self.init()
  297. }
  298. guard let ob = cls.init() as? JCMessageContentViewType else {
  299. return self.init()
  300. }
  301. return ob
  302. }
  303. }
  304. fileprivate extension UICollectionReusableView {
  305. @NSManaged fileprivate func _setLayoutAttributes(_ layoutAttributes: UICollectionViewLayoutAttributes)
  306. }
  307. fileprivate prefix func -(edg: UIEdgeInsets) -> UIEdgeInsets {
  308. // 取反
  309. return .init(top: -edg.top, left: -edg.left, bottom: -edg.bottom, right: edg.right)
  310. }