This commit is contained in:
2026-01-27 16:28:17 +08:00
parent ce889e1ed0
commit 2b749cd2b0
26 changed files with 1092 additions and 128 deletions

View File

@@ -11,18 +11,37 @@
@interface KBVoiceInputBar () <KBAiRecordButtonDelegate>
///
@property (nonatomic, strong) UIView *backgroundView;
///
@property (nonatomic, strong) UIVisualEffectView *blurEffectView;
///
@property (nonatomic, strong) UILabel *statusLabel;
///
@property (nonatomic, strong) KBAiRecordButton *recordButton;
///
@property (nonatomic, strong) UIView *inputContainer;
///
@property (nonatomic, strong) UIView *textInputView;
@property (nonatomic, strong) UIButton *textCenterButton;
///
@property (nonatomic, strong) UIView *voiceInputView;
@property (nonatomic, strong) UILabel *voiceCenterLabel;
/// /
@property (nonatomic, strong) UIButton *toggleIconButton;
///
@property (nonatomic, strong) UIView *recordingView;
@property (nonatomic, strong) UIImageView *recordingCenterIconView;
///
@property (nonatomic, strong) UIView *cancelView;
@property (nonatomic, strong) UILabel *cancelLabel;
///
@property (nonatomic, strong) UITextField *hiddenTextField;
///
@property (nonatomic, assign) BOOL isRecording;
@@ -52,30 +71,8 @@
self.backgroundColor = [UIColor clearColor];
self.enabled = YES;
self.isRecording = NO;
//
[self addSubview:self.backgroundView];
[self.backgroundView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self);
}];
//
[self.backgroundView addSubview:self.blurEffectView];
[self.blurEffectView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.backgroundView);
}];
// blurEffectView mask
CAGradientLayer *maskLayer = [CAGradientLayer layer];
maskLayer.startPoint = CGPointMake(0.5, 1); //
maskLayer.endPoint = CGPointMake(0.5, 0); //
maskLayer.colors = @[
(__bridge id)[UIColor whiteColor].CGColor, //
(__bridge id)[UIColor whiteColor].CGColor, //
(__bridge id)[UIColor clearColor].CGColor //
];
maskLayer.locations = @[@(0.0), @(0.5), @(1.0)];
self.blurEffectView.layer.mask = maskLayer;
self.inputMode = KBVoiceInputBarModeVoice;
self.inputState = KBVoiceInputBarStateText;
//
[self addSubview:self.statusLabel];
@@ -86,24 +83,92 @@
make.height.mas_equalTo(20);
}];
//
[self addSubview:self.recordButton];
[self.recordButton mas_makeConstraints:^(MASConstraintMaker *make) {
//
[self addSubview:self.inputContainer];
[self.inputContainer mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.statusLabel.mas_bottom).offset(12);
make.left.equalTo(self).offset(20);
make.right.equalTo(self).offset(-20);
make.height.mas_equalTo(50);
make.bottom.lessThanOrEqualTo(self).offset(-16);
}];
}
- (void)layoutSubviews {
[super layoutSubviews];
UILongPressGestureRecognizer *longPress =
[[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(handleVoiceLongPress:)];
longPress.minimumPressDuration = 0.05;
longPress.cancelsTouchesInView = NO;
[self.inputContainer addGestureRecognizer:longPress];
// mask frame
if (self.blurEffectView.layer.mask) {
self.blurEffectView.layer.mask.frame = self.blurEffectView.bounds;
}
//
[self.inputContainer addSubview:self.textInputView];
[self.textInputView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.inputContainer);
}];
[self.inputContainer addSubview:self.toggleIconButton];
[self.toggleIconButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.textInputView).offset(16);
make.centerY.equalTo(self.textInputView);
make.width.height.mas_equalTo(24);
}];
[self.textInputView addSubview:self.textCenterButton];
[self.textCenterButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.toggleIconButton.mas_right).offset(12);
make.right.equalTo(self.textInputView).offset(-16);
make.centerY.equalTo(self.textInputView);
make.height.mas_equalTo(30);
}];
//
[self.inputContainer addSubview:self.voiceInputView];
[self.voiceInputView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.inputContainer);
}];
[self.voiceInputView addSubview:self.voiceCenterLabel];
[self.voiceCenterLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.toggleIconButton.mas_right).offset(12);
make.right.equalTo(self.voiceInputView).offset(-16);
make.centerY.equalTo(self.voiceInputView);
}];
//
[self.inputContainer addSubview:self.recordingView];
[self.recordingView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.inputContainer);
}];
[self.recordingView addSubview:self.recordingCenterIconView];
[self.recordingCenterIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.recordingView);
make.width.height.mas_equalTo(36);
}];
//
[self.inputContainer addSubview:self.cancelView];
[self.cancelView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.inputContainer);
}];
[self.cancelView addSubview:self.cancelLabel];
[self.cancelLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.cancelView);
}];
//
[self addSubview:self.hiddenTextField];
[self.hiddenTextField mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.height.mas_equalTo(1);
make.left.top.equalTo(self);
}];
//
self.statusLabel.hidden = YES;
//
self.inputState = KBVoiceInputBarStateText;
}
#pragma mark - Setter
@@ -111,17 +176,55 @@
- (void)setStatusText:(NSString *)statusText {
_statusText = [statusText copy];
self.statusLabel.text = statusText;
[self updateCenterTextIfNeeded];
}
- (void)setEnabled:(BOOL)enabled {
_enabled = enabled;
self.recordButton.userInteractionEnabled = enabled;
self.recordButton.alpha = enabled ? 1.0 : 0.5;
self.inputContainer.userInteractionEnabled = enabled;
self.inputContainer.alpha = enabled ? 1.0 : 0.5;
}
- (void)setRecording:(BOOL)recording {
_isRecording = recording;
self.recordButton.state = recording ? KBAiRecordButtonStateRecording : KBAiRecordButtonStateNormal;
if (recording) {
self.inputState = KBVoiceInputBarStateRecording;
} else if (self.inputState == KBVoiceInputBarStateRecording) {
self.inputState = KBVoiceInputBarStateVoice;
}
}
- (void)setInputMode:(KBVoiceInputBarMode)inputMode {
_inputMode = inputMode;
if (inputMode == KBVoiceInputBarModeText) {
[self.toggleIconButton setImage:[UIImage imageNamed:@"ai_maikefeng_icon"]
forState:UIControlStateNormal];
} else {
[self.toggleIconButton setImage:[UIImage imageNamed:@"ai_jianpan_icon"]
forState:UIControlStateNormal];
}
}
- (void)setInputState:(KBVoiceInputBarState)inputState {
_inputState = inputState;
self.textInputView.hidden = (inputState != KBVoiceInputBarStateText);
self.voiceInputView.hidden = (inputState != KBVoiceInputBarStateVoice);
self.recordingView.hidden = (inputState != KBVoiceInputBarStateRecording);
self.cancelView.hidden = (inputState != KBVoiceInputBarStateCancel);
self.toggleIconButton.hidden = (inputState == KBVoiceInputBarStateRecording ||
inputState == KBVoiceInputBarStateCancel);
if (inputState == KBVoiceInputBarStateText) {
self.inputMode = KBVoiceInputBarModeText;
} else if (inputState == KBVoiceInputBarStateVoice) {
self.inputMode = KBVoiceInputBarModeVoice;
}
if (!self.toggleIconButton.hidden) {
[self.inputContainer bringSubviewToFront:self.toggleIconButton];
}
[self updateCenterTextIfNeeded];
}
#pragma mark - Public Methods
@@ -162,22 +265,6 @@
#pragma mark - Lazy Load
- (UIView *)backgroundView {
if (!_backgroundView) {
_backgroundView = [[UIView alloc] init];
_backgroundView.clipsToBounds = YES;
}
return _backgroundView;
}
- (UIVisualEffectView *)blurEffectView {
if (!_blurEffectView) {
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
_blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
}
return _blurEffectView;
}
- (UILabel *)statusLabel {
if (!_statusLabel) {
_statusLabel = [[UILabel alloc] init];
@@ -195,8 +282,198 @@
_recordButton.delegate = self;
_recordButton.normalTitle = @"按住说话";
_recordButton.recordingTitle = @"松开结束";
_recordButton.normalIconImage = [UIImage imageNamed:@"ai_jianpan_icon"];
_recordButton.recordingIconImage = [UIImage imageNamed:@"ai_luyining_icon"];
_recordButton.hidden = YES;
}
return _recordButton;
}
- (UIView *)inputContainer {
if (!_inputContainer) {
_inputContainer = [[UIView alloc] init];
_inputContainer.clipsToBounds = YES;
_inputContainer.layer.cornerRadius = 25;
_inputContainer.backgroundColor = [UIColor clearColor];
}
return _inputContainer;
}
- (UIView *)textInputView {
if (!_textInputView) {
_textInputView = [[UIView alloc] init];
_textInputView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7];
}
return _textInputView;
}
- (UIButton *)textCenterButton {
if (!_textCenterButton) {
_textCenterButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_textCenterButton setTitle:@"发送一个消息给她" forState:UIControlStateNormal];
_textCenterButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
[_textCenterButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_textCenterButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
[_textCenterButton addTarget:self
action:@selector(handleTextCenterTap)
forControlEvents:UIControlEventTouchUpInside];
}
return _textCenterButton;
}
- (UIView *)voiceInputView {
if (!_voiceInputView) {
_voiceInputView = [[UIView alloc] init];
_voiceInputView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7];
}
return _voiceInputView;
}
- (UILabel *)voiceCenterLabel {
if (!_voiceCenterLabel) {
_voiceCenterLabel = [[UILabel alloc] init];
_voiceCenterLabel.text = @"按住说话";
_voiceCenterLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
_voiceCenterLabel.textColor = [UIColor whiteColor];
_voiceCenterLabel.textAlignment = NSTextAlignmentCenter;
}
return _voiceCenterLabel;
}
- (UIButton *)toggleIconButton {
if (!_toggleIconButton) {
_toggleIconButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_toggleIconButton setImage:[UIImage imageNamed:@"ai_maikefeng_icon"]
forState:UIControlStateNormal];
[_toggleIconButton addTarget:self
action:@selector(handleToggleIconTap)
forControlEvents:UIControlEventTouchUpInside];
_toggleIconButton.exclusiveTouch = YES;
}
return _toggleIconButton;
}
- (UIView *)recordingView {
if (!_recordingView) {
_recordingView = [[UIView alloc] init];
_recordingView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.6];
}
return _recordingView;
}
- (UIImageView *)recordingCenterIconView {
if (!_recordingCenterIconView) {
_recordingCenterIconView = [[UIImageView alloc] init];
_recordingCenterIconView.image = [UIImage imageNamed:@"ai_luyining_icon"];
_recordingCenterIconView.contentMode = UIViewContentModeScaleAspectFit;
}
return _recordingCenterIconView;
}
- (UIView *)cancelView {
if (!_cancelView) {
_cancelView = [[UIView alloc] init];
_cancelView.backgroundColor = [UIColor colorWithRed:0.75 green:0.3 blue:0.3 alpha:1.0];
}
return _cancelView;
}
- (UILabel *)cancelLabel {
if (!_cancelLabel) {
_cancelLabel = [[UILabel alloc] init];
_cancelLabel.text = @"Release To Cancel";
_cancelLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
_cancelLabel.textColor = [UIColor whiteColor];
}
return _cancelLabel;
}
- (UITextField *)hiddenTextField {
if (!_hiddenTextField) {
_hiddenTextField = [[UITextField alloc] init];
_hiddenTextField.hidden = YES;
}
return _hiddenTextField;
}
#pragma mark - Actions
- (void)handleToggleIconTap {
if (self.inputState == KBVoiceInputBarStateText) {
self.inputState = KBVoiceInputBarStateVoice;
[self endEditing:YES];
} else {
self.inputState = KBVoiceInputBarStateText;
}
}
- (void)handleTextCenterTap {
self.inputState = KBVoiceInputBarStateText;
[self.hiddenTextField becomeFirstResponder];
}
- (void)handleVoiceLongPress:(UILongPressGestureRecognizer *)gesture {
if (self.inputState != KBVoiceInputBarStateVoice &&
self.inputState != KBVoiceInputBarStateRecording &&
self.inputState != KBVoiceInputBarStateCancel) {
return;
}
CGPoint location = [gesture locationInView:self.inputContainer];
BOOL isInside = CGRectContainsPoint(self.inputContainer.bounds, location);
CGPoint iconPoint = [gesture locationInView:self.toggleIconButton];
BOOL isOnToggleIcon = CGRectContainsPoint(self.toggleIconButton.bounds, iconPoint);
if (isOnToggleIcon) {
return;
}
switch (gesture.state) {
case UIGestureRecognizerStateBegan: {
if (self.inputState != KBVoiceInputBarStateVoice) {
return;
}
self.inputState = KBVoiceInputBarStateRecording;
if ([self.delegate respondsToSelector:@selector(voiceInputBarDidBeginRecording:)]) {
[self.delegate voiceInputBarDidBeginRecording:self];
}
} break;
case UIGestureRecognizerStateChanged: {
if (isInside) {
self.inputState = KBVoiceInputBarStateRecording;
} else {
self.inputState = KBVoiceInputBarStateCancel;
}
} break;
case UIGestureRecognizerStateEnded: {
if (isInside) {
if ([self.delegate respondsToSelector:@selector(voiceInputBarDidEndRecording:)]) {
[self.delegate voiceInputBarDidEndRecording:self];
}
} else {
if ([self.delegate respondsToSelector:@selector(voiceInputBarDidCancelRecording:)]) {
[self.delegate voiceInputBarDidCancelRecording:self];
}
}
self.inputState = KBVoiceInputBarStateVoice;
} break;
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed: {
if ([self.delegate respondsToSelector:@selector(voiceInputBarDidCancelRecording:)]) {
[self.delegate voiceInputBarDidCancelRecording:self];
}
self.inputState = KBVoiceInputBarStateVoice;
} break;
default:
break;
}
}
- (void)updateCenterTextIfNeeded {
if (self.inputState == KBVoiceInputBarStateText) {
[self.textCenterButton setTitle:@"发送一个消息给她" forState:UIControlStateNormal];
} else if (self.inputState == KBVoiceInputBarStateVoice) {
self.voiceCenterLabel.text = @"按住说话";
}
}
@end