// // KBVoiceInputBar.m // keyBoard // // Created by Kiro on 2026/1/26. // #import "KBVoiceInputBar.h" #import "KBAiRecordButton.h" #import @interface KBVoiceInputBar () /// 毛玻璃背景容器 @property (nonatomic, strong) UIView *backgroundView; /// 毛玻璃效果 @property (nonatomic, strong) UIVisualEffectView *blurEffectView; /// 状态标签 @property (nonatomic, strong) UILabel *statusLabel; /// 录音按钮 @property (nonatomic, strong) KBAiRecordButton *recordButton; /// 是否正在录音 @property (nonatomic, assign) BOOL isRecording; @end @implementation KBVoiceInputBar #pragma mark - Lifecycle - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self setupUI]; } return self; } - (instancetype)initWithCoder:(NSCoder *)coder { if (self = [super initWithCoder:coder]) { [self setupUI]; } return self; } #pragma mark - 1:控件初始化 - (void)setupUI { 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 addSubview:self.statusLabel]; [self.statusLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self).offset(16); make.left.equalTo(self).offset(20); make.right.equalTo(self).offset(-20); make.height.mas_equalTo(20); }]; // 录音按钮 [self addSubview:self.recordButton]; [self.recordButton 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]; // 更新 mask 的 frame if (self.blurEffectView.layer.mask) { self.blurEffectView.layer.mask.frame = self.blurEffectView.bounds; } } #pragma mark - Setter - (void)setStatusText:(NSString *)statusText { _statusText = [statusText copy]; self.statusLabel.text = statusText; } - (void)setEnabled:(BOOL)enabled { _enabled = enabled; self.recordButton.userInteractionEnabled = enabled; self.recordButton.alpha = enabled ? 1.0 : 0.5; } - (void)setRecording:(BOOL)recording { _isRecording = recording; self.recordButton.state = recording ? KBAiRecordButtonStateRecording : KBAiRecordButtonStateNormal; } #pragma mark - Public Methods - (void)updateVolumeRMS:(float)rms { [self.recordButton updateVolumeRMS:rms]; } #pragma mark - KBAiRecordButtonDelegate - (void)recordButtonDidBeginPress:(KBAiRecordButton *)button { if (!self.enabled) { return; } self.isRecording = YES; if ([self.delegate respondsToSelector:@selector(voiceInputBarDidBeginRecording:)]) { [self.delegate voiceInputBarDidBeginRecording:self]; } } - (void)recordButtonDidEndPress:(KBAiRecordButton *)button { self.isRecording = NO; if ([self.delegate respondsToSelector:@selector(voiceInputBarDidEndRecording:)]) { [self.delegate voiceInputBarDidEndRecording:self]; } } - (void)recordButtonDidCancelPress:(KBAiRecordButton *)button { self.isRecording = NO; if ([self.delegate respondsToSelector:@selector(voiceInputBarDidCancelRecording:)]) { [self.delegate voiceInputBarDidCancelRecording:self]; } } #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]; _statusLabel.text = @"按住按钮开始对话"; _statusLabel.font = [UIFont systemFontOfSize:14]; _statusLabel.textColor = [UIColor secondaryLabelColor]; _statusLabel.textAlignment = NSTextAlignmentCenter; } return _statusLabel; } - (KBAiRecordButton *)recordButton { if (!_recordButton) { _recordButton = [[KBAiRecordButton alloc] init]; _recordButton.delegate = self; _recordButton.normalTitle = @"按住说话"; _recordButton.recordingTitle = @"松开结束"; } return _recordButton; } @end