TUICameraView.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. // Created by Tencent on 2023/06/09.
  2. // Copyright © 2023 Tencent. All rights reserved.
  3. #import "TUICameraView.h"
  4. #import <TIMCommon/TIMCommonModel.h>
  5. #import <TIMCommon/TIMDefine.h>
  6. #import "TUICaptureTimer.h"
  7. static CGFloat gPhotoBtnZoomInRatio = 1.125;
  8. static CGFloat gProgressLayerLineWidth = 5.0;
  9. @interface TUICameraView ()
  10. @property(nonatomic) UIView *contentView;
  11. @property(nonatomic) UIButton *switchCameraButton;
  12. @property(nonatomic) UIButton *closeButton;
  13. @property(nonatomic, strong) UIButton *pictureLibButton;
  14. @property(nonatomic) UIView *focusView;
  15. @property(nonatomic) UISlider *slider;
  16. @property(nonatomic) UIView *photoBtn;
  17. @property(nonatomic) UIView *photoStateView;
  18. @property(nonatomic) UILongPressGestureRecognizer *longPress;
  19. @property(nonatomic) CGRect lastRect;
  20. @property(nonatomic) CAShapeLayer *progressLayer;
  21. @property(nonatomic) TUICaptureTimer *timer;
  22. @property(nonatomic) BOOL isVideoRecording;
  23. @end
  24. @implementation TUICameraView
  25. @synthesize previewView = _previewView;
  26. - (instancetype)initWithFrame:(CGRect)frame {
  27. self = [super initWithFrame:frame];
  28. if (self) {
  29. self.type = TUICameraMediaTypePhoto;
  30. self.aspectRatio = TUICameraViewAspectRatio16x9;
  31. self.backgroundColor = [UIColor blackColor];
  32. self.maxVideoCaptureTimeLimit = 15.0;
  33. }
  34. return self;
  35. }
  36. - (void)setupUI {
  37. [self addSubview:self.contentView];
  38. [self.contentView addSubview:self.previewView];
  39. [self.contentView addSubview:self.switchCameraButton];
  40. [self.contentView addSubview:self.photoBtn];
  41. [self.contentView addSubview:self.closeButton];
  42. [self.contentView addSubview:self.pictureLibButton];
  43. [self.previewView addSubview:self.focusView];
  44. [self.previewView addSubview:self.slider];
  45. self.timer = ({
  46. TUICaptureTimer *timer = [TUICaptureTimer new];
  47. timer.maxCaptureTime = self.maxVideoCaptureTimeLimit;
  48. __weak __typeof(self) weakSelf = self;
  49. timer.progressBlock = ^(CGFloat ratio, CGFloat recordTime) {
  50. weakSelf.progress = ratio;
  51. };
  52. timer.progressFinishBlock = ^(CGFloat ratio, CGFloat recordTime) {
  53. weakSelf.progress = 1;
  54. self.longPress.enabled = NO;
  55. [weakSelf endVideoRecordWithCaptureDuration:recordTime];
  56. self.longPress.enabled = YES;
  57. };
  58. timer.progressCancelBlock = ^{
  59. weakSelf.progress = 0;
  60. };
  61. timer;
  62. });
  63. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)];
  64. [self.previewView addGestureRecognizer:tap];
  65. UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchAction:)];
  66. [self.previewView addGestureRecognizer:pinch];
  67. }
  68. - (void)layoutSubviews {
  69. [super layoutSubviews];
  70. if (!CGRectEqualToRect(self.lastRect, self.bounds)) {
  71. [self setupUI];
  72. self.lastRect = self.bounds;
  73. self.contentView.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
  74. CGFloat previewViewWidth = self.contentView.bounds.size.width;
  75. CGFloat previewViewHeight;
  76. switch (self.aspectRatio) {
  77. case TUICameraViewAspectRatio1x1:
  78. previewViewHeight = previewViewWidth;
  79. break;
  80. case TUICameraViewAspectRatio16x9:
  81. previewViewHeight = previewViewWidth * (16.0 / 9.0);
  82. break;
  83. case TUICameraViewAspectRatio5x4:
  84. previewViewHeight = previewViewWidth * (5.0 / 4.0);
  85. break;
  86. default:
  87. break;
  88. }
  89. CGFloat previewViewY = (self.contentView.bounds.size.height - previewViewHeight) / 2.0;
  90. self.previewView.frame = CGRectMake(0, previewViewY, self.contentView.bounds.size.width, previewViewHeight);
  91. CGFloat switchCameraButtonWidth = 44.0;
  92. self.switchCameraButton.frame =
  93. CGRectMake(self.contentView.bounds.size.width - switchCameraButtonWidth - 16.0, 30.0, switchCameraButtonWidth, switchCameraButtonWidth);
  94. if (isRTL()) {
  95. [self.switchCameraButton resetFrameToFitRTL];
  96. }
  97. CGFloat photoBtnWidth = 100.0;
  98. self.photoBtn.frame = CGRectMake((self.contentView.bounds.size.width - photoBtnWidth) / 2.0, self.contentView.bounds.size.height - photoBtnWidth - 30,
  99. photoBtnWidth, photoBtnWidth);
  100. self.photoBtn.layer.cornerRadius = photoBtnWidth / 2.0;
  101. CGFloat distanceToPhotoBtn = 10.0;
  102. CGFloat photoStateViewWidth = photoBtnWidth - 2 * distanceToPhotoBtn;
  103. self.photoStateView.frame = CGRectMake(distanceToPhotoBtn, distanceToPhotoBtn, photoStateViewWidth, photoStateViewWidth);
  104. self.photoStateView.layer.cornerRadius = photoStateViewWidth / 2.0;
  105. if (self.type == TUICameraMediaTypeVideo) {
  106. self.progressLayer.frame = CGRectInset(self.photoBtn.bounds, gProgressLayerLineWidth / 2.0, gProgressLayerLineWidth / 2.0);
  107. CGFloat radius = self.progressLayer.bounds.size.width / 2;
  108. UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radius, radius)
  109. radius:radius
  110. startAngle:-M_PI_2
  111. endAngle:-M_PI_2 + M_PI * 2
  112. clockwise:YES];
  113. self.progressLayer.path = path.CGPath;
  114. [self.photoBtn.layer addSublayer:self.progressLayer];
  115. }
  116. CGFloat closeButtonWidth = 30.0;
  117. CGFloat closeButtonX = (self.photoBtn.frame.origin.x - closeButtonWidth) / 2.0;
  118. CGFloat closeButtonY = self.photoBtn.center.y - closeButtonWidth / 2.0;
  119. self.closeButton.frame = CGRectMake(closeButtonX, closeButtonY, closeButtonWidth, closeButtonWidth);
  120. if (isRTL()) {
  121. [self.closeButton resetFrameToFitRTL];
  122. }
  123. CGFloat pictureButtonWidth = 30.0;
  124. self.pictureLibButton.frame = CGRectMake(self.contentView.frame.size.width - closeButtonX, closeButtonY, pictureButtonWidth, pictureButtonWidth);
  125. if (isRTL()) {
  126. [self.pictureLibButton resetFrameToFitRTL];
  127. }
  128. self.slider.transform = CGAffineTransformMakeRotation(M_PI_2);
  129. self.slider.frame = CGRectMake(self.bounds.size.width - 50, 50, 15, 200);
  130. }
  131. }
  132. #pragma mark -
  133. - (void)setProgress:(CGFloat)progress {
  134. if (progress < 0) {
  135. return;
  136. } else if (progress < 1.0) {
  137. self.progressLayer.strokeEnd = progress;
  138. }
  139. if (progress >= 1.0) {
  140. self.progressLayer.strokeEnd = 1.0;
  141. }
  142. }
  143. #pragma mark - Event Response
  144. - (void)tapAction:(UIGestureRecognizer *)tap {
  145. if ([_delegate respondsToSelector:@selector(focusAction:point:handle:)]) {
  146. CGPoint point = [tap locationInView:self.previewView];
  147. [self runFocusAnimation:self.focusView point:point];
  148. [_delegate focusAction:self
  149. point:[self.previewView captureDevicePointForPoint:point]
  150. handle:^(NSError *error) {
  151. if (error) NSAssert1(NO, @"%@", error); //[self showError:error];
  152. }];
  153. }
  154. }
  155. - (void)runFocusAnimation:(UIView *)view point:(CGPoint)point {
  156. view.center = point;
  157. view.hidden = NO;
  158. [UIView animateWithDuration:0.15f
  159. delay:0.0f
  160. options:UIViewAnimationOptionCurveEaseInOut
  161. animations:^{
  162. view.layer.transform = CATransform3DMakeScale(0.5, 0.5, 1.0);
  163. }
  164. completion:^(BOOL complete) {
  165. double delayInSeconds = 0.5f;
  166. dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
  167. dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
  168. view.hidden = YES;
  169. view.transform = CGAffineTransformIdentity;
  170. });
  171. }];
  172. }
  173. - (void)pinchAction:(UIPinchGestureRecognizer *)pinch {
  174. if ([_delegate respondsToSelector:@selector(zoomAction:factor:)]) {
  175. if (pinch.state == UIGestureRecognizerStateBegan) {
  176. [UIView animateWithDuration:0.1
  177. animations:^{
  178. self->_slider.alpha = 1;
  179. }];
  180. } else if (pinch.state == UIGestureRecognizerStateChanged) {
  181. if (pinch.velocity > 0) {
  182. _slider.value += pinch.velocity / 100;
  183. } else {
  184. _slider.value += pinch.velocity / 20;
  185. }
  186. [_delegate zoomAction:self factor:powf(5, _slider.value)];
  187. } else {
  188. [UIView animateWithDuration:0.1
  189. animations:^{
  190. self->_slider.alpha = 0.0;
  191. }];
  192. }
  193. }
  194. }
  195. - (void)switchCameraButtonClick:(UIButton *)btn {
  196. if ([self.delegate respondsToSelector:@selector(swicthCameraAction:handle:)]) {
  197. [self.delegate swicthCameraAction:self
  198. handle:^(NSError *_Nonnull error){
  199. //
  200. }];
  201. }
  202. }
  203. - (void)closeButtonClick:(UIButton *)btn {
  204. if ([self.delegate respondsToSelector:@selector(cancelAction:)]) {
  205. [self.delegate cancelAction:self];
  206. }
  207. }
  208. - (void)pictureLibClick:(UIButton *)btn {
  209. if ([self.delegate respondsToSelector:@selector(pictureLibAction:)]) {
  210. [self.delegate pictureLibAction:self];
  211. }
  212. }
  213. - (void)longPressGesture:(UILongPressGestureRecognizer *)gesture {
  214. switch (gesture.state) {
  215. case UIGestureRecognizerStateBegan: {
  216. [self beginVideoRecord];
  217. break;
  218. }
  219. case UIGestureRecognizerStateChanged: {
  220. break;
  221. }
  222. case UIGestureRecognizerStateEnded: {
  223. [self endVideoRecordWithCaptureDuration:self.timer.captureDuration];
  224. break;
  225. }
  226. default:
  227. break;
  228. }
  229. }
  230. - (void)beginVideoRecord {
  231. if (self.isVideoRecording) {
  232. return;
  233. }
  234. self.closeButton.hidden = YES;
  235. self.isVideoRecording = YES;
  236. self.pictureLibButton.hidden = YES;
  237. [self.timer startTimer];
  238. dispatch_async(dispatch_get_main_queue(), ^{
  239. self.progressLayer.strokeEnd = 0.0;
  240. [UIView animateWithDuration:0.2
  241. animations:^{
  242. self.photoStateView.transform = CGAffineTransformMakeScale(.5, .5);
  243. self.photoBtn.transform = CGAffineTransformMakeScale(gPhotoBtnZoomInRatio, gPhotoBtnZoomInRatio);
  244. }];
  245. if ([self.delegate respondsToSelector:@selector(startRecordVideoAction:)]) {
  246. [self.delegate startRecordVideoAction:self];
  247. }
  248. });
  249. }
  250. - (void)endVideoRecordWithCaptureDuration:(CGFloat)duration {
  251. if (self.isVideoRecording == NO) {
  252. return;
  253. }
  254. self.closeButton.hidden = NO;
  255. self.isVideoRecording = NO;
  256. self.pictureLibButton.hidden = NO;
  257. [self.timer stopTimer];
  258. dispatch_async(dispatch_get_main_queue(), ^{
  259. [UIView animateWithDuration:0.2
  260. animations:^{
  261. self.photoStateView.transform = CGAffineTransformIdentity;
  262. self.photoBtn.transform = CGAffineTransformIdentity;
  263. }];
  264. if ([self.delegate respondsToSelector:@selector(stopRecordVideoAction:RecordDuration:)]) {
  265. [self.delegate stopRecordVideoAction:self RecordDuration:duration];
  266. }
  267. self.progressLayer.strokeEnd = 0.0;
  268. });
  269. }
  270. - (void)tapGesture:(UITapGestureRecognizer *)tapGesture {
  271. if ([_delegate respondsToSelector:@selector(takePhotoAction:)]) {
  272. [_delegate takePhotoAction:self];
  273. }
  274. }
  275. #pragma mark - Getters & Setters
  276. - (UIView *)contentView {
  277. if (!_contentView) {
  278. _contentView = [UIView new];
  279. }
  280. return _contentView;
  281. }
  282. - (TUICaptureVideoPreviewView *)previewView {
  283. if (!_previewView) {
  284. _previewView = [[TUICaptureVideoPreviewView alloc] init];
  285. _previewView.userInteractionEnabled = YES;
  286. }
  287. return _previewView;
  288. }
  289. - (UIButton *)switchCameraButton {
  290. if (!_switchCameraButton) {
  291. _switchCameraButton = [UIButton buttonWithType:UIButtonTypeCustom];
  292. UIImage *switchCameraButtonImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"camera_switchCamera")];
  293. [_switchCameraButton setImage:switchCameraButtonImage forState:UIControlStateNormal];
  294. [_switchCameraButton addTarget:self action:@selector(switchCameraButtonClick:) forControlEvents:UIControlEventTouchUpInside];
  295. }
  296. return _switchCameraButton;
  297. }
  298. - (UIButton *)closeButton {
  299. if (!_closeButton) {
  300. _closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
  301. UIImage *closeButtonImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"camera_back")];
  302. [_closeButton setBackgroundImage:[closeButtonImage rtl_imageFlippedForRightToLeftLayoutDirection] forState:UIControlStateNormal];
  303. [_closeButton addTarget:self action:@selector(closeButtonClick:) forControlEvents:UIControlEventTouchUpInside];
  304. }
  305. return _closeButton;
  306. }
  307. - (UIButton *)pictureLibButton {
  308. if (!_pictureLibButton) {
  309. _pictureLibButton = [UIButton buttonWithType:UIButtonTypeCustom];
  310. UIImage *pictureImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"more_picture")];
  311. [_pictureLibButton setBackgroundImage:pictureImage forState:UIControlStateNormal];
  312. [_pictureLibButton addTarget:self action:@selector(pictureLibClick:) forControlEvents:UIControlEventTouchUpInside];
  313. }
  314. return _pictureLibButton;
  315. }
  316. - (UIView *)photoBtn {
  317. if (!_photoBtn) {
  318. _photoBtn = [UIView new];
  319. [_photoBtn setBackgroundColor:[[UIColor whiteColor] colorWithAlphaComponent:0.5]];
  320. if (self.type == TUICameraMediaTypeVideo) {
  321. UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGesture:)];
  322. [_photoBtn addGestureRecognizer:longPress];
  323. _longPress = longPress;
  324. }
  325. if (self.type == TUICameraMediaTypePhoto) {
  326. UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)];
  327. [_photoBtn addGestureRecognizer:tapGesture];
  328. }
  329. _photoBtn.userInteractionEnabled = YES;
  330. _photoStateView = [UIView new];
  331. _photoStateView.backgroundColor = [UIColor whiteColor];
  332. [_photoBtn addSubview:_photoStateView];
  333. }
  334. return _photoBtn;
  335. }
  336. - (CAShapeLayer *)progressLayer {
  337. if (!_progressLayer) {
  338. _progressLayer = [CAShapeLayer layer];
  339. _progressLayer.fillColor = [UIColor clearColor].CGColor;
  340. _progressLayer.lineWidth = gProgressLayerLineWidth;
  341. _progressLayer.strokeColor = [UIColor colorWithRed:0 green:204.0 / 255 blue:0 alpha:1].CGColor;
  342. _progressLayer.strokeStart = 0;
  343. _progressLayer.strokeEnd = 0;
  344. _progressLayer.lineCap = kCALineCapButt;
  345. }
  346. return _progressLayer;
  347. }
  348. - (UIView *)focusView {
  349. if (_focusView == nil) {
  350. _focusView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 150, 150.0f)];
  351. _focusView.backgroundColor = [UIColor clearColor];
  352. _focusView.layer.borderColor = [UIColor colorWithRed:0 green:204.0 / 255 blue:0 alpha:1].CGColor;
  353. _focusView.layer.borderWidth = 3.0f;
  354. _focusView.hidden = YES;
  355. }
  356. return _focusView;
  357. }
  358. - (UISlider *)slider {
  359. if (_slider == nil) {
  360. _slider = [[UISlider alloc] init];
  361. _slider.minimumValue = 0;
  362. _slider.maximumValue = 1;
  363. _slider.maximumTrackTintColor = [UIColor whiteColor];
  364. _slider.minimumTrackTintColor = [UIColor whiteColor];
  365. _slider.alpha = 0.0;
  366. _slider.hidden = YES;
  367. }
  368. return _slider;
  369. }
  370. @end