279 lines
8.5 KiB
Objective-C
279 lines
8.5 KiB
Objective-C
//
|
|
// 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, strong) UIImageView *recordingIconView;
|
|
@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.recordingIconView = [[UIImageView alloc] init];
|
|
self.recordingIconView.contentMode = UIViewContentModeScaleAspectFit;
|
|
self.recordingIconView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
self.recordingIconView.hidden = YES;
|
|
[self.backgroundView addSubview:self.recordingIconView];
|
|
|
|
// 标题标签
|
|
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],
|
|
|
|
[self.recordingIconView.centerXAnchor
|
|
constraintEqualToAnchor:self.backgroundView.centerXAnchor],
|
|
[self.recordingIconView.centerYAnchor
|
|
constraintEqualToAnchor:self.backgroundView.centerYAnchor],
|
|
[self.recordingIconView.widthAnchor constraintEqualToConstant:36],
|
|
[self.recordingIconView.heightAnchor constraintEqualToConstant:36],
|
|
]];
|
|
|
|
// 添加手势
|
|
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;
|
|
}
|
|
|
|
- (void)setNormalIconImage:(UIImage *)normalIconImage {
|
|
_normalIconImage = normalIconImage;
|
|
if (normalIconImage) {
|
|
self.micIconView.image = normalIconImage;
|
|
}
|
|
}
|
|
|
|
- (void)setRecordingIconImage:(UIImage *)recordingIconImage {
|
|
_recordingIconImage = recordingIconImage;
|
|
self.recordingIconView.image = recordingIconImage;
|
|
}
|
|
|
|
#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.hidden = NO;
|
|
self.titleLabel.hidden = NO;
|
|
self.recordingIconView.hidden = YES;
|
|
self.waveformView.alpha = 0;
|
|
[self.waveformView stopAnimation];
|
|
break;
|
|
|
|
case KBAiRecordButtonStateRecording:
|
|
self.titleLabel.text = self.recordingTitle;
|
|
self.backgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.6];
|
|
self.micIconView.hidden = YES;
|
|
self.titleLabel.hidden = YES;
|
|
self.recordingIconView.hidden = NO;
|
|
self.waveformView.alpha = 0;
|
|
[self.waveformView stopAnimation];
|
|
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
|