Files
featherVoice/TUIKit/TUIChat/CommonUI/Camera/TUICameraView.m
2025-08-08 10:49:36 +08:00

435 lines
16 KiB
Objective-C

// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
#import "TUICameraView.h"
#import <TIMCommon/TIMCommonModel.h>
#import <TIMCommon/TIMDefine.h>
#import "TUICaptureTimer.h"
static CGFloat gPhotoBtnZoomInRatio = 1.125;
static CGFloat gProgressLayerLineWidth = 5.0;
@interface TUICameraView ()
@property(nonatomic) UIView *contentView;
@property(nonatomic) UIButton *switchCameraButton;
@property(nonatomic) UIButton *closeButton;
@property(nonatomic, strong) UIButton *pictureLibButton;
@property(nonatomic) UIView *focusView;
@property(nonatomic) UISlider *slider;
@property(nonatomic) UIView *photoBtn;
@property(nonatomic) UIView *photoStateView;
@property(nonatomic) UILongPressGestureRecognizer *longPress;
@property(nonatomic) CGRect lastRect;
@property(nonatomic) CAShapeLayer *progressLayer;
@property(nonatomic) TUICaptureTimer *timer;
@property(nonatomic) BOOL isVideoRecording;
@end
@implementation TUICameraView
@synthesize previewView = _previewView;
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.type = TUICameraMediaTypePhoto;
self.aspectRatio = TUICameraViewAspectRatio16x9;
self.backgroundColor = [UIColor blackColor];
self.maxVideoCaptureTimeLimit = 15.0;
}
return self;
}
- (void)setupUI {
[self addSubview:self.contentView];
[self.contentView addSubview:self.previewView];
[self.contentView addSubview:self.switchCameraButton];
[self.contentView addSubview:self.photoBtn];
[self.contentView addSubview:self.closeButton];
[self.contentView addSubview:self.pictureLibButton];
[self.previewView addSubview:self.focusView];
[self.previewView addSubview:self.slider];
self.timer = ({
TUICaptureTimer *timer = [TUICaptureTimer new];
timer.maxCaptureTime = self.maxVideoCaptureTimeLimit;
__weak __typeof(self) weakSelf = self;
timer.progressBlock = ^(CGFloat ratio, CGFloat recordTime) {
weakSelf.progress = ratio;
};
timer.progressFinishBlock = ^(CGFloat ratio, CGFloat recordTime) {
weakSelf.progress = 1;
self.longPress.enabled = NO;
[weakSelf endVideoRecordWithCaptureDuration:recordTime];
self.longPress.enabled = YES;
};
timer.progressCancelBlock = ^{
weakSelf.progress = 0;
};
timer;
});
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)];
[self.previewView addGestureRecognizer:tap];
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchAction:)];
[self.previewView addGestureRecognizer:pinch];
}
- (void)layoutSubviews {
[super layoutSubviews];
if (!CGRectEqualToRect(self.lastRect, self.bounds)) {
[self setupUI];
self.lastRect = self.bounds;
self.contentView.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
CGFloat previewViewWidth = self.contentView.bounds.size.width;
CGFloat previewViewHeight;
switch (self.aspectRatio) {
case TUICameraViewAspectRatio1x1:
previewViewHeight = previewViewWidth;
break;
case TUICameraViewAspectRatio16x9:
previewViewHeight = previewViewWidth * (16.0 / 9.0);
break;
case TUICameraViewAspectRatio5x4:
previewViewHeight = previewViewWidth * (5.0 / 4.0);
break;
default:
break;
}
CGFloat previewViewY = (self.contentView.bounds.size.height - previewViewHeight) / 2.0;
self.previewView.frame = CGRectMake(0, previewViewY, self.contentView.bounds.size.width, previewViewHeight);
CGFloat switchCameraButtonWidth = 44.0;
self.switchCameraButton.frame =
CGRectMake(self.contentView.bounds.size.width - switchCameraButtonWidth - 16.0, 30.0, switchCameraButtonWidth, switchCameraButtonWidth);
if (isRTL()) {
[self.switchCameraButton resetFrameToFitRTL];
}
CGFloat photoBtnWidth = 100.0;
self.photoBtn.frame = CGRectMake((self.contentView.bounds.size.width - photoBtnWidth) / 2.0, self.contentView.bounds.size.height - photoBtnWidth - 30,
photoBtnWidth, photoBtnWidth);
self.photoBtn.layer.cornerRadius = photoBtnWidth / 2.0;
CGFloat distanceToPhotoBtn = 10.0;
CGFloat photoStateViewWidth = photoBtnWidth - 2 * distanceToPhotoBtn;
self.photoStateView.frame = CGRectMake(distanceToPhotoBtn, distanceToPhotoBtn, photoStateViewWidth, photoStateViewWidth);
self.photoStateView.layer.cornerRadius = photoStateViewWidth / 2.0;
if (self.type == TUICameraMediaTypeVideo) {
self.progressLayer.frame = CGRectInset(self.photoBtn.bounds, gProgressLayerLineWidth / 2.0, gProgressLayerLineWidth / 2.0);
CGFloat radius = self.progressLayer.bounds.size.width / 2;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radius, radius)
radius:radius
startAngle:-M_PI_2
endAngle:-M_PI_2 + M_PI * 2
clockwise:YES];
self.progressLayer.path = path.CGPath;
[self.photoBtn.layer addSublayer:self.progressLayer];
}
CGFloat closeButtonWidth = 30.0;
CGFloat closeButtonX = (self.photoBtn.frame.origin.x - closeButtonWidth) / 2.0;
CGFloat closeButtonY = self.photoBtn.center.y - closeButtonWidth / 2.0;
self.closeButton.frame = CGRectMake(closeButtonX, closeButtonY, closeButtonWidth, closeButtonWidth);
if (isRTL()) {
[self.closeButton resetFrameToFitRTL];
}
CGFloat pictureButtonWidth = 30.0;
self.pictureLibButton.frame = CGRectMake(self.contentView.frame.size.width - closeButtonX, closeButtonY, pictureButtonWidth, pictureButtonWidth);
if (isRTL()) {
[self.pictureLibButton resetFrameToFitRTL];
}
self.slider.transform = CGAffineTransformMakeRotation(M_PI_2);
self.slider.frame = CGRectMake(self.bounds.size.width - 50, 50, 15, 200);
}
}
#pragma mark -
- (void)setProgress:(CGFloat)progress {
if (progress < 0) {
return;
} else if (progress < 1.0) {
self.progressLayer.strokeEnd = progress;
}
if (progress >= 1.0) {
self.progressLayer.strokeEnd = 1.0;
}
}
#pragma mark - Event Response
- (void)tapAction:(UIGestureRecognizer *)tap {
if ([_delegate respondsToSelector:@selector(focusAction:point:handle:)]) {
CGPoint point = [tap locationInView:self.previewView];
[self runFocusAnimation:self.focusView point:point];
[_delegate focusAction:self
point:[self.previewView captureDevicePointForPoint:point]
handle:^(NSError *error) {
if (error) NSAssert1(NO, @"%@", error); //[self showError:error];
}];
}
}
- (void)runFocusAnimation:(UIView *)view point:(CGPoint)point {
view.center = point;
view.hidden = NO;
[UIView animateWithDuration:0.15f
delay:0.0f
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
view.layer.transform = CATransform3DMakeScale(0.5, 0.5, 1.0);
}
completion:^(BOOL complete) {
double delayInSeconds = 0.5f;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
view.hidden = YES;
view.transform = CGAffineTransformIdentity;
});
}];
}
- (void)pinchAction:(UIPinchGestureRecognizer *)pinch {
if ([_delegate respondsToSelector:@selector(zoomAction:factor:)]) {
if (pinch.state == UIGestureRecognizerStateBegan) {
[UIView animateWithDuration:0.1
animations:^{
self->_slider.alpha = 1;
}];
} else if (pinch.state == UIGestureRecognizerStateChanged) {
if (pinch.velocity > 0) {
_slider.value += pinch.velocity / 100;
} else {
_slider.value += pinch.velocity / 20;
}
[_delegate zoomAction:self factor:powf(5, _slider.value)];
} else {
[UIView animateWithDuration:0.1
animations:^{
self->_slider.alpha = 0.0;
}];
}
}
}
- (void)switchCameraButtonClick:(UIButton *)btn {
if ([self.delegate respondsToSelector:@selector(swicthCameraAction:handle:)]) {
[self.delegate swicthCameraAction:self
handle:^(NSError *_Nonnull error){
//
}];
}
}
- (void)closeButtonClick:(UIButton *)btn {
if ([self.delegate respondsToSelector:@selector(cancelAction:)]) {
[self.delegate cancelAction:self];
}
}
- (void)pictureLibClick:(UIButton *)btn {
if ([self.delegate respondsToSelector:@selector(pictureLibAction:)]) {
[self.delegate pictureLibAction:self];
}
}
- (void)longPressGesture:(UILongPressGestureRecognizer *)gesture {
switch (gesture.state) {
case UIGestureRecognizerStateBegan: {
[self beginVideoRecord];
break;
}
case UIGestureRecognizerStateChanged: {
break;
}
case UIGestureRecognizerStateEnded: {
[self endVideoRecordWithCaptureDuration:self.timer.captureDuration];
break;
}
default:
break;
}
}
- (void)beginVideoRecord {
if (self.isVideoRecording) {
return;
}
self.closeButton.hidden = YES;
self.isVideoRecording = YES;
self.pictureLibButton.hidden = YES;
[self.timer startTimer];
dispatch_async(dispatch_get_main_queue(), ^{
self.progressLayer.strokeEnd = 0.0;
[UIView animateWithDuration:0.2
animations:^{
self.photoStateView.transform = CGAffineTransformMakeScale(.5, .5);
self.photoBtn.transform = CGAffineTransformMakeScale(gPhotoBtnZoomInRatio, gPhotoBtnZoomInRatio);
}];
if ([self.delegate respondsToSelector:@selector(startRecordVideoAction:)]) {
[self.delegate startRecordVideoAction:self];
}
});
}
- (void)endVideoRecordWithCaptureDuration:(CGFloat)duration {
if (self.isVideoRecording == NO) {
return;
}
self.closeButton.hidden = NO;
self.isVideoRecording = NO;
self.pictureLibButton.hidden = NO;
[self.timer stopTimer];
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.2
animations:^{
self.photoStateView.transform = CGAffineTransformIdentity;
self.photoBtn.transform = CGAffineTransformIdentity;
}];
if ([self.delegate respondsToSelector:@selector(stopRecordVideoAction:RecordDuration:)]) {
[self.delegate stopRecordVideoAction:self RecordDuration:duration];
}
self.progressLayer.strokeEnd = 0.0;
});
}
- (void)tapGesture:(UITapGestureRecognizer *)tapGesture {
if ([_delegate respondsToSelector:@selector(takePhotoAction:)]) {
[_delegate takePhotoAction:self];
}
}
#pragma mark - Getters & Setters
- (UIView *)contentView {
if (!_contentView) {
_contentView = [UIView new];
}
return _contentView;
}
- (TUICaptureVideoPreviewView *)previewView {
if (!_previewView) {
_previewView = [[TUICaptureVideoPreviewView alloc] init];
_previewView.userInteractionEnabled = YES;
}
return _previewView;
}
- (UIButton *)switchCameraButton {
if (!_switchCameraButton) {
_switchCameraButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *switchCameraButtonImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"camera_switchCamera")];
[_switchCameraButton setImage:switchCameraButtonImage forState:UIControlStateNormal];
[_switchCameraButton addTarget:self action:@selector(switchCameraButtonClick:) forControlEvents:UIControlEventTouchUpInside];
}
return _switchCameraButton;
}
- (UIButton *)closeButton {
if (!_closeButton) {
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *closeButtonImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"camera_back")];
[_closeButton setBackgroundImage:[closeButtonImage rtl_imageFlippedForRightToLeftLayoutDirection] forState:UIControlStateNormal];
[_closeButton addTarget:self action:@selector(closeButtonClick:) forControlEvents:UIControlEventTouchUpInside];
}
return _closeButton;
}
- (UIButton *)pictureLibButton {
if (!_pictureLibButton) {
_pictureLibButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *pictureImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"more_picture")];
[_pictureLibButton setBackgroundImage:pictureImage forState:UIControlStateNormal];
[_pictureLibButton addTarget:self action:@selector(pictureLibClick:) forControlEvents:UIControlEventTouchUpInside];
}
return _pictureLibButton;
}
- (UIView *)photoBtn {
if (!_photoBtn) {
_photoBtn = [UIView new];
[_photoBtn setBackgroundColor:[[UIColor whiteColor] colorWithAlphaComponent:0.5]];
if (self.type == TUICameraMediaTypeVideo) {
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGesture:)];
[_photoBtn addGestureRecognizer:longPress];
_longPress = longPress;
}
if (self.type == TUICameraMediaTypePhoto) {
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)];
[_photoBtn addGestureRecognizer:tapGesture];
}
_photoBtn.userInteractionEnabled = YES;
_photoStateView = [UIView new];
_photoStateView.backgroundColor = [UIColor whiteColor];
[_photoBtn addSubview:_photoStateView];
}
return _photoBtn;
}
- (CAShapeLayer *)progressLayer {
if (!_progressLayer) {
_progressLayer = [CAShapeLayer layer];
_progressLayer.fillColor = [UIColor clearColor].CGColor;
_progressLayer.lineWidth = gProgressLayerLineWidth;
_progressLayer.strokeColor = [UIColor colorWithRed:0 green:204.0 / 255 blue:0 alpha:1].CGColor;
_progressLayer.strokeStart = 0;
_progressLayer.strokeEnd = 0;
_progressLayer.lineCap = kCALineCapButt;
}
return _progressLayer;
}
- (UIView *)focusView {
if (_focusView == nil) {
_focusView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 150, 150.0f)];
_focusView.backgroundColor = [UIColor clearColor];
_focusView.layer.borderColor = [UIColor colorWithRed:0 green:204.0 / 255 blue:0 alpha:1].CGColor;
_focusView.layer.borderWidth = 3.0f;
_focusView.hidden = YES;
}
return _focusView;
}
- (UISlider *)slider {
if (_slider == nil) {
_slider = [[UISlider alloc] init];
_slider.minimumValue = 0;
_slider.maximumValue = 1;
_slider.maximumTrackTintColor = [UIColor whiteColor];
_slider.minimumTrackTintColor = [UIColor whiteColor];
_slider.alpha = 0.0;
_slider.hidden = YES;
}
return _slider;
}
@end