// // KBAiRecordButton.m // keyBoard // // Created by Mac on 2026/1/15. // #import "KBAiRecordButton.h" #import "KBAiWaveformView.h" @interface KBAiRecordButton () @property(nonatomic, strong) UIView *backgroundView; @property(nonatomic, strong) UILabel *titleLabel; @property(nonatomic, strong) KBAiWaveformView *waveformView; @property(nonatomic, strong) UIImageView *micIconView; @property(nonatomic, assign) BOOL isPressing; @end @implementation KBAiRecordButton - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setup]; } return self; } - (instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { [self setup]; } return self; } - (void)setup { _state = KBAiRecordButtonStateNormal; _normalTitle = @"按住说话"; _recordingTitle = @"松开结束"; _tintColor = [UIColor systemBlueColor]; // 背景视图 self.backgroundView = [[UIView alloc] init]; self.backgroundView.backgroundColor = [UIColor systemGray6Color]; self.backgroundView.layer.cornerRadius = 25; self.backgroundView.layer.masksToBounds = YES; self.backgroundView.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:self.backgroundView]; // 麦克风图标 self.micIconView = [[UIImageView alloc] init]; self.micIconView.image = [UIImage systemImageNamed:@"mic.fill"]; self.micIconView.tintColor = self.tintColor; self.micIconView.contentMode = UIViewContentModeScaleAspectFit; self.micIconView.translatesAutoresizingMaskIntoConstraints = NO; [self.backgroundView addSubview:self.micIconView]; // 标题标签 self.titleLabel = [[UILabel alloc] init]; self.titleLabel.text = self.normalTitle; self.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium]; self.titleLabel.textColor = [UIColor labelColor]; self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO; [self.backgroundView addSubview:self.titleLabel]; // 波形视图(录音时显示) self.waveformView = [[KBAiWaveformView alloc] init]; self.waveformView.waveColor = self.tintColor; self.waveformView.alpha = 0; self.waveformView.translatesAutoresizingMaskIntoConstraints = NO; [self.backgroundView addSubview:self.waveformView]; // 布局约束 [NSLayoutConstraint activateConstraints:@[ [self.backgroundView.topAnchor constraintEqualToAnchor:self.topAnchor], [self.backgroundView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor], [self.backgroundView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], [self.backgroundView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor], [self.micIconView.leadingAnchor constraintEqualToAnchor:self.backgroundView.leadingAnchor constant:20], [self.micIconView.centerYAnchor constraintEqualToAnchor:self.backgroundView.centerYAnchor], [self.micIconView.widthAnchor constraintEqualToConstant:24], [self.micIconView.heightAnchor constraintEqualToConstant:24], [self.titleLabel.leadingAnchor constraintEqualToAnchor:self.micIconView.trailingAnchor constant:12], [self.titleLabel.centerYAnchor constraintEqualToAnchor:self.backgroundView.centerYAnchor], [self.waveformView.trailingAnchor constraintEqualToAnchor:self.backgroundView.trailingAnchor constant:-20], [self.waveformView.centerYAnchor constraintEqualToAnchor:self.backgroundView.centerYAnchor], [self.waveformView.widthAnchor constraintEqualToConstant:60], [self.waveformView.heightAnchor constraintEqualToConstant:30], ]]; // 添加手势 UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; longPress.minimumPressDuration = 0.05; [self addGestureRecognizer:longPress]; } #pragma mark - Setters - (void)setState:(KBAiRecordButtonState)state { if (_state == state) return; _state = state; [self updateAppearance]; } - (void)setTintColor:(UIColor *)tintColor { _tintColor = tintColor; self.micIconView.tintColor = tintColor; self.waveformView.waveColor = tintColor; } #pragma mark - Public Methods - (void)updateVolumeRMS:(float)rms { [self.waveformView updateWithRMS:rms]; } #pragma mark - Private Methods - (void)updateAppearance { switch (self.state) { case KBAiRecordButtonStateNormal: self.titleLabel.text = self.normalTitle; self.backgroundView.backgroundColor = [UIColor systemGray6Color]; self.micIconView.alpha = 1; self.waveformView.alpha = 0; [self.waveformView stopAnimation]; break; case KBAiRecordButtonStateRecording: self.titleLabel.text = self.recordingTitle; self.backgroundView.backgroundColor = [self.tintColor colorWithAlphaComponent:0.15]; self.micIconView.alpha = 1; self.waveformView.alpha = 1; [self.waveformView startIdleAnimation]; break; case KBAiRecordButtonStateDisabled: self.titleLabel.text = self.normalTitle; self.backgroundView.backgroundColor = [UIColor systemGray5Color]; self.alpha = 0.5; break; } } - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture { if (self.state == KBAiRecordButtonStateDisabled) { return; } CGPoint location = [gesture locationInView:self]; BOOL isInside = CGRectContainsPoint(self.bounds, location); switch (gesture.state) { case UIGestureRecognizerStateBegan: self.isPressing = YES; [self animateScale:0.95]; self.state = KBAiRecordButtonStateRecording; if ([self.delegate respondsToSelector:@selector(recordButtonDidBeginPress:)]) { [self.delegate recordButtonDidBeginPress:self]; } break; case UIGestureRecognizerStateChanged: if (!isInside && self.isPressing) { // 手指滑出 [self animateScale:1.0]; } else if (isInside && self.isPressing) { // 手指滑回 [self animateScale:0.95]; } break; case UIGestureRecognizerStateEnded: if (self.isPressing) { self.isPressing = NO; [self animateScale:1.0]; self.state = KBAiRecordButtonStateNormal; [self.waveformView reset]; if (isInside) { if ([self.delegate respondsToSelector:@selector(recordButtonDidEndPress:)]) { [self.delegate recordButtonDidEndPress:self]; } } else { if ([self.delegate respondsToSelector:@selector(recordButtonDidCancelPress:)]) { [self.delegate recordButtonDidCancelPress:self]; } } } break; case UIGestureRecognizerStateCancelled: case UIGestureRecognizerStateFailed: if (self.isPressing) { self.isPressing = NO; [self animateScale:1.0]; self.state = KBAiRecordButtonStateNormal; [self.waveformView reset]; if ([self.delegate respondsToSelector:@selector(recordButtonDidCancelPress:)]) { [self.delegate recordButtonDidCancelPress:self]; } } break; default: break; } } - (void)animateScale:(CGFloat)scale { [UIView animateWithDuration:0.15 animations:^{ self.backgroundView.transform = CGAffineTransformMakeScale(scale, scale); }]; } @end