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.
737 lines
28 KiB
737 lines
28 KiB
// |
|
// XHMessageInputView.m |
|
// MessageDisplayExample |
|
// |
|
// Created by HUAJIE-1 on 14-4-24. |
|
// Copyright (c) 2014年 曾宪华 开发团队(http://iyilunba.com ) 本人QQ:543413507 本人QQ群(142557668). 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; |
|
if (self.allowsSendText && self.allowsSendVoice) { |
|
[self addSubview:button]; |
|
} |
|
allButtonWidth += CGRectGetMaxX(buttonFrame) + horizontalPadding; |
|
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 * 1.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 * 0.5; |
|
} else { |
|
buttonFrame.origin = CGPointMake(CGRectGetWidth(self.bounds) - horizontalPadding - CGRectGetWidth(buttonFrame), verticalPadding); |
|
allButtonWidth += CGRectGetWidth(buttonFrame) + horizontalPadding * 1.5; |
|
} |
|
button.frame = buttonFrame; |
|
[self addSubview:button]; |
|
|
|
self.faceSendButton = button; |
|
} |
|
|
|
// 输入框的高度和宽度 update by lsz 2020-12-10 把按钮的长度去掉 |
|
CGFloat width = CGRectGetWidth(self.bounds) - (textViewLeftMargin * 2); //- (allButtonWidth ? allButtonWidth : (textViewLeftMargin * 2)); |
|
CGFloat height = [XHMessageInputView textViewLineHeight]; |
|
|
|
if (self.allowsSendText) { |
|
// 初始化输入框 |
|
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) { |
|
if (self.allowsSendText) { |
|
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]; |
|
if([UIScreen mainScreen].bounds.size.height >= 812){ //是iphoneX |
|
buttonFrame = CGRectMake(textViewLeftMargin-5, 0, width+10, self.frame.size.height-35); |
|
}else{ |
|
buttonFrame = CGRectMake(textViewLeftMargin-5, 0, width+10, self.frame.size.height); |
|
} |
|
button.frame = buttonFrame; |
|
button.alpha = self.voiceChangeButton.selected; |
|
}else{ |
|
if([UIScreen mainScreen].bounds.size.height >= 812){ //是iphoneX |
|
button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame)-35)]; |
|
}else{ |
|
button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame))]; |
|
} |
|
// button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame))]; |
|
[button setTitleColor:[UIColor whiteColor] |
|
forState:UIControlStateNormal]; |
|
button.backgroundColor = [UIColor colorWithRed:255/255.0 |
|
green:126/255.0 |
|
blue:0 alpha:1]; |
|
} |
|
|
|
[button setTitle:NSLocalizedStringFromTable(@"HoldToTalk", @"MessageDisplayKitString", nil) forState:UIControlStateNormal]; |
|
[button setTitle:NSLocalizedStringFromTable(@"ReleaseToSend", @"MessageDisplayKitString", nil) forState:UIControlStateHighlighted]; |
|
|
|
[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; |
|
|
|
// 默认设置 |
|
_allowsSendText = 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; |
|
// 最终的结果 |
|
NSString *str = textView.text; |
|
NSString *resultStr = [str stringByReplacingCharactersInRange:range withString:text]; |
|
|
|
|
|
|
|
if ([text isEqualToString:@"\n"]) { |
|
if ([self.delegate respondsToSelector:@selector(didSendTextAction:)]) { |
|
[self.delegate didSendTextAction:textView.text]; |
|
} |
|
return NO; |
|
}else if ([resultStr getStringLenthOfBytes] > 64) //中文和字符一起检测 中文是两个字符 |
|
{ |
|
[SVProgressHUD dismiss]; |
|
[SVProgressHUD showErrorWithStatus:@"文字不允许超过32个汉字或64个英文字符"]; |
|
return NO; |
|
} |
|
|
|
if ([textView isFirstResponder]) { |
|
|
|
if ([[[textView textInputMode] primaryLanguage] isEqualToString:@"emoji"] || ![[textView textInputMode] primaryLanguage]) { |
|
return NO; |
|
} |
|
|
|
//判断键盘是不是九宫格键盘 |
|
if ([self isNineKeyBoard:str] ){ |
|
return YES; |
|
}else{ |
|
if ([self hasEmoji:str] || [self stringContainsEmoji:str]){ |
|
return NO; |
|
} |
|
} |
|
} |
|
return YES; |
|
} |
|
/** |
|
判断是不是九宫格 |
|
@param string 输入的字符 |
|
@return YES(是九宫格拼音键盘) |
|
*/ |
|
-(BOOL)isNineKeyBoard:(NSString *)string |
|
{ |
|
NSString *other = @"➋➌➍➎➏➐➑➒"; |
|
int len = (int)string.length; |
|
for(int i=0;i<len;i++) |
|
{ |
|
if(!([other rangeOfString:string].location != NSNotFound)) |
|
return NO; |
|
} |
|
return YES; |
|
} |
|
/** |
|
* 判断字符串中是否存在emoji |
|
* @param string 字符串 |
|
* @return YES(含有表情) |
|
*/ |
|
- (BOOL)hasEmoji:(NSString*)string; |
|
{ |
|
NSString *pattern = @"[^\\u0020-\\u007E\\u00A0-\\u00BE\\u2E80-\\uA4CF\\uF900-\\uFAFF\\uFE30-\\uFE4F\\uFF00-\\uFFEF\\u0080-\\u009F\\u2000-\\u201f\r\n]"; |
|
NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", pattern]; |
|
BOOL isMatch = [pred evaluateWithObject:string]; |
|
return isMatch; |
|
} |
|
/** |
|
* 判断字符串中是否存在emoji |
|
* @param string 字符串 |
|
* @return YES(含有表情) |
|
*/ |
|
- (BOOL)stringContainsEmoji:(NSString *)string { |
|
|
|
__block BOOL returnValue = NO; |
|
|
|
[string enumerateSubstringsInRange:NSMakeRange(0, [string length]) options:NSStringEnumerationByComposedCharacterSequences usingBlock: |
|
^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { |
|
|
|
const unichar hs = [substring characterAtIndex:0]; |
|
// surrogate pair |
|
if (0xd800 <= hs && hs <= 0xdbff) { |
|
if (substring.length > 1) { |
|
const unichar ls = [substring characterAtIndex:1]; |
|
const int uc = ((hs - 0xd800) * 0x400) + (ls - 0xdc00) + 0x10000; |
|
if (0x1d000 <= uc && uc <= 0x1f77f) { |
|
returnValue = YES; |
|
} |
|
} |
|
} else if (substring.length > 1) { |
|
const unichar ls = [substring characterAtIndex:1]; |
|
if (ls == 0x20e3) { |
|
returnValue = YES; |
|
} |
|
|
|
} else { |
|
// non surrogate |
|
if (0x2100 <= hs && hs <= 0x27ff) { |
|
returnValue = YES; |
|
} else if (0x2B05 <= hs && hs <= 0x2b07) { |
|
returnValue = YES; |
|
} else if (0x2934 <= hs && hs <= 0x2935) { |
|
returnValue = YES; |
|
} else if (0x3297 <= hs && hs <= 0x3299) { |
|
returnValue = YES; |
|
} else if (hs == 0xa9 || hs == 0xae || hs == 0x303d || hs == 0x3030 || hs == 0x2b55 || hs == 0x2b1c || hs == 0x2b1b || hs == 0x2b50) { |
|
returnValue = YES; |
|
} |
|
} |
|
}]; |
|
|
|
return returnValue; |
|
} |
|
|
|
@end
|
|
|