mirror of
https://github.com/wahyd4/Yep.git
synced 2026-08-18 17:25:59 +10:00
Merge branch 'feature/cell_action_bubble' into develop
This commit is contained in:
@@ -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 = "<group>"; };
|
||||
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>"; };
|
||||
5044BF051B5FA74800930ADC /* BubbleMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BubbleMenuView.swift; path = Views/BubbleMenu/BubbleMenuView.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>"; };
|
||||
@@ -782,6 +784,7 @@
|
||||
5019F62F1B39BFCB0000D993 /* ConversationMore */,
|
||||
500A0B201B451A7400D49358 /* Badge */,
|
||||
504F85AD1B4D21E400F909BE /* DiscoverFilter */,
|
||||
5044BF071B5FA75000930ADC /* BubbleMenu */,
|
||||
);
|
||||
name = Views;
|
||||
sourceTree = "<group>";
|
||||
@@ -1030,6 +1033,14 @@
|
||||
name = Conversation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5044BF071B5FA75000930ADC /* BubbleMenu */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5044BF051B5FA74800930ADC /* BubbleMenuView.swift */,
|
||||
);
|
||||
name = BubbleMenu;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 */,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="15A216g" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Ha4-ol-D2e">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="15A226f" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Ha4-ol-D2e">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
|
||||
<capability name="Alignment constraints with different attributes" minToolsVersion="5.1"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<CGPoint>) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user