diff --git a/Yep.xcodeproj/project.pbxproj b/Yep.xcodeproj/project.pbxproj index 21ff1372..ac671dc0 100644 --- a/Yep.xcodeproj/project.pbxproj +++ b/Yep.xcodeproj/project.pbxproj @@ -97,6 +97,7 @@ 5039594F1B05E6E000797A3E /* ProfileSocialAccountGithubCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5039594D1B05E6E000797A3E /* ProfileSocialAccountGithubCell.xib */; }; 503A54ED1B328ADB002077BD /* NSLocale+Yep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503A54EC1B328ADB002077BD /* NSLocale+Yep.swift */; }; 503CAB431AC00CB100DFE830 /* ConversationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503CAB421AC00CB100DFE830 /* ConversationViewController.swift */; }; + 5044BF061B5FA74800930ADC /* BubbleMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044BF051B5FA74800930ADC /* BubbleMenuView.swift */; }; 504BDC151B4528A500DBD35D /* Badges.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504BDC141B4528A500DBD35D /* Badges.xcassets */; }; 504CBA3D1B3D04B800663A90 /* ChatTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504CBA3C1B3D04B800663A90 /* ChatTextView.swift */; }; 504F41251ACE2D1D00FBB19A /* SampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504F41241ACE2D1D00FBB19A /* SampleView.swift */; }; @@ -321,6 +322,7 @@ 5039594D1B05E6E000797A3E /* ProfileSocialAccountGithubCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ProfileSocialAccountGithubCell.xib; path = Views/Cells/ProfileSocialAccount/ProfileSocialAccountGithubCell.xib; sourceTree = ""; }; 503A54EC1B328ADB002077BD /* NSLocale+Yep.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSLocale+Yep.swift"; path = "Extensions/NSLocale+Yep.swift"; sourceTree = ""; }; 503CAB421AC00CB100DFE830 /* ConversationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConversationViewController.swift; path = ViewControllers/Conversation/ConversationViewController.swift; sourceTree = ""; }; + 5044BF051B5FA74800930ADC /* BubbleMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BubbleMenuView.swift; path = Views/BubbleMenu/BubbleMenuView.swift; sourceTree = ""; }; 504BDC141B4528A500DBD35D /* Badges.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Badges.xcassets; sourceTree = ""; }; 504CBA3C1B3D04B800663A90 /* ChatTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ChatTextView.swift; path = Views/TextViews/ChatTextView.swift; sourceTree = ""; }; 504F41241ACE2D1D00FBB19A /* SampleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SampleView.swift; path = Views/AudioWaves/SampleView.swift; sourceTree = ""; }; @@ -782,6 +784,7 @@ 5019F62F1B39BFCB0000D993 /* ConversationMore */, 500A0B201B451A7400D49358 /* Badge */, 504F85AD1B4D21E400F909BE /* DiscoverFilter */, + 5044BF071B5FA75000930ADC /* BubbleMenu */, ); name = Views; sourceTree = ""; @@ -1030,6 +1033,14 @@ name = Conversation; sourceTree = ""; }; + 5044BF071B5FA75000930ADC /* BubbleMenu */ = { + isa = PBXGroup; + children = ( + 5044BF051B5FA74800930ADC /* BubbleMenuView.swift */, + ); + name = BubbleMenu; + sourceTree = ""; + }; 504CBA3B1B3D048900663A90 /* TextViews */ = { isa = PBXGroup; children = ( @@ -1802,6 +1813,7 @@ 5023DE2A1AB685BA00B3EE96 /* ContactsViewController.swift in Sources */, 50CDBE491ACBAD3200459CE0 /* ChatLeftImageCell.swift in Sources */, 5033159C1AC101C80008A209 /* ChatRightTextCell.swift in Sources */, + 5044BF061B5FA74800930ADC /* BubbleMenuView.swift in Sources */, 5023DE561AB6CA1000B3EE96 /* AvatarCache.swift in Sources */, 5023DE371AB6979600B3EE96 /* ConversationCell.swift in Sources */, 502AE5351AB8649F005BD199 /* LoginByMobileViewController.swift in Sources */, diff --git a/Yep/Base.lproj/Main.storyboard b/Yep/Base.lproj/Main.storyboard index d9095c0b..24bc0f63 100644 --- a/Yep/Base.lproj/Main.storyboard +++ b/Yep/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + diff --git a/Yep/Configs/YepConfig.swift b/Yep/Configs/YepConfig.swift index c7431915..735b9801 100644 --- a/Yep/Configs/YepConfig.swift +++ b/Yep/Configs/YepConfig.swift @@ -81,6 +81,10 @@ class YepConfig { return 100 } + struct Conversation { + static let menuDirectionUpThreshold: CGFloat = 64 + 60 + } + struct AudioRecord { static let shortestDuration: NSTimeInterval = 0.5 static let longestDuration: NSTimeInterval = 60 diff --git a/Yep/ViewControllers/Conversation/ConversationViewController.swift b/Yep/ViewControllers/Conversation/ConversationViewController.swift index f601d591..d284208a 100644 --- a/Yep/ViewControllers/Conversation/ConversationViewController.swift +++ b/Yep/ViewControllers/Conversation/ConversationViewController.swift @@ -109,6 +109,13 @@ class ConversationViewController: BaseViewController { @IBOutlet weak var takePhotoButton: MessageTypeButton! @IBOutlet weak var addLocationButton: MessageTypeButton! + var currentMenu: BubbleMenuView? + + func removeOldMenu() { + currentMenu?.hide() + currentMenu = nil + } + var originalNavigationControllerDelegate: UINavigationControllerDelegate? var waverView: YepWaverView! @@ -525,6 +532,8 @@ class ConversationViewController: BaseViewController { messageToolbar.stateTransitionAction = { [weak self] (messageToolbar, previousState, currentState) in + self?.removeOldMenu() + if let strongSelf = self { switch (previousState, currentState) { @@ -634,8 +643,6 @@ class ConversationViewController: BaseViewController { override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) - - conversationCollectionViewHasBeenMovedToBottomOnce = true FayeService.sharedManager.delegate = self @@ -668,6 +675,49 @@ class ConversationViewController: BaseViewController { self.navigationController?.setNavigationBarHidden(false, animated: true) self.setNeedsStatusBarAppearanceUpdate() + + + /* + // test menu + + if true { + + let copyItem = BubbleMenuView.Item(title: "Copy") { + print("copy\n") + } + + let deleteItem = BubbleMenuView.Item(title: "Delete") { + print("delete\n") + } + + let menu = BubbleMenuView(items: [copyItem, deleteItem]) + + menu.setTranslatesAutoresizingMaskIntoConstraints(false) + view.addSubview(menu) + + let menuCenterY = NSLayoutConstraint(item: menu, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: 0) + let menuCenterX = NSLayoutConstraint(item: menu, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1, constant: 0) + + NSLayoutConstraint.activateConstraints([menuCenterY, menuCenterX]) + } + + if true { + + let copyItem = BubbleMenuView.Item(title: "Copy") { + print("copy\n") + } + + let menu = BubbleMenuView(items: [copyItem]) + + menu.setTranslatesAutoresizingMaskIntoConstraints(false) + view.addSubview(menu) + + let menuCenterY = NSLayoutConstraint(item: menu, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: -100) + let menuCenterX = NSLayoutConstraint(item: menu, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1, constant: 0) + + NSLayoutConstraint.activateConstraints([menuCenterY, menuCenterX]) + } + */ } private func markMessageAsReaded(message: Message) { @@ -700,6 +750,12 @@ class ConversationViewController: BaseViewController { } } + override func viewWillDisappear(animated: Bool) { + super.viewWillDisappear(animated) + + removeOldMenu() + } + override func viewDidDisappear(animated: Bool) { super.viewDidDisappear(animated) @@ -971,6 +1027,8 @@ class ConversationViewController: BaseViewController { func moreAction() { + removeOldMenu() + messageToolbar.state = .Default moreView.showProfileAction = { [weak self] in @@ -1802,6 +1860,7 @@ extension ConversationViewController: UICollectionViewDataSource, UICollectionVi markMessageAsReaded(message) switch message.mediaType { + case MessageMediaType.Image.rawValue: let cell = collectionView.dequeueReusableCellWithReuseIdentifier(chatLeftImageCellIdentifier, forIndexPath: indexPath) as! ChatLeftImageCell @@ -1886,12 +1945,40 @@ extension ConversationViewController: UICollectionViewDataSource, UICollectionVi cell.configureWithMessage(message, textContentLabelWidth: textContentLabelWidthOfMessage(message), collectionView: collectionView, indexPath: indexPath) + cell.longPressAction = { [weak self] cell in + + self?.removeOldMenu() + + if let strongSelf = self { + + let copyItem = BubbleMenuView.Item(type: .Normal, title: NSLocalizedString("Copy", comment: "")) { menu in + print("copy\n") + + UIPasteboard.generalPasteboard().string = cell.textContentTextView.text + + menu.hide() + self?.currentMenu = nil + } + + let bubbleFrame = cell.convertRect(cell.bubbleBodyImageView.frame, toView: strongSelf.view) + + let arrowDirection: BubbleMenuView.ArrowDirection = CGRectGetMidY(bubbleFrame) < YepConfig.Conversation.menuDirectionUpThreshold ? .Up : .Down + + let menu = BubbleMenuView(arrowDirection: arrowDirection, items: [copyItem]) + + strongSelf.currentMenu = menu + + menu.showInView(strongSelf.view, withBubbleFrame: bubbleFrame) + } + } + return cell } } else { // from Me switch message.mediaType { + case MessageMediaType.Image.rawValue: let cell = collectionView.dequeueReusableCellWithReuseIdentifier(chatRightImageCellIdentifier, forIndexPath: indexPath) as! ChatRightImageCell @@ -2063,77 +2150,109 @@ extension ConversationViewController: UICollectionViewDataSource, UICollectionVi } }, collectionView: collectionView, indexPath: indexPath) - cell.deleteMessageAction = { [weak self] in - dispatch_async(dispatch_get_main_queue()) { - if let strongSelf = self, realm = message.realm { + cell.longPressAction = { [weak self] cell in - var sectionDateMessage: Message? + self?.removeOldMenu() - if let currentMessageIndex = strongSelf.messages.indexOf(message) { + if let strongSelf = self { - let previousMessageIndex = currentMessageIndex - 1 + let copyItem = BubbleMenuView.Item(type: .Normal, title: NSLocalizedString("Copy", comment: "")) { menu in + print("copy\n") - if let previousMessage = strongSelf.messages[safe: previousMessageIndex] { + UIPasteboard.generalPasteboard().string = cell.textContentTextView.text - if previousMessage.mediaType == MessageMediaType.SectionDate.rawValue { - sectionDateMessage = previousMessage - } - } - } - - let currentIndexPath: NSIndexPath - if let index = strongSelf.messages.indexOf(message) { - currentIndexPath = NSIndexPath(forItem: index - strongSelf.displayedMessagesRange.location, inSection: indexPath.section) - } else { - currentIndexPath = indexPath - } - - if let sectionDateMessage = sectionDateMessage { - - var canDeleteTwoMessages = false // 考虑刚好的边界情况,例如消息为本束的最后一条,而 sectionDate 在上一束中 - if strongSelf.displayedMessagesRange.length >= 2 { - strongSelf.displayedMessagesRange.length -= 2 - canDeleteTwoMessages = true - - } else { - if strongSelf.displayedMessagesRange.location >= 1 { - strongSelf.displayedMessagesRange.location -= 1 - } - strongSelf.displayedMessagesRange.length -= 1 - } - - realm.write { - if let mediaMetaData = sectionDateMessage.mediaMetaData { - realm.delete(mediaMetaData) - } - if let mediaMetaData = message.mediaMetaData { - realm.delete(mediaMetaData) - } - realm.delete(sectionDateMessage) - realm.delete(message) - } - - if canDeleteTwoMessages { - let previousIndexPath = NSIndexPath(forItem: currentIndexPath.item - 1, inSection: currentIndexPath.section) - strongSelf.conversationCollectionView.deleteItemsAtIndexPaths([previousIndexPath, currentIndexPath]) - } else { - strongSelf.conversationCollectionView.deleteItemsAtIndexPaths([currentIndexPath]) - } - - } else { - strongSelf.displayedMessagesRange.length -= 1 - realm.write { - if let mediaMetaData = message.mediaMetaData { - realm.delete(mediaMetaData) - } - realm.delete(message) - } - strongSelf.conversationCollectionView.deleteItemsAtIndexPaths([currentIndexPath]) - } - - // 必须更新,插入时需要 - strongSelf.lastTimeMessagesCount = strongSelf.messages.count + menu.hide() + self?.currentMenu = nil } + + let deleteItem = BubbleMenuView.Item(type: .Danger, title: NSLocalizedString("Delete", comment: "")) { menu in + print("delete\n") + + dispatch_async(dispatch_get_main_queue()) { + if let strongSelf = self, realm = message.realm { + + var sectionDateMessage: Message? + + if let currentMessageIndex = strongSelf.messages.indexOf(message) { + + let previousMessageIndex = currentMessageIndex - 1 + + if let previousMessage = strongSelf.messages[safe: previousMessageIndex] { + + if previousMessage.mediaType == MessageMediaType.SectionDate.rawValue { + sectionDateMessage = previousMessage + } + } + } + + let currentIndexPath: NSIndexPath + if let index = strongSelf.messages.indexOf(message) { + currentIndexPath = NSIndexPath(forItem: index - strongSelf.displayedMessagesRange.location, inSection: indexPath.section) + } else { + currentIndexPath = indexPath + } + + if let sectionDateMessage = sectionDateMessage { + + var canDeleteTwoMessages = false // 考虑刚好的边界情况,例如消息为本束的最后一条,而 sectionDate 在上一束中 + if strongSelf.displayedMessagesRange.length >= 2 { + strongSelf.displayedMessagesRange.length -= 2 + canDeleteTwoMessages = true + + } else { + if strongSelf.displayedMessagesRange.location >= 1 { + strongSelf.displayedMessagesRange.location -= 1 + } + strongSelf.displayedMessagesRange.length -= 1 + } + + realm.write { + if let mediaMetaData = sectionDateMessage.mediaMetaData { + realm.delete(mediaMetaData) + } + if let mediaMetaData = message.mediaMetaData { + realm.delete(mediaMetaData) + } + realm.delete(sectionDateMessage) + realm.delete(message) + } + + if canDeleteTwoMessages { + let previousIndexPath = NSIndexPath(forItem: currentIndexPath.item - 1, inSection: currentIndexPath.section) + strongSelf.conversationCollectionView.deleteItemsAtIndexPaths([previousIndexPath, currentIndexPath]) + } else { + strongSelf.conversationCollectionView.deleteItemsAtIndexPaths([currentIndexPath]) + } + + } else { + strongSelf.displayedMessagesRange.length -= 1 + realm.write { + if let mediaMetaData = message.mediaMetaData { + realm.delete(mediaMetaData) + } + realm.delete(message) + } + strongSelf.conversationCollectionView.deleteItemsAtIndexPaths([currentIndexPath]) + } + + // 必须更新,插入时需要 + strongSelf.lastTimeMessagesCount = strongSelf.messages.count + } + } + + menu.hide() + self?.currentMenu = nil + } + + let bubbleFrame = cell.convertRect(cell.bubbleBodyImageView.frame, toView: strongSelf.view) + + let arrowDirection: BubbleMenuView.ArrowDirection = CGRectGetMidY(bubbleFrame) < YepConfig.Conversation.menuDirectionUpThreshold ? .Up : .Down + + let menu = BubbleMenuView(arrowDirection: arrowDirection, items: [copyItem, deleteItem]) + + strongSelf.currentMenu = menu + + menu.showInView(strongSelf.view, withBubbleFrame: bubbleFrame) } } @@ -2186,6 +2305,8 @@ extension ConversationViewController: UICollectionViewDataSource, UICollectionVi func scrollViewDidScroll(scrollView: UIScrollView) { pullToRefreshView.scrollViewDidScroll(scrollView) + + removeOldMenu() } func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { diff --git a/Yep/Views/BubbleMenu/BubbleMenuView.swift b/Yep/Views/BubbleMenu/BubbleMenuView.swift new file mode 100644 index 00000000..e4b1b322 --- /dev/null +++ b/Yep/Views/BubbleMenu/BubbleMenuView.swift @@ -0,0 +1,269 @@ +// +// BubbleMenuView.swift +// Yep +// +// Created by NIX on 15/7/22. +// Copyright (c) 2015年 Catch Inc. All rights reserved. +// + +import UIKit + +class BubbleMenuView: UIView { + + enum ArrowDirection { + case Up + case Down + } + + struct Item { + + enum Type { + case Normal + case Danger + } + let type: Type + + let title: String + + let action: BubbleMenuView -> Void + } + + var arrowDirection: ArrowDirection + var items: [Item] + + var buttons = [UIButton]() + + init(arrowDirection: ArrowDirection, items: [Item]) { + + self.arrowDirection = arrowDirection + self.items = items + + super.init(frame: CGRectZero) + + makeUI() + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Actions + + func tapButton(button: UIButton) { + if let index = find(buttons, button) { + let action = items[index].action + action(self) + } + } + + func showInView(view: UIView, withBubbleFrame bubbleFrame: CGRect) { + + // position + + setTranslatesAutoresizingMaskIntoConstraints(false) + view.addSubview(self) + + var vConstant = CGRectGetMidY(bubbleFrame) - CGRectGetMidY(view.frame) + + let menuV: NSLayoutConstraint + + switch arrowDirection { + + case .Up: + vConstant += ceil(CGRectGetHeight(bubbleFrame) * 0.5) - offsetV + + menuV = NSLayoutConstraint(item: self, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: vConstant) + + case .Down: + vConstant -= ceil(CGRectGetHeight(bubbleFrame) * 0.5) - offsetV + + menuV = NSLayoutConstraint(item: self, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: vConstant) + } + + let centerXConstant = CGRectGetMidX(bubbleFrame) - CGRectGetMidX(view.frame) + + let menuCenterX = NSLayoutConstraint(item: self, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1, constant: centerXConstant) + + NSLayoutConstraint.activateConstraints([menuV, menuCenterX]) + + + // animation + + transform = CGAffineTransformMakeScale(0.0001, 0.0001) + + UIView.animateWithDuration(0.25, delay: 0.0, usingSpringWithDamping: 0.65, initialSpringVelocity: 0.0, options: UIViewAnimationOptions(0), animations: { _ in + self.transform = CGAffineTransformMakeScale(1.0, 1.0) + }, completion: { finished in + self.transform = CGAffineTransformIdentity + }) + } + + func hide() { + + UIView.animateWithDuration(0.1, delay: 0.0, options: .CurveEaseInOut, animations: { _ in + self.transform = CGAffineTransformMakeScale(0.0001, 0.0001) + + }, completion: { finished in + self.removeFromSuperview() + }) + } + + // MARK: UI + + let arrowHeight: CGFloat = 8 + let buttonGap: CGFloat = 16 + let offsetV: CGFloat = -1 + + func makeUI() { + + backgroundColor = UIColor.clearColor() + + var format = "H:|" + + for (index, item) in enumerate(items) { + + let button = UIButton() + + switch item.type { + + case .Normal: + + button.setTitleColor(UIColor.whiteColor(), forState: .Normal) + button.setTitleColor(UIColor.lightGrayColor(), forState: .Highlighted) + + case .Danger: + + let dangerRed = UIColor(red: 1, green: 0.4, blue: 0.4, alpha: 1) + + button.setTitleColor(dangerRed, forState: .Normal) + button.setTitleColor(dangerRed.colorWithAlphaComponent(0.3), forState: .Highlighted) + } + + button.setTitle(item.title, forState: .Normal) + button.addTarget(self, action: "tapButton:", forControlEvents: .TouchUpInside) + + button.setTranslatesAutoresizingMaskIntoConstraints(false) + + addSubview(button) + + buttons.append(button) + + if index == 0 { + format += "-[button\(index)]" + } else { + format += "-(\(buttonGap))-[button\(index)(==button0)]" + } + } + + format += "-|" + + var views = [NSObject: AnyObject]() + + for (index, button) in enumerate(buttons) { + let key = "button\(index)" + views[key] = button + } + + let hConstraints = NSLayoutConstraint.constraintsWithVisualFormat(format, options: .AlignAllCenterY, metrics: nil, views: views) + + NSLayoutConstraint.activateConstraints(hConstraints) + + if let firstButton = buttons.first { + + let firstButtonTop = NSLayoutConstraint(item: firstButton, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0) + + switch arrowDirection { + + case .Up: + + let firstButtonTop = NSLayoutConstraint(item: firstButton, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: arrowHeight) + let firstButtonBottom = NSLayoutConstraint(item: firstButton, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: 0) + + NSLayoutConstraint.activateConstraints([firstButtonTop, firstButtonBottom]) + + case .Down: + + let firstButtonTop = NSLayoutConstraint(item: firstButton, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0) + let firstButtonBottom = NSLayoutConstraint(item: firstButton, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: -arrowHeight) + + NSLayoutConstraint.activateConstraints([firstButtonTop, firstButtonBottom]) + } + } + } + + override func drawRect(rect: CGRect) { + + UIColor.blackColor().colorWithAlphaComponent(0.6).setFill() + + // bubble + + var roundedRect = rect + + switch arrowDirection { + + case .Up: + roundedRect.origin.y += arrowHeight + roundedRect.size.height -= arrowHeight + + case .Down: + roundedRect.size.height -= arrowHeight + } + + let bubblePath = UIBezierPath(roundedRect: roundedRect, cornerRadius: 5) + bubblePath.fill() + + + // arrow + + switch arrowDirection { + + case .Up: + let topX = CGRectGetMidX(rect) + let topY = CGRectGetMinY(rect) + + let arrowPath = UIBezierPath() + arrowPath.moveToPoint(CGPointMake(topX, topY)) + arrowPath.addLineToPoint(CGPointMake(topX - (arrowHeight - 1), topY + arrowHeight)) + arrowPath.addLineToPoint(CGPointMake(topX + (arrowHeight - 1), topY + arrowHeight)) + arrowPath.closePath() + arrowPath.fill() + + case .Down: + let buttomX = CGRectGetMidX(rect) + let buttomY = CGRectGetMaxY(rect) + + let arrowPath = UIBezierPath() + arrowPath.moveToPoint(CGPointMake(buttomX, buttomY)) + arrowPath.addLineToPoint(CGPointMake(buttomX - (arrowHeight - 1), buttomY - arrowHeight)) + arrowPath.addLineToPoint(CGPointMake(buttomX + (arrowHeight - 1), buttomY - arrowHeight)) + arrowPath.closePath() + arrowPath.fill() + } + + + // lines + + UIColor.whiteColor().colorWithAlphaComponent(0.3).setStroke() + + for (index, button) in enumerate(buttons) { + if index > 0 { + let line = UIBezierPath() + + let x = CGRectGetMinX(button.frame) - buttonGap * 0.5 + let y = CGRectGetMaxY(rect) + + line.moveToPoint(CGPoint(x: x , y: 0)) + line.addLineToPoint(CGPoint(x: x, y: y)) + + line.lineWidth = 1 + + line.stroke() + } + } + } + +} + + + + diff --git a/Yep/Views/Cells/ChatLeftText/ChatLeftTextCell.swift b/Yep/Views/Cells/ChatLeftText/ChatLeftTextCell.swift index de7f7919..9cb14931 100644 --- a/Yep/Views/Cells/ChatLeftText/ChatLeftTextCell.swift +++ b/Yep/Views/Cells/ChatLeftText/ChatLeftTextCell.swift @@ -22,6 +22,8 @@ class ChatLeftTextCell: UICollectionViewCell { @IBOutlet weak var textContentTextViewLeadingConstraint: NSLayoutConstraint! @IBOutlet weak var textContentTextViewWidthConstraint: NSLayoutConstraint! + var longPressAction: (ChatLeftTextCell -> Void)? + override func awakeFromNib() { super.awakeFromNib() @@ -38,15 +40,6 @@ class ChatLeftTextCell: UICollectionViewCell { NSUnderlineStyleAttributeName: NSNumber(integer: NSUnderlineStyle.StyleSingle.rawValue), ] -// textContentTextView.editable = false -// if let gestureRecognizers = textContentTextView.gestureRecognizers as? [UIGestureRecognizer] { -// for recognizer in gestureRecognizers { -// if recognizer.isKindOfClass(UILongPressGestureRecognizer.self) { -// recognizer.enabled = false -// } -// } -// } - let longPress = UILongPressGestureRecognizer(target: self, action: "handleLongPress:") textContentTextView.addGestureRecognizer(longPress) @@ -59,13 +52,7 @@ class ChatLeftTextCell: UICollectionViewCell { func handleLongPress(longPress: UILongPressGestureRecognizer) { if longPress.state == .Began { - if let view = longPress.view, superview = view.superview { - view.becomeFirstResponder() - - let menu = UIMenuController.sharedMenuController() - menu.setTargetRect(view.frame, inView: superview) - menu.setMenuVisible(true, animated: true) - } + longPressAction?(self) } } diff --git a/Yep/Views/Cells/ChatRightText/ChatRightTextCell.swift b/Yep/Views/Cells/ChatRightText/ChatRightTextCell.swift index 15641756..66e9bf20 100644 --- a/Yep/Views/Cells/ChatRightText/ChatRightTextCell.swift +++ b/Yep/Views/Cells/ChatRightText/ChatRightTextCell.swift @@ -22,10 +22,11 @@ class ChatRightTextCell: ChatRightBaseCell { @IBOutlet weak var textContentTextViewLeadingConstraint: NSLayoutConstraint! @IBOutlet weak var textContentTextViewWidthConstraint: NSLayoutConstraint! - var deleteMessageAction: (() -> Void)? typealias MediaTapAction = () -> Void var mediaTapAction: MediaTapAction? + var longPressAction: (ChatRightTextCell -> Void)? + override func awakeFromNib() { super.awakeFromNib() @@ -43,12 +44,6 @@ class ChatRightTextCell: ChatRightBaseCell { NSUnderlineStyleAttributeName: NSNumber(integer: NSUnderlineStyle.StyleSingle.rawValue), ] - textContentTextView.deleteEnabled = true - textContentTextView.deleteAction = { [weak self] in - println("delete text message?") - self?.deleteMessageAction?() - } - let longPress = UILongPressGestureRecognizer(target: self, action: "handleLongPress:") textContentTextView.addGestureRecognizer(longPress) @@ -65,13 +60,7 @@ class ChatRightTextCell: ChatRightBaseCell { func handleLongPress(longPress: UILongPressGestureRecognizer) { if longPress.state == .Began { - if let view = longPress.view, superview = view.superview { - view.becomeFirstResponder() - - let menu = UIMenuController.sharedMenuController() - menu.setTargetRect(view.frame, inView: superview) - menu.setMenuVisible(true, animated: true) - } + longPressAction?(self) } } diff --git a/Yep/Views/TextViews/ChatTextView.swift b/Yep/Views/TextViews/ChatTextView.swift index f81fcd8e..c9b44672 100644 --- a/Yep/Views/TextViews/ChatTextView.swift +++ b/Yep/Views/TextViews/ChatTextView.swift @@ -10,26 +10,7 @@ import UIKit class ChatTextView: UITextView { -// override func canBecomeFirstResponder() -> Bool { -// return false -// } - - var deleteEnabled = false - var deleteAction: (() -> Void)? - - override func copy(sender: AnyObject?) { - UIPasteboard.generalPasteboard().string = text - } - - override func delete(sender: AnyObject?) { - deleteAction?() - } - - override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool { - if deleteEnabled { - return action == Selector("copy:") || action == Selector("delete:") - } else { - return action == Selector("copy:") - } + override func canBecomeFirstResponder() -> Bool { + return false } }