Merge branch 'feature/new_text_menu' into develop

This commit is contained in:
nixzhu
2015-08-25 12:26:37 +08:00
8 changed files with 239 additions and 462 deletions
+7 -7
View File
@@ -96,7 +96,7 @@
503A54ED1B328ADB002077BD /* NSLocale+Yep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503A54EC1B328ADB002077BD /* NSLocale+Yep.swift */; };
503CAB431AC00CB100DFE830 /* ConversationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503CAB421AC00CB100DFE830 /* ConversationViewController.swift */; };
503ED23A1B8ADCE70072187D /* BlackListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503ED2391B8ADCE70072187D /* BlackListViewController.swift */; };
5044BF061B5FA74800930ADC /* BubbleMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044BF051B5FA74800930ADC /* BubbleMenuView.swift */; };
503ED23D1B8C12100072187D /* ChatTextContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503ED23C1B8C12100072187D /* ChatTextContainerView.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 */; };
@@ -345,7 +345,7 @@
503A54EC1B328ADB002077BD /* NSLocale+Yep.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSLocale+Yep.swift"; path = "Extensions/NSLocale+Yep.swift"; sourceTree = "<group>"; };
503CAB421AC00CB100DFE830 /* ConversationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConversationViewController.swift; path = ViewControllers/Conversation/ConversationViewController.swift; sourceTree = "<group>"; };
503ED2391B8ADCE70072187D /* BlackListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BlackListViewController.swift; path = ViewControllers/BlackList/BlackListViewController.swift; sourceTree = "<group>"; };
5044BF051B5FA74800930ADC /* BubbleMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BubbleMenuView.swift; path = Views/BubbleMenu/BubbleMenuView.swift; sourceTree = "<group>"; };
503ED23C1B8C12100072187D /* ChatTextContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ChatTextContainerView.swift; path = Views/ChatTextContainer/ChatTextContainerView.swift; sourceTree = "<group>"; };
504BDC141B4528A500DBD35D /* Badges.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Badges.xcassets; sourceTree = "<group>"; };
504CBA3C1B3D04B800663A90 /* ChatTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ChatTextView.swift; path = Views/TextViews/ChatTextView.swift; sourceTree = "<group>"; };
504F41241ACE2D1D00FBB19A /* SampleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SampleView.swift; path = Views/AudioWaves/SampleView.swift; sourceTree = "<group>"; };
@@ -826,6 +826,7 @@
504CBA3B1B3D048900663A90 /* TextViews */,
50F2B9791B2EEAE300F840CC /* Labels */,
50A9D4581ACD3943000B2599 /* AudioWaves */,
503ED23E1B8C13280072187D /* ChatTextContainer */,
5023DE3A1AB697A700B3EE96 /* Cells */,
502AE5221AB71C82005BD199 /* Buttons */,
502AE52F1AB8387D005BD199 /* TextFields */,
@@ -843,7 +844,6 @@
5019F62F1B39BFCB0000D993 /* ConversationMore */,
500A0B201B451A7400D49358 /* Badge */,
504F85AD1B4D21E400F909BE /* DiscoverFilter */,
5044BF071B5FA75000930ADC /* BubbleMenu */,
8409137E1B7DB178001C89D6 /* FriendRequest */,
);
name = Views;
@@ -1104,12 +1104,12 @@
name = BlackList;
sourceTree = "<group>";
};
5044BF071B5FA75000930ADC /* BubbleMenu */ = {
503ED23E1B8C13280072187D /* ChatTextContainer */ = {
isa = PBXGroup;
children = (
5044BF051B5FA74800930ADC /* BubbleMenuView.swift */,
503ED23C1B8C12100072187D /* ChatTextContainerView.swift */,
);
name = BubbleMenu;
name = ChatTextContainer;
sourceTree = "<group>";
};
504CBA3B1B3D048900663A90 /* TextViews */ = {
@@ -1925,6 +1925,7 @@
508C2AA41AD661B3002B8097 /* UserStateCell.swift in Sources */,
0A5D83941AC5D3C8000045BF /* YepTabBarController.swift in Sources */,
50E114FC1ABC549300F13000 /* ContactsCell.swift in Sources */,
503ED23D1B8C12100072187D /* ChatTextContainerView.swift in Sources */,
50F2B9781B2EDF3D00F840CC /* BorderTextField.swift in Sources */,
50CDBE461ACA9F2200459CE0 /* ImageCache.swift in Sources */,
505EC81C1ADFBE1B001D27E0 /* SkillSelectionCell.swift in Sources */,
@@ -1984,7 +1985,6 @@
50CDBE491ACBAD3200459CE0 /* ChatLeftImageCell.swift in Sources */,
5033159C1AC101C80008A209 /* ChatRightTextCell.swift in Sources */,
50E1D7291B870D7500D90A82 /* NSParagraphStyle+Yep.swift in Sources */,
5044BF061B5FA74800930ADC /* BubbleMenuView.swift in Sources */,
5023DE561AB6CA1000B3EE96 /* AvatarCache.swift in Sources */,
5023DE371AB6979600B3EE96 /* ConversationCell.swift in Sources */,
502AE5351AB8649F005BD199 /* LoginByMobileViewController.swift in Sources */,
@@ -84,18 +84,8 @@ class ConversationViewController: BaseViewController {
@IBOutlet weak var swipeUpView: UIView!
@IBOutlet weak var swipeUpPromptLabel: UILabel!
lazy var menuDirectionUpThreshold: CGFloat = {
return self.topBarsHeight + 60
}()
var currentMenu: BubbleMenuView?
var isTryingShowFriendRequestView = false
func removeOldMenu() {
currentMenu?.hide()
currentMenu = nil
}
var originalNavigationControllerDelegate: UINavigationControllerDelegate?
var waverView: YepWaverView!
@@ -557,8 +547,6 @@ class ConversationViewController: BaseViewController {
messageToolbar.stateTransitionAction = { [weak self] (messageToolbar, previousState, currentState) in
self?.removeOldMenu()
if let strongSelf = self {
switch (previousState, currentState) {
@@ -751,12 +739,6 @@ class ConversationViewController: BaseViewController {
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
removeOldMenu()
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
@@ -1202,8 +1184,6 @@ class ConversationViewController: BaseViewController {
func moreAction() {
removeOldMenu()
messageToolbar.state = .Default
moreView.showProfileAction = { [weak self] in
@@ -1973,33 +1953,6 @@ 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) < strongSelf.menuDirectionUpThreshold ? .Up : .Down
let menu = BubbleMenuView(arrowDirection: arrowDirection, items: [copyItem])
strongSelf.currentMenu = menu
menu.showInView(strongSelf.view, withBubbleFrame: bubbleFrame)
}
}
cell.tapAvatarAction = { [weak self] user in
self?.performSegueWithIdentifier("showProfile", sender: user)
}
@@ -2182,109 +2135,77 @@ extension ConversationViewController: UICollectionViewDataSource, UICollectionVi
}
}, collectionView: collectionView, indexPath: indexPath)
cell.longPressAction = { [weak self] cell in
cell.longPressAction = { [weak self] in
dispatch_async(dispatch_get_main_queue()) {
if let strongSelf = self, realm = message.realm {
self?.removeOldMenu()
var sectionDateMessage: Message?
if let strongSelf = self {
if let currentMessageIndex = strongSelf.messages.indexOf(message) {
let copyItem = BubbleMenuView.Item(type: .Normal, title: NSLocalizedString("Copy", comment: "")) { menu in
print("copy\n")
let previousMessageIndex = currentMessageIndex - 1
UIPasteboard.generalPasteboard().string = cell.textContentTextView.text
if let previousMessage = strongSelf.messages[safe: previousMessageIndex] {
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
}
}
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 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
}
let bubbleFrame = cell.convertRect(cell.bubbleBodyImageView.frame, toView: strongSelf.view)
let arrowDirection: BubbleMenuView.ArrowDirection = CGRectGetMidY(bubbleFrame) < strongSelf.menuDirectionUpThreshold ? .Up : .Down
let menu = BubbleMenuView(arrowDirection: arrowDirection, items: [copyItem, deleteItem])
strongSelf.currentMenu = menu
menu.showInView(strongSelf.view, withBubbleFrame: bubbleFrame)
}
}
@@ -2340,8 +2261,6 @@ extension ConversationViewController: UICollectionViewDataSource, UICollectionVi
func scrollViewDidScroll(scrollView: UIScrollView) {
pullToRefreshView.scrollViewDidScroll(scrollView)
removeOldMenu()
}
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
-269
View File
@@ -1,269 +0,0 @@
//
// 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()
}
}
}
}
@@ -15,13 +15,12 @@ class ChatLeftTextCell: ChatBaseCell {
@IBOutlet weak var bubbleBodyImageView: UIImageView!
@IBOutlet weak var bubbleTailImageView: UIImageView!
@IBOutlet weak var textContainerView: ChatTextContainerView!
@IBOutlet weak var textContentTextView: ChatTextView!
@IBOutlet weak var textContentTextViewTrailingConstraint: NSLayoutConstraint!
@IBOutlet weak var textContentTextViewLeadingConstraint: NSLayoutConstraint!
//@IBOutlet weak var textContentTextViewWidthConstraint: NSLayoutConstraint!
var longPressAction: (ChatLeftTextCell -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
@@ -38,7 +37,12 @@ class ChatLeftTextCell: ChatBaseCell {
]
let longPress = UILongPressGestureRecognizer(target: self, action: "handleLongPress:")
textContentTextView.addGestureRecognizer(longPress)
textContainerView.addGestureRecognizer(longPress)
longPress.delegate = self
textContainerView.copyTextAction = { [weak self] in
UIPasteboard.generalPasteboard().string = self?.textContentTextView.text
}
textContentTextViewTrailingConstraint.constant = YepConfig.chatTextGapBetweenWallAndContentLabel() - YepConfig.ChatCell.magicWidth
textContentTextViewLeadingConstraint.constant = YepConfig.chatCellGapBetweenTextContentLabelAndAvatar()
@@ -49,7 +53,17 @@ class ChatLeftTextCell: ChatBaseCell {
func handleLongPress(longPress: UILongPressGestureRecognizer) {
if longPress.state == .Began {
longPressAction?(self)
if let view = longPress.view, superview = view.superview {
view.becomeFirstResponder()
let menu = UIMenuController.sharedMenuController()
let copyItem = UIMenuItem(title: NSLocalizedString("Copy", comment: ""), action:"copyText")
menu.menuItems = [copyItem]
menu.setTargetRect(view.frame, inView: superview)
menu.setMenuVisible(true, animated: true)
}
}
}
@@ -75,3 +89,16 @@ class ChatLeftTextCell: ChatBaseCell {
}
}
extension ChatLeftTextCell: UIGestureRecognizerDelegate {
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOfGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
@@ -34,32 +34,44 @@
<constraint firstAttribute="width" secondItem="hYu-LI-zcX" secondAttribute="height" multiplier="1:1" id="LI4-Sm-y72"/>
</constraints>
</imageView>
<textView clipsSubviews="YES" contentMode="scaleToFill" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" editable="NO" text="Text Content" translatesAutoresizingMaskIntoConstraints="NO" id="o2v-vm-ItZ" customClass="ChatTextView" customModule="Yep" customModuleProvider="target">
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ieh-wh-12u" customClass="ChatTextContainerView" customModule="Yep" customModuleProvider="target">
<rect key="frame" x="75" y="3" width="92" height="123"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<dataDetectorType key="dataDetectorTypes" phoneNumber="YES" link="YES" address="YES" calendarEvent="YES"/>
</textView>
<subviews>
<textView clipsSubviews="YES" contentMode="scaleToFill" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" editable="NO" text="Text Content" translatesAutoresizingMaskIntoConstraints="NO" id="o2v-vm-ItZ" customClass="ChatTextView" customModule="Yep" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="92" height="123"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<dataDetectorType key="dataDetectorTypes" phoneNumber="YES" link="YES" address="YES" calendarEvent="YES"/>
</textView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="o2v-vm-ItZ" secondAttribute="trailing" id="751-18-ycS"/>
<constraint firstItem="o2v-vm-ItZ" firstAttribute="leading" secondItem="Ieh-wh-12u" secondAttribute="leading" id="LvO-5D-arq"/>
<constraint firstItem="o2v-vm-ItZ" firstAttribute="top" secondItem="Ieh-wh-12u" secondAttribute="top" id="NiX-G4-RiT"/>
<constraint firstAttribute="bottom" secondItem="o2v-vm-ItZ" secondAttribute="bottom" id="RvB-S3-Pgd"/>
</constraints>
</view>
</subviews>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<animations/>
<constraints>
<constraint firstAttribute="bottom" secondItem="o2v-vm-ItZ" secondAttribute="bottom" constant="3" id="4HG-w9-FvJ"/>
<constraint firstItem="hYu-LI-zcX" firstAttribute="centerY" secondItem="ppr-iG-zxj" secondAttribute="centerY" id="CqX-BA-AO1"/>
<constraint firstItem="o2v-vm-ItZ" firstAttribute="top" secondItem="Yfx-Em-Job" secondAttribute="top" constant="3" id="MHY-GR-4fS"/>
<constraint firstItem="o2v-vm-ItZ" firstAttribute="bottom" secondItem="Yfx-Em-Job" secondAttribute="bottom" constant="-3" id="MIb-vK-cmz"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="o2v-vm-ItZ" secondAttribute="trailing" constant="50" id="Tpu-kA-CsQ"/>
<constraint firstItem="o2v-vm-ItZ" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" constant="3" id="aOB-ao-yJA"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Ieh-wh-12u" secondAttribute="trailing" constant="40" id="R7d-pr-61M"/>
<constraint firstItem="Ieh-wh-12u" firstAttribute="leading" secondItem="hYu-LI-zcX" secondAttribute="trailing" constant="20" id="SFJ-md-0X6"/>
<constraint firstItem="Yfx-Em-Job" firstAttribute="top" secondItem="hYu-LI-zcX" secondAttribute="top" id="cCW-R1-CFn"/>
<constraint firstItem="Ieh-wh-12u" firstAttribute="top" secondItem="Yfx-Em-Job" secondAttribute="top" constant="3" id="cHR-67-SWw"/>
<constraint firstItem="Ieh-wh-12u" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" constant="3" id="ezx-XB-nhW"/>
<constraint firstItem="Ieh-wh-12u" firstAttribute="leading" secondItem="Yfx-Em-Job" secondAttribute="leading" constant="12" id="gj0-ap-nOp"/>
<constraint firstItem="hYu-LI-zcX" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" constant="15" id="hM8-00-nUm"/>
<constraint firstItem="o2v-vm-ItZ" firstAttribute="leading" secondItem="Yfx-Em-Job" secondAttribute="leading" constant="12" id="nPH-jo-SPd"/>
<constraint firstItem="o2v-vm-ItZ" firstAttribute="trailing" secondItem="Yfx-Em-Job" secondAttribute="trailing" constant="-12" id="ohu-dH-zSM"/>
<constraint firstItem="o2v-vm-ItZ" firstAttribute="leading" secondItem="ppr-iG-zxj" secondAttribute="trailing" constant="5" id="uyS-e2-1DM"/>
<constraint firstItem="o2v-vm-ItZ" firstAttribute="leading" secondItem="hYu-LI-zcX" secondAttribute="trailing" constant="20" id="x4c-mB-0Ui"/>
<constraint firstItem="Ieh-wh-12u" firstAttribute="bottom" secondItem="Yfx-Em-Job" secondAttribute="bottom" constant="-3" id="lAw-xK-S4p"/>
<constraint firstItem="Ieh-wh-12u" firstAttribute="trailing" secondItem="Yfx-Em-Job" secondAttribute="trailing" constant="-12" id="uOJ-OF-AyG"/>
<constraint firstItem="Yfx-Em-Job" firstAttribute="leading" secondItem="ppr-iG-zxj" secondAttribute="trailing" constant="-7" id="vNs-HM-uGF"/>
<constraint firstAttribute="bottom" secondItem="Ieh-wh-12u" secondAttribute="bottom" constant="3" id="z32-pR-mtC"/>
</constraints>
<size key="customSize" width="350" height="129"/>
<connections>
@@ -68,9 +80,10 @@
<outlet property="avatarImageViewWidthConstraint" destination="JV9-zT-1At" id="fe4-74-Gyw"/>
<outlet property="bubbleBodyImageView" destination="Yfx-Em-Job" id="bQk-7x-1RF"/>
<outlet property="bubbleTailImageView" destination="ppr-iG-zxj" id="kxh-jI-4L8"/>
<outlet property="textContainerView" destination="Ieh-wh-12u" id="VVJ-yk-Twz"/>
<outlet property="textContentTextView" destination="o2v-vm-ItZ" id="lj4-7V-6TH"/>
<outlet property="textContentTextViewLeadingConstraint" destination="x4c-mB-0Ui" id="LPr-oY-JOg"/>
<outlet property="textContentTextViewTrailingConstraint" destination="Tpu-kA-CsQ" id="FYH-1w-mbJ"/>
<outlet property="textContentTextViewLeadingConstraint" destination="SFJ-md-0X6" id="Icw-33-Beu"/>
<outlet property="textContentTextViewTrailingConstraint" destination="R7d-pr-61M" id="GFh-QJ-Fg8"/>
</connections>
<point key="canvasLocation" x="338" y="346.5"/>
</collectionViewCell>
@@ -15,6 +15,7 @@ class ChatRightTextCell: ChatRightBaseCell {
@IBOutlet weak var bubbleBodyImageView: UIImageView!
@IBOutlet weak var bubbleTailImageView: UIImageView!
@IBOutlet weak var textContainerView: ChatTextContainerView!
@IBOutlet weak var textContentTextView: ChatTextView!
@IBOutlet weak var textContentTextViewTrailingConstraint: NSLayoutConstraint!
@IBOutlet weak var textContentTextViewLeadingConstraint: NSLayoutConstraint!
@@ -23,7 +24,7 @@ class ChatRightTextCell: ChatRightBaseCell {
typealias MediaTapAction = () -> Void
var mediaTapAction: MediaTapAction?
var longPressAction: (ChatRightTextCell -> Void)?
var longPressAction: (() -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
@@ -42,7 +43,16 @@ class ChatRightTextCell: ChatRightBaseCell {
]
let longPress = UILongPressGestureRecognizer(target: self, action: "handleLongPress:")
textContentTextView.addGestureRecognizer(longPress)
textContainerView.addGestureRecognizer(longPress)
longPress.delegate = self
textContainerView.copyTextAction = { [weak self] in
UIPasteboard.generalPasteboard().string = self?.textContentTextView.text
}
textContainerView.deleteTextMessageAction = { [weak self] in
self?.longPressAction?()
}
textContentTextViewTrailingConstraint.constant = YepConfig.chatCellGapBetweenTextContentLabelAndAvatar()
textContentTextViewLeadingConstraint.constant = YepConfig.chatTextGapBetweenWallAndContentLabel() - YepConfig.ChatCell.magicWidth
@@ -57,7 +67,18 @@ class ChatRightTextCell: ChatRightBaseCell {
func handleLongPress(longPress: UILongPressGestureRecognizer) {
if longPress.state == .Began {
longPressAction?(self)
if let view = longPress.view, superview = view.superview {
view.becomeFirstResponder()
let menu = UIMenuController.sharedMenuController()
let copyItem = UIMenuItem(title: NSLocalizedString("Copy", comment: ""), action:"copyText")
let deleteItem = UIMenuItem(title: NSLocalizedString("Delete", comment: ""), action:"deleteTextMessage")
menu.menuItems = [copyItem, deleteItem]
menu.setTargetRect(view.frame, inView: superview)
menu.setMenuVisible(true, animated: true)
}
}
}
@@ -89,3 +110,17 @@ class ChatRightTextCell: ChatRightBaseCell {
}
}
}
extension ChatRightTextCell: UIGestureRecognizerDelegate {
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOfGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
@@ -34,14 +34,6 @@
<constraint firstAttribute="width" constant="40" id="hm6-ty-e5H"/>
</constraints>
</imageView>
<textView clipsSubviews="YES" contentMode="scaleToFill" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" editable="NO" text="Text Content" translatesAutoresizingMaskIntoConstraints="NO" id="em7-jr-W4a" customClass="ChatTextView" customModule="Yep" customModuleProvider="target">
<rect key="frame" x="333" y="3" width="92" height="125"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<dataDetectorType key="dataDetectorTypes" phoneNumber="YES" link="YES" address="YES" calendarEvent="YES"/>
</textView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="icon_dot_sending" translatesAutoresizingMaskIntoConstraints="NO" id="tB7-GG-3Zd" userLabel="Dot Image View">
<rect key="frame" x="310" y="62.5" width="6" height="6"/>
<animations/>
@@ -50,25 +42,45 @@
<constraint firstAttribute="width" constant="6" id="ncl-qf-tmv"/>
</constraints>
</imageView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6da-H4-jC2" customClass="ChatTextContainerView" customModule="Yep" customModuleProvider="target">
<rect key="frame" x="333" y="3" width="92" height="125"/>
<subviews>
<textView clipsSubviews="YES" contentMode="scaleToFill" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" editable="NO" text="Text Content" translatesAutoresizingMaskIntoConstraints="NO" id="em7-jr-W4a" customClass="ChatTextView" customModule="Yep" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="92" height="125"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<dataDetectorType key="dataDetectorTypes" phoneNumber="YES" link="YES" address="YES" calendarEvent="YES"/>
</textView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="em7-jr-W4a" firstAttribute="top" secondItem="6da-H4-jC2" secondAttribute="top" id="Ipq-Cf-Cqw"/>
<constraint firstItem="em7-jr-W4a" firstAttribute="leading" secondItem="6da-H4-jC2" secondAttribute="leading" id="K1B-jz-hNU"/>
<constraint firstAttribute="trailing" secondItem="em7-jr-W4a" secondAttribute="trailing" id="ZAC-Ol-sRC"/>
<constraint firstAttribute="bottom" secondItem="em7-jr-W4a" secondAttribute="bottom" id="pzv-BH-Rot"/>
</constraints>
</view>
</subviews>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<animations/>
<constraints>
<constraint firstItem="em7-jr-W4a" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="gTV-IL-0wX" secondAttribute="leading" constant="50" id="ABk-EQ-Edt"/>
<constraint firstItem="RKK-8d-9vO" firstAttribute="leading" secondItem="em7-jr-W4a" secondAttribute="trailing" constant="20" id="Dsx-Jg-HtG"/>
<constraint firstItem="em7-jr-W4a" firstAttribute="top" secondItem="73l-kE-1KG" secondAttribute="top" constant="3" id="Nzn-Kc-TWM"/>
<constraint firstItem="em7-jr-W4a" firstAttribute="bottom" secondItem="73l-kE-1KG" secondAttribute="bottom" constant="-3" id="Pz2-Yd-kF9"/>
<constraint firstAttribute="bottom" secondItem="6da-H4-jC2" secondAttribute="bottom" constant="3" id="0MI-n7-CQK"/>
<constraint firstItem="6da-H4-jC2" firstAttribute="bottom" secondItem="73l-kE-1KG" secondAttribute="bottom" constant="-3" id="8U8-ia-5w1"/>
<constraint firstItem="RKK-8d-9vO" firstAttribute="leading" secondItem="6da-H4-jC2" secondAttribute="trailing" constant="20" id="ET9-4m-yXe"/>
<constraint firstItem="1JC-jy-KNh" firstAttribute="leading" secondItem="73l-kE-1KG" secondAttribute="trailing" constant="-7" id="Ewb-Xc-fpj"/>
<constraint firstItem="6da-H4-jC2" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="gTV-IL-0wX" secondAttribute="leading" constant="40" id="IFK-hl-cQK"/>
<constraint firstItem="6da-H4-jC2" firstAttribute="leading" secondItem="73l-kE-1KG" secondAttribute="leading" constant="12" id="OBi-9l-myf"/>
<constraint firstItem="73l-kE-1KG" firstAttribute="top" secondItem="RKK-8d-9vO" secondAttribute="top" id="QzP-5b-ovG"/>
<constraint firstItem="73l-kE-1KG" firstAttribute="leading" secondItem="tB7-GG-3Zd" secondAttribute="trailing" constant="5" id="UIT-W7-WO6"/>
<constraint firstItem="1JC-jy-KNh" firstAttribute="centerY" secondItem="RKK-8d-9vO" secondAttribute="centerY" id="Upo-MS-aQJ"/>
<constraint firstItem="em7-jr-W4a" firstAttribute="leading" secondItem="73l-kE-1KG" secondAttribute="leading" constant="12" id="WkS-Oi-AjM"/>
<constraint firstAttribute="bottom" secondItem="em7-jr-W4a" secondAttribute="bottom" constant="3" id="dXH-j4-Dn8"/>
<constraint firstItem="6da-H4-jC2" firstAttribute="trailing" secondItem="73l-kE-1KG" secondAttribute="trailing" constant="-12" id="dMB-tv-isj"/>
<constraint firstItem="6da-H4-jC2" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" constant="3" id="gu0-N0-n98"/>
<constraint firstItem="tB7-GG-3Zd" firstAttribute="centerY" secondItem="73l-kE-1KG" secondAttribute="centerY" id="h7l-0p-SWm"/>
<constraint firstItem="1JC-jy-KNh" firstAttribute="leading" secondItem="em7-jr-W4a" secondAttribute="trailing" constant="5" id="iAD-hS-cmr"/>
<constraint firstItem="73l-kE-1KG" firstAttribute="trailing" secondItem="em7-jr-W4a" secondAttribute="trailing" constant="12" id="sFr-bX-lfC"/>
<constraint firstItem="em7-jr-W4a" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" constant="3" id="vdd-9g-iCq"/>
<constraint firstItem="6da-H4-jC2" firstAttribute="top" secondItem="73l-kE-1KG" secondAttribute="top" constant="3" id="p2y-Oi-B1W"/>
<constraint firstAttribute="trailing" secondItem="RKK-8d-9vO" secondAttribute="trailing" constant="15" id="vs0-4M-TaV"/>
</constraints>
<size key="customSize" width="417" height="131"/>
@@ -80,9 +92,10 @@
<outlet property="bubbleTailImageView" destination="1JC-jy-KNh" id="MGo-KB-Kb3"/>
<outlet property="dotImageView" destination="tB7-GG-3Zd" id="odg-hQ-nba"/>
<outlet property="gapBetweenDotImageViewAndBubbleConstraint" destination="UIT-W7-WO6" id="3P5-Dl-2GX"/>
<outlet property="textContainerView" destination="6da-H4-jC2" id="jNs-2X-eia"/>
<outlet property="textContentTextView" destination="em7-jr-W4a" id="Pch-yz-67H"/>
<outlet property="textContentTextViewLeadingConstraint" destination="ABk-EQ-Edt" id="ZFU-Y1-eXX"/>
<outlet property="textContentTextViewTrailingConstraint" destination="Dsx-Jg-HtG" id="YPC-jA-wZK"/>
<outlet property="textContentTextViewLeadingConstraint" destination="IFK-hl-cQK" id="2gq-ft-jvH"/>
<outlet property="textContentTextViewTrailingConstraint" destination="ET9-4m-yXe" id="aCa-1k-pBI"/>
</connections>
<point key="canvasLocation" x="339" y="326.5"/>
</collectionViewCell>
@@ -0,0 +1,39 @@
//
// ChatTextContainerView.swift
// Yep
//
// Created by nixzhu on 15/8/25.
// Copyright (c) 2015 Catch Inc. All rights reserved.
//
import UIKit
class ChatTextContainerView: UIView {
override func canBecomeFirstResponder() -> Bool {
return true
}
override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
if action == "copyText" {
return true
} else if action == "deleteTextMessage" {
return true
}
return false
}
var copyTextAction: (() -> Void)?
var deleteTextMessageAction: (() -> Void)?
func copyText() {
println("copyText")
copyTextAction?()
}
func deleteTextMessage() {
println("deleteTextMessage")
deleteTextMessageAction?()
}
}