You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

601 lines
24 KiB

//
// XHMessageInputView.m
// MessageDisplayExample
//
// Created by HUAJIE-1 on 14-4-24.
// Copyright (c) 2014年 嗨,我是曾宪华(@xhzengAIB),曾加入YY Inc.担任高级移动开发工程师,拍立秀App联合创始人,热衷于简洁、而富有理性的事物 QQ:543413507 主页:http://zengxianhua.com All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
#import "XHMessageInputView.h"
#import "NSString+MessageInputView.h"
#import "XHMacro.h"
#import "XHConfigurationHelper.h"
@interface XHMessageInputView () <UITextViewDelegate>
@property (nonatomic, weak, readwrite) XHMessageTextView *inputTextView;
@property (nonatomic, weak, readwrite) UIButton *voiceChangeButton;
@property (nonatomic, weak, readwrite) UIButton *multiMediaSendButton;
@property (nonatomic, weak, readwrite) UIButton *faceSendButton;
@property (nonatomic, weak, readwrite) UIButton *holdDownButton;
/**
* 是否取消錄音
*/
@property (nonatomic, assign, readwrite) BOOL isCancelled;
/**
* 是否正在錄音
*/
@property (nonatomic, assign, readwrite) BOOL isRecording;
/**
* 在切换语音和文本消息的时候,需要保存原本已经输入的文本,这样达到一个好的UE
*/
@property (nonatomic, copy) NSString *inputedText;
/**
* 输入框内的所有按钮,点击事件所触发的方法
*
* @param sender 被点击的按钮对象
*/
- (void)messageStyleButtonClicked:(UIButton *)sender;
/**
* 当录音按钮被按下所触发的事件,这时候是开始录音
*/
- (void)holdDownButtonTouchDown;
/**
* 当手指在录音按钮范围之外离开屏幕所触发的事件,这时候是取消录音
*/
- (void)holdDownButtonTouchUpOutside;
/**
* 当手指在录音按钮范围之内离开屏幕所触发的事件,这时候是完成录音
*/
- (void)holdDownButtonTouchUpInside;
/**
* 当手指滑动到录音按钮的范围之外所触发的事件
*/
- (void)holdDownDragOutside;
/**
* 当手指滑动到录音按钮的范围之内所触发的时间
*/
- (void)holdDownDragInside;
#pragma mark - layout subViews UI
/**
* 根据正常显示和高亮状态创建一个按钮对象
*
* @param image 正常显示图
* @param hlImage 高亮显示图
*
* @return 返回按钮对象
*/
- (UIButton *)createButtonWithImage:(UIImage *)image HLImage:(UIImage *)hlImage ;
/**
* 根据输入框的样式类型配置输入框的样式和UI布局
*
* @param style 输入框样式类型
*/
- (void)setupMessageInputViewBarWithStyle:(XHMessageInputViewStyle)style ;
/**
* 配置默认参数
*/
- (void)setup ;
#pragma mark - Message input view
/**
* 动态改变textView的高度
*
* @param changeInHeight 动态的高度
*/
- (void)adjustTextViewHeightBy:(CGFloat)changeInHeight;
@end
@implementation XHMessageInputView
#pragma mark - Action
- (void)messageStyleButtonClicked:(UIButton *)sender {
NSInteger index = sender.tag;
switch (index) {
case 0: {
sender.selected = !sender.selected;
if (sender.selected) {
self.inputedText = self.inputTextView.text;
self.inputTextView.text = @"";
[self.inputTextView resignFirstResponder];
} else {
self.inputTextView.text = self.inputedText;
self.inputedText = nil;
[self.inputTextView becomeFirstResponder];
}
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.holdDownButton.alpha = sender.selected;
self.inputTextView.alpha = !sender.selected;
} completion:^(BOOL finished) {
}];
if ([self.delegate respondsToSelector:@selector(didChangeSendVoiceAction:)]) {
[self.delegate didChangeSendVoiceAction:sender.selected];
}
break;
}
case 1: {
sender.selected = !sender.selected;
self.voiceChangeButton.selected = !sender.selected;
if (!sender.selected) {
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.holdDownButton.alpha = sender.selected;
self.inputTextView.alpha = !sender.selected;
} completion:^(BOOL finished) {
}];
} else {
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.holdDownButton.alpha = !sender.selected;
self.inputTextView.alpha = sender.selected;
} completion:^(BOOL finished) {
}];
}
if ([self.delegate respondsToSelector:@selector(didSendFaceAction:)]) {
[self.delegate didSendFaceAction:sender.selected];
}
break;
}
case 2: {
self.faceSendButton.selected = NO;
if ([self.delegate respondsToSelector:@selector(didSelectedMultipleMediaAction)]) {
[self.delegate didSelectedMultipleMediaAction];
}
break;
}
default:
break;
}
}
- (void)holdDownButtonTouchDown {
self.isCancelled = NO;
self.isRecording = NO;
if ([self.delegate respondsToSelector:@selector(prepareRecordingVoiceActionWithCompletion:)]) {
WEAKSELF
//這邊回調 return 的 YES, 或 NO, 可以讓底層知道該次錄音是否成功, 進而處理無用的 record 對象
[self.delegate prepareRecordingVoiceActionWithCompletion:^BOOL{
STRONGSELF
//這邊要判斷回調回來的時候, 使用者是不是已經早就鬆開手了
if (strongSelf && !strongSelf.isCancelled) {
strongSelf.isRecording = YES;
[strongSelf.delegate didStartRecordingVoiceAction];
return YES;
} else {
return NO;
}
}];
}
}
- (void)holdDownButtonTouchUpOutside {
//如果已經開始錄音了, 才需要做取消的動作, 否則只要切換 isCancelled, 不讓錄音開始.
if (self.isRecording) {
if ([self.delegate respondsToSelector:@selector(didCancelRecordingVoiceAction)]) {
[self.delegate didCancelRecordingVoiceAction];
}
} else {
self.isCancelled = YES;
}
}
- (void)holdDownButtonTouchUpInside {
//如果已經開始錄音了, 才需要做結束的動作, 否則只要切換 isCancelled, 不讓錄音開始.
if (self.isRecording) {
if ([self.delegate respondsToSelector:@selector(didFinishRecoingVoiceAction)]) {
[self.delegate didFinishRecoingVoiceAction];
}
} else {
self.isCancelled = YES;
}
}
- (void)holdDownDragOutside {
//如果已經開始錄音了, 才需要做拖曳出去的動作, 否則只要切換 isCancelled, 不讓錄音開始.
if (self.isRecording) {
if ([self.delegate respondsToSelector:@selector(didDragOutsideAction)]) {
[self.delegate didDragOutsideAction];
}
} else {
self.isCancelled = YES;
}
}
- (void)holdDownDragInside {
//如果已經開始錄音了, 才需要做拖曳回來的動作, 否則只要切換 isCancelled, 不讓錄音開始.
if (self.isRecording) {
if ([self.delegate respondsToSelector:@selector(didDragInsideAction)]) {
[self.delegate didDragInsideAction];
}
} else {
self.isCancelled = YES;
}
}
#pragma mark - layout subViews UI
- (UIButton *)createButtonWithImage:(UIImage *)image HLImage:(UIImage *)hlImage {
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, [XHMessageInputView textViewLineHeight], [XHMessageInputView textViewLineHeight])];
if (image)
[button setBackgroundImage:image forState:UIControlStateNormal];
if (hlImage)
[button setBackgroundImage:hlImage forState:UIControlStateHighlighted];
return button;
}
- (void)setupMessageInputViewBarWithStyle:(XHMessageInputViewStyle)style {
// 配置输入工具条的样式和布局
// 需要显示按钮的总宽度,包括间隔在内
CGFloat allButtonWidth = 0.0;
// 水平间隔
CGFloat horizontalPadding = 8;
// 垂直间隔
CGFloat verticalPadding = 5;
// 输入框
CGFloat textViewLeftMargin = ((style == XHMessageInputViewStyleFlat) ? 6.0 : 4.0);
// 每个按钮统一使用的frame变量
CGRect buttonFrame;
// 按钮对象消息
UIButton *button;
// 允许发送语音
if (self.allowsSendVoice) {
NSString *voiceNormalImageName = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewVoiceNormalImageNameKey];
if (!voiceNormalImageName) {
voiceNormalImageName = @"voice";
}
NSString *voiceHLImageName = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewVoiceHLImageNameKey];
if (!voiceHLImageName) {
voiceHLImageName = @"voice_HL";
}
NSString *keyboardNormalImageName = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewKeyboardNormalImageNameKey];
if (!keyboardNormalImageName) {
keyboardNormalImageName = @"keyboard";
}
NSString *keyboardHLImageName = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewKeyboardHLImageNameKey];
if (!keyboardHLImageName) {
keyboardHLImageName = @"keyboard_HL";
}
button = [self createButtonWithImage:[UIImage imageNamed:voiceNormalImageName] HLImage:[UIImage imageNamed:voiceHLImageName]];
[button addTarget:self action:@selector(messageStyleButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
button.tag = 0;
[button setBackgroundImage:[UIImage imageNamed:keyboardNormalImageName] forState:UIControlStateSelected];
buttonFrame = button.frame;
buttonFrame.origin = CGPointMake(horizontalPadding, verticalPadding);
button.frame = buttonFrame;
[self addSubview:button];
allButtonWidth += CGRectGetMaxX(buttonFrame);
textViewLeftMargin += CGRectGetMaxX(buttonFrame);
self.voiceChangeButton = button;
}
// 允许发送多媒体消息,为什么不是先放表情按钮呢?因为布局的需要!
if (self.allowsSendMultiMedia) {
NSString *extensionNormalImageName = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewExtensionNormalImageNameKey];
if (!extensionNormalImageName) {
extensionNormalImageName = @"multiMedia";
}
NSString *extensionHLImageName = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewExtensionHLImageNameKey];
if (!extensionHLImageName) {
extensionHLImageName = @"multiMedia_HL";
}
button = [self createButtonWithImage:[UIImage imageNamed:extensionNormalImageName] HLImage:[UIImage imageNamed:extensionHLImageName]];
button.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
[button addTarget:self action:@selector(messageStyleButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
button.tag = 2;
buttonFrame = button.frame;
buttonFrame.origin = CGPointMake(CGRectGetWidth(self.bounds) - horizontalPadding - CGRectGetWidth(buttonFrame), verticalPadding);
button.frame = buttonFrame;
[self addSubview:button];
allButtonWidth += CGRectGetWidth(buttonFrame) + horizontalPadding * 2.5;
self.multiMediaSendButton = button;
}
// 允许发送表情
if (self.allowsSendFace) {
NSString *emotionNormalImageName = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewEmotionNormalImageNameKey];
if (!emotionNormalImageName) {
emotionNormalImageName = @"face";
}
NSString *emotionHLImageName = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewEmotionHLImageNameKey];
if (!emotionHLImageName) {
emotionHLImageName = @"face_HL";
}
NSString *keyboardNormalImageName = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewKeyboardNormalImageNameKey];
if (!keyboardNormalImageName) {
keyboardNormalImageName = @"keyboard";
}
NSString *keyboardHLImageName = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewKeyboardHLImageNameKey];
if (!keyboardHLImageName) {
keyboardHLImageName = @"keyboard_HL";
}
button = [self createButtonWithImage:[UIImage imageNamed:emotionNormalImageName] HLImage:[UIImage imageNamed:emotionHLImageName]];
button.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
[button setBackgroundImage:[UIImage imageNamed:keyboardNormalImageName] forState:UIControlStateSelected];
[button addTarget:self action:@selector(messageStyleButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
button.tag = 1;
buttonFrame = button.frame;
if (self.allowsSendMultiMedia) {
buttonFrame.origin = CGPointMake(CGRectGetMinX(self.multiMediaSendButton.frame) - CGRectGetWidth(buttonFrame) - horizontalPadding, verticalPadding);
allButtonWidth += CGRectGetWidth(buttonFrame) + horizontalPadding * 1.5;
} else {
buttonFrame.origin = CGPointMake(CGRectGetWidth(self.bounds) - horizontalPadding - CGRectGetWidth(buttonFrame), verticalPadding);
allButtonWidth += CGRectGetWidth(buttonFrame) + horizontalPadding * 2.5;
}
button.frame = buttonFrame;
[self addSubview:button];
self.faceSendButton = button;
}
// 输入框的高度和宽度
CGFloat width = CGRectGetWidth(self.bounds) - (allButtonWidth ? allButtonWidth : (textViewLeftMargin * 2));
CGFloat height = [XHMessageInputView textViewLineHeight];
// 初始化输入框
XHMessageTextView *textView = [[XHMessageTextView alloc] initWithFrame:CGRectZero];
// 这个是仿微信的一个细节体验
textView.returnKeyType = UIReturnKeySend;
textView.enablesReturnKeyAutomatically = YES; // UITextView内部判断send按钮是否可以用
UIColor *placeHolderTextColor = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewPlaceHolderTextColorKey];
if (placeHolderTextColor) {
textView.placeHolderTextColor = placeHolderTextColor;
}
UIColor *textColor = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewTextColorKey];
if (textColor) {
textView.textColor = textColor;
}
NSString *placeHolder = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewPlaceHolderKey];
if (!placeHolder) {
placeHolder = NSLocalizedStringFromTable(@"SendAMessage", @"MessageDisplayKitString", nil);
}
textView.placeHolder = placeHolder;
textView.delegate = self;
[self addSubview:textView];
_inputTextView = textView;
// 配置不同iOS SDK版本的样式
switch (style) {
case XHMessageInputViewStyleFlat: {
UIColor *inputBackgroundColor = [XHConfigurationHelper appearance].messageInputViewStyle[kXHMessageInputViewBackgroundColorKey];
NSString *inputViewBackgroundImageName = nil;
if (!inputBackgroundColor) {
inputViewBackgroundImageName = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewBackgroundImageNameKey];
if (!inputViewBackgroundImageName) {
inputViewBackgroundImageName = @"input-bar-flat";
}
}
UIColor *borderColor = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewBorderColorKey];
if (!borderColor) {
borderColor = [UIColor colorWithWhite:0.8f alpha:1.0f];
}
CGFloat borderWidth = [[[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewBorderWidthKey] floatValue];
if (borderWidth == 0) {
borderWidth = 0.65f;
}
CGFloat cornerRadius = [[[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewCornerRadiusKey] floatValue];
if (cornerRadius == 0) {
cornerRadius = 6.0f;
}
_inputTextView.frame = CGRectMake(textViewLeftMargin, 4.5f, width, height);
_inputTextView.backgroundColor = [UIColor clearColor];
_inputTextView.layer.borderColor = borderColor.CGColor;
_inputTextView.layer.borderWidth = borderWidth;
_inputTextView.layer.cornerRadius = cornerRadius;
if (inputBackgroundColor) {
self.backgroundColor = inputBackgroundColor;
} else {
self.image = [[UIImage imageNamed:inputViewBackgroundImageName] resizableImageWithCapInsets:UIEdgeInsetsMake(2.0f, 0.0f, 0.0f, 0.0f)
resizingMode:UIImageResizingModeTile];
}
break;
}
default:
break;
}
// 如果是可以发送语音的,那就需要一个按钮录音的按钮,事件可以在外部添加
if (self.allowsSendVoice) {
NSString *voiceHolderImageName = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewVoiceHolderImageNameKey];
if (!voiceHolderImageName) {
voiceHolderImageName = @"VoiceBtn_Black";
}
NSString *voiceHolderHLImageName = [[XHConfigurationHelper appearance].messageInputViewStyle objectForKey:kXHMessageInputViewVoiceHolderHLImageNameKey];
if (!voiceHolderHLImageName) {
voiceHolderHLImageName = @"VoiceBtn_BlackHL";
}
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(9, 9, 9, 9);
button = [self createButtonWithImage:XH_STRETCH_IMAGE([UIImage imageNamed:voiceHolderImageName], edgeInsets) HLImage:XH_STRETCH_IMAGE([UIImage imageNamed:voiceHolderHLImageName], edgeInsets)];
[button setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal];
[button setTitle:NSLocalizedStringFromTable(@"HoldToTalk", @"MessageDisplayKitString", nil) forState:UIControlStateNormal];
[button setTitle:NSLocalizedStringFromTable(@"ReleaseToSend", @"MessageDisplayKitString", nil) forState:UIControlStateHighlighted];
buttonFrame = CGRectMake(Adapted(25), 0, SCREEN_WIDTH-Adapted(50), Adapted(45));
button.frame = buttonFrame;
//button.alpha = self.voiceChangeButton.selected;
[button addTarget:self action:@selector(holdDownButtonTouchDown) forControlEvents:UIControlEventTouchDown];
[button addTarget:self action:@selector(holdDownButtonTouchUpOutside) forControlEvents:UIControlEventTouchUpOutside];
[button addTarget:self action:@selector(holdDownButtonTouchUpInside) forControlEvents:UIControlEventTouchUpInside];
[button addTarget:self action:@selector(holdDownDragOutside) forControlEvents:UIControlEventTouchDragExit];
[button addTarget:self action:@selector(holdDownDragInside) forControlEvents:UIControlEventTouchDragEnter];
[self addSubview:button];
self.holdDownButton = button;
}
}
#pragma mark - Life cycle
- (void)setup {
// 配置自适应
self.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin);
self.opaque = YES;
// 由于继承UIImageView,所以需要这个属性设置
self.userInteractionEnabled = YES;
// 默认设置
_allowsSendVoice = YES;
_allowsSendFace = YES;
_allowsSendMultiMedia = YES;
_messageInputViewStyle = XHMessageInputViewStyleFlat;
}
- (void)awakeFromNib {
[self setup];
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self setup];
}
return self;
}
- (void)dealloc {
self.inputedText = nil;
_inputTextView.delegate = nil;
_inputTextView = nil;
_voiceChangeButton = nil;
_multiMediaSendButton = nil;
_faceSendButton = nil;
_holdDownButton = nil;
}
- (void)willMoveToSuperview:(UIView *)newSuperview {
// 当别的地方需要add的时候,就会调用这里
if (newSuperview) {
[self setupMessageInputViewBarWithStyle:self.messageInputViewStyle];
}
}
#pragma mark - Message input view
- (void)adjustTextViewHeightBy:(CGFloat)changeInHeight {
// 动态改变自身的高度和输入框的高度
CGRect prevFrame = self.inputTextView.frame;
NSUInteger numLines = MAX([self.inputTextView numberOfLinesOfText],
[self.inputTextView.text numberOfLines]);
self.inputTextView.frame = CGRectMake(prevFrame.origin.x,
prevFrame.origin.y,
prevFrame.size.width,
prevFrame.size.height + changeInHeight);
self.inputTextView.contentInset = UIEdgeInsetsMake((numLines >= 6 ? 4.0f : 0.0f),
0.0f,
(numLines >= 6 ? 4.0f : 0.0f),
0.0f);
// from iOS 7, the content size will be accurate only if the scrolling is enabled.
self.inputTextView.scrollEnabled = YES;
if (numLines >= 6) {
CGPoint bottomOffset = CGPointMake(0.0f, self.inputTextView.contentSize.height - self.inputTextView.bounds.size.height);
[self.inputTextView setContentOffset:bottomOffset animated:YES];
[self.inputTextView scrollRangeToVisible:NSMakeRange(self.inputTextView.text.length - 2, 1)];
}
}
+ (CGFloat)textViewLineHeight {
return 36.0f; // for fontSize 16.0f
}
+ (CGFloat)maxLines {
return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) ? 3.0f : 8.0f;
}
+ (CGFloat)maxHeight {
return ([XHMessageInputView maxLines] + 1.0f) * [XHMessageInputView textViewLineHeight];
}
#pragma mark - Text view delegate
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
if ([self.delegate respondsToSelector:@selector(inputTextViewWillBeginEditing:)]) {
[self.delegate inputTextViewWillBeginEditing:self.inputTextView];
}
self.faceSendButton.selected = NO;
self.voiceChangeButton.selected = NO;
return YES;
}
- (void)textViewDidBeginEditing:(UITextView *)textView {
[textView becomeFirstResponder];
if ([self.delegate respondsToSelector:@selector(inputTextViewDidBeginEditing:)]) {
[self.delegate inputTextViewDidBeginEditing:self.inputTextView];
}
}
- (void)textViewDidEndEditing:(UITextView *)textView {
[textView resignFirstResponder];
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([text isEqualToString:@"\n"]) {
if ([self.delegate respondsToSelector:@selector(didSendTextAction:)]) {
[self.delegate didSendTextAction:textView.text];
}
return NO;
}
return YES;
}
@end