// Created by Tencent on 2023/06/09. // Copyright © 2023 Tencent. All rights reserved. #import "TUICameraView.h" #import #import #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