TUIImageCollectionCell.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. // Created by Tencent on 2023/06/09.
  2. // Copyright © 2023 Tencent. All rights reserved.
  3. #import "TUIImageCollectionCell.h"
  4. #import <TIMCommon/TIMDefine.h>
  5. #import <TUICore/TUITool.h>
  6. #import "TUICircleLodingView.h"
  7. @interface TUIImageCollectionCellScrollView : UIScrollView <UIScrollViewDelegate>
  8. @property(nonatomic, strong) UIView *containerView;
  9. @property(assign, nonatomic) CGFloat imageNormalWidth;
  10. @property(assign, nonatomic) CGFloat imageNormalHeight;
  11. - (void)pictureZoomWithScale:(CGFloat)zoomScale;
  12. @end
  13. @implementation TUIImageCollectionCellScrollView
  14. - (id)initWithFrame:(CGRect)frame {
  15. self = [super initWithFrame:frame];
  16. if (self) {
  17. self.delegate = self;
  18. self.minimumZoomScale = 0.1f;
  19. self.maximumZoomScale = 2.0f;
  20. _imageNormalHeight = frame.size.height;
  21. _imageNormalWidth = frame.size.width;
  22. self.containerView = [[UIView alloc] initWithFrame:frame];
  23. [self addSubview:self.containerView];
  24. if (@available(iOS 11.0, *)) {
  25. self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
  26. } else {
  27. // Fallback on earlier versions
  28. }
  29. }
  30. return self;
  31. }
  32. - (void)drawRect:(CGRect)rect {
  33. [super drawRect:rect];
  34. }
  35. #pragma mark-- Help Methods
  36. - (void)pictureZoomWithScale:(CGFloat)zoomScale {
  37. CGFloat imageScaleWidth = zoomScale * self.imageNormalWidth;
  38. CGFloat imageScaleHeight = zoomScale * self.imageNormalHeight;
  39. CGFloat imageX = 0;
  40. CGFloat imageY = 0;
  41. if (imageScaleWidth < self.frame.size.width) {
  42. imageX = floorf((self.frame.size.width - imageScaleWidth) / 2.0);
  43. }
  44. if (imageScaleHeight < self.frame.size.height) {
  45. imageY = floorf((self.frame.size.height - imageScaleHeight) / 2.0);
  46. }
  47. self.containerView.frame = CGRectMake(imageX, imageY, imageScaleWidth, imageScaleHeight);
  48. self.contentSize = CGSizeMake(imageScaleWidth, imageScaleHeight);
  49. }
  50. #pragma mark-- Setter
  51. - (void)setImageNormalWidth:(CGFloat)imageNormalWidth {
  52. _imageNormalWidth = imageNormalWidth;
  53. self.containerView.frame = CGRectMake(0, 0, _imageNormalWidth, _imageNormalHeight);
  54. self.containerView.center = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
  55. }
  56. - (void)setImageNormalHeight:(CGFloat)imageNormalHeight {
  57. _imageNormalHeight = imageNormalHeight;
  58. self.containerView.frame = CGRectMake(0, 0, _imageNormalWidth, _imageNormalHeight);
  59. self.containerView.center = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
  60. }
  61. #pragma mark-- UIScrollViewDelegate
  62. // Returns the view control that needs to be zoomed. During zooming
  63. - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
  64. return self.containerView;
  65. }
  66. // BeginZooming
  67. - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
  68. NSLog(@"BeginZooming");
  69. }
  70. // EndZooming
  71. - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
  72. NSLog(@"EndZooming");
  73. }
  74. // zoom
  75. - (void)scrollViewDidZoom:(UIScrollView *)scrollView {
  76. CGFloat imageScaleWidth = scrollView.zoomScale * self.imageNormalWidth;
  77. CGFloat imageScaleHeight = scrollView.zoomScale * self.imageNormalHeight;
  78. CGFloat imageX = 0;
  79. CGFloat imageY = 0;
  80. if (imageScaleWidth < self.frame.size.width) {
  81. imageX = floorf((self.frame.size.width - imageScaleWidth) / 2.0);
  82. }
  83. if (imageScaleHeight < self.frame.size.height) {
  84. imageY = floorf((self.frame.size.height - imageScaleHeight) / 2.0);
  85. }
  86. self.containerView.frame = CGRectMake(imageX, imageY, imageScaleWidth, imageScaleHeight);
  87. }
  88. @end
  89. @interface TUIImageCollectionCell ()
  90. @property(nonatomic, strong) TUIImageCollectionCellScrollView *scrollView;
  91. @property(nonatomic, strong) TUIImageMessageCellData *imgCellData;
  92. @property(nonatomic, strong) UIButton *mainDownloadBtn;
  93. @property(nonatomic, strong) TUICircleLodingView *animateCircleView;
  94. @end
  95. @implementation TUIImageCollectionCell
  96. - (id)initWithFrame:(CGRect)frame {
  97. self = [super initWithFrame:frame];
  98. if (self) {
  99. [self setupViews];
  100. [self setupRotaionNotifications];
  101. }
  102. return self;
  103. }
  104. - (void)setupViews {
  105. self.scrollView = [[TUIImageCollectionCellScrollView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
  106. [self addSubview:self.scrollView];
  107. self.imageView = [[UIImageView alloc] init];
  108. self.imageView.layer.cornerRadius = 5.0;
  109. [self.imageView.layer setMasksToBounds:YES];
  110. self.imageView.contentMode = UIViewContentModeScaleAspectFill;
  111. self.imageView.backgroundColor = [UIColor clearColor];
  112. [self.scrollView.containerView addSubview:self.imageView];
  113. self.imageView.mm_fill();
  114. self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  115. self.mainDownloadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
  116. self.mainDownloadBtn.contentMode = UIViewContentModeScaleToFill;
  117. [self.mainDownloadBtn setTitle:TIMCommonLocalizableString(TUIKitImageViewOrigin) forState:UIControlStateNormal];
  118. self.mainDownloadBtn.backgroundColor = [UIColor grayColor];
  119. [self.mainDownloadBtn.titleLabel setFont:[UIFont systemFontOfSize:14]];
  120. self.mainDownloadBtn.layer.borderColor = [UIColor whiteColor].CGColor;
  121. self.mainDownloadBtn.layer.cornerRadius = .16;
  122. self.mainDownloadBtn.hidden = YES;
  123. [self.mainDownloadBtn addTarget:self action:@selector(mainDownloadBtnClick) forControlEvents:UIControlEventTouchUpInside];
  124. [self addSubview:self.mainDownloadBtn];
  125. self.downloadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
  126. self.downloadBtn.contentMode = UIViewContentModeScaleToFill;
  127. [self.downloadBtn setImage:TUIChatCommonBundleImage(@"download") forState:UIControlStateNormal];
  128. [self.downloadBtn addTarget:self action:@selector(onSaveBtnClick) forControlEvents:UIControlEventTouchUpInside];
  129. [self addSubview:self.downloadBtn];
  130. self.animateCircleView = [[TUICircleLodingView alloc] initWithFrame:CGRectMake(0, 0, kScale390(40), kScale390(40))];
  131. self.animateCircleView.hidden = YES;
  132. self.animateCircleView.progress = 0;
  133. [self addSubview:_animateCircleView];
  134. self.backgroundColor = [UIColor clearColor];
  135. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onSelectMedia)];
  136. [self addGestureRecognizer:tap];
  137. }
  138. - (void)setupRotaionNotifications {
  139. if (@available(iOS 16.0, *)) {
  140. [[NSNotificationCenter defaultCenter] addObserver:self
  141. selector:@selector(onDeviceOrientationChange:)
  142. name:TUIMessageMediaViewDeviceOrientationChangeNotification
  143. object:nil];
  144. } else {
  145. [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
  146. [[NSNotificationCenter defaultCenter] addObserver:self
  147. selector:@selector(onDeviceOrientationChange:)
  148. name:UIDeviceOrientationDidChangeNotification
  149. object:nil];
  150. }
  151. }
  152. - (void)mainDownloadBtnClick {
  153. if (self.imgCellData.originImage == nil) {
  154. [self.imgCellData downloadImage:TImage_Type_Origin];
  155. }
  156. }
  157. - (void)onSaveBtnClick {
  158. UIImage *image = self.imageView.image;
  159. [[PHPhotoLibrary sharedPhotoLibrary]
  160. performChanges:^{
  161. [PHAssetChangeRequest creationRequestForAssetFromImage:image];
  162. }
  163. completionHandler:^(BOOL success, NSError *_Nullable error) {
  164. dispatch_async(dispatch_get_main_queue(), ^{
  165. if (success) {
  166. [TUITool makeToast:TIMCommonLocalizableString(TUIKitPictureSavedSuccess)];
  167. } else {
  168. [TUITool makeToast:TIMCommonLocalizableString(TUIKitPictureSavedFailed)];
  169. }
  170. });
  171. }];
  172. }
  173. - (void)onSelectMedia {
  174. if (self.delegate && [self.delegate respondsToSelector:@selector(onCloseMedia:)]) {
  175. [self.delegate onCloseMedia:self];
  176. }
  177. }
  178. - (void)fillWithData:(TUIImageMessageCellData *)data;
  179. {
  180. [super fillWithData:data];
  181. self.imgCellData = data;
  182. self.imageView.image = nil;
  183. BOOL hasRiskContent = data.innerMessage.hasRiskContent;
  184. if (hasRiskContent ) {
  185. self.imageView.image = TIMCommonBundleThemeImage(@"", @"icon_security_strike");
  186. for (UIView *subview in self.subviews) {
  187. if (subview != self.scrollView ){
  188. subview.hidden = YES;
  189. }
  190. }
  191. return;
  192. }
  193. //1.Read from cache
  194. if ([self originImageFirst:data]) {
  195. return;
  196. }
  197. if ([self largeImageSecond:data]) {
  198. return;
  199. }
  200. //2. download image
  201. if (data.thumbImage == nil) {
  202. [data downloadImage:TImage_Type_Thumb];
  203. }
  204. if (data.thumbImage && data.largeImage == nil) {
  205. self.animateCircleView.hidden = NO;
  206. [data downloadImage:TImage_Type_Large];
  207. }
  208. [self fillThumbImageWithData:data];
  209. [self fillLargeImageWithData:data];
  210. [self fillOriginImageWithData:data];
  211. }
  212. - (BOOL)largeImageSecond:(TUIImageMessageCellData *)data {
  213. BOOL isExist = NO;
  214. NSString *path = [data getImagePath:TImage_Type_Large isExist:&isExist];
  215. if (isExist) {
  216. [data decodeImage:TImage_Type_Large];
  217. [self fillLargeImageWithData:data];
  218. }
  219. return isExist;
  220. }
  221. - (BOOL)originImageFirst:(TUIImageMessageCellData *)data {
  222. BOOL isExist = NO;
  223. NSString *path = [data getImagePath:TImage_Type_Origin isExist:&isExist];
  224. if (isExist) {
  225. [data decodeImage:TImage_Type_Origin];
  226. [self fillOriginImageWithData:data];
  227. }
  228. return isExist;
  229. }
  230. - (void)fillOriginImageWithData:(TUIImageMessageCellData *)data{
  231. @weakify(self);
  232. // originImage
  233. [[RACObserve(data, originImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *originImage) {
  234. @strongify(self);
  235. if (originImage) {
  236. self.imageView.image = originImage;
  237. [self setNeedsLayout];
  238. }
  239. }];
  240. [[[RACObserve(data, originProgress) takeUntil:self.rac_prepareForReuseSignal] distinctUntilChanged] subscribeNext:^(NSNumber *x) {
  241. @strongify(self);
  242. int progress = [x intValue];
  243. if (progress == 100) {
  244. self.animateCircleView.progress = 99;
  245. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  246. self.animateCircleView.progress = 100;
  247. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  248. self.animateCircleView.progress = 0;
  249. self.mainDownloadBtn.hidden = YES;
  250. self.animateCircleView.hidden = YES;
  251. [self.mainDownloadBtn setTitle:TIMCommonLocalizableString(TUIKitImageViewOrigin) forState:UIControlStateNormal];
  252. });
  253. });
  254. } else if (progress > 1 && progress < 100) {
  255. self.animateCircleView.progress = progress;
  256. [self.mainDownloadBtn setTitle:[NSString stringWithFormat:@"%d%%", progress] forState:UIControlStateNormal];
  257. self.animateCircleView.hidden = YES;
  258. } else {
  259. self.animateCircleView.progress = progress;
  260. }
  261. }];
  262. }
  263. - (void)fillLargeImageWithData:(TUIImageMessageCellData *)data {
  264. @weakify(self);
  265. // largeImage
  266. [[RACObserve(data, largeImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *largeImage) {
  267. @strongify(self);
  268. if (largeImage) {
  269. self.imageView.image = largeImage;
  270. [self setNeedsLayout];
  271. }
  272. }];
  273. [[[RACObserve(data, largeProgress) takeUntil:self.rac_prepareForReuseSignal] distinctUntilChanged] subscribeNext:^(NSNumber *x) {
  274. @strongify(self);
  275. int progress = [x intValue];
  276. if (progress == 100) {
  277. self.animateCircleView.progress = 99;
  278. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  279. self.animateCircleView.progress = 100;
  280. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  281. self.animateCircleView.progress = 0;
  282. self.mainDownloadBtn.hidden = NO;
  283. self.animateCircleView.hidden = YES;
  284. });
  285. });
  286. } else if (progress > 1 && progress < 100) {
  287. self.animateCircleView.progress = progress;
  288. self.mainDownloadBtn.hidden = YES;
  289. self.animateCircleView.hidden = NO;
  290. } else {
  291. self.animateCircleView.progress = progress;
  292. }
  293. }];
  294. }
  295. - (void)fillThumbImageWithData:(TUIImageMessageCellData *)data {
  296. @weakify(self);
  297. [[RACObserve(data, thumbImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *thumbImage) {
  298. @strongify(self);
  299. if (thumbImage) {
  300. self.imageView.image = thumbImage;
  301. [self setNeedsLayout];
  302. }
  303. }];
  304. }
  305. - (void)layoutSubviews {
  306. [super layoutSubviews];
  307. [self.mainDownloadBtn sizeToFit];
  308. self.mainDownloadBtn.mm_width(self.mainDownloadBtn.mm_w + 10).mm_height(self.mainDownloadBtn.mm_h).mm__centerX(self.mm_w / 2).mm_bottom(48);
  309. self.mainDownloadBtn.layer.cornerRadius = (self.mainDownloadBtn.mm_h * 0.5);
  310. self.animateCircleView.tui_mm_center();
  311. self.downloadBtn.mm_width(31).mm_height(31).mm_right(16).mm_bottom(48);
  312. self.scrollView.mm_width(self.mm_w).mm_height(self.mm_h).mm__centerX(self.mm_w / 2).mm__centerY(self.mm_h / 2);
  313. self.scrollView.imageNormalWidth = self.imageView.image.size.width;
  314. self.scrollView.imageNormalHeight = self.imageView.image.size.height;
  315. self.imageView.frame = CGRectMake(self.scrollView.bounds.origin.x,
  316. self.scrollView.bounds.origin.y,
  317. self.imageView.image.size.width,
  318. self.imageView.image.size.height);
  319. [self.imageView layoutIfNeeded];
  320. [self adjustScale];
  321. }
  322. - (void)onDeviceOrientationChange:(NSNotification *)noti {
  323. [self reloadAllView];
  324. }
  325. - (void)reloadAllView {
  326. for (UIView *subview in self.subviews) {
  327. if (subview) {
  328. [UIView animateWithDuration:0.1 animations:^{
  329. [subview removeFromSuperview];
  330. }];
  331. }
  332. }
  333. [self setupViews];
  334. [self fillWithData:self.imgCellData];
  335. }
  336. #pragma mark - V2TIMAdvancedMsgListener
  337. - (void)onRecvMessageModified:(V2TIMMessage *)msg {
  338. V2TIMMessage *imMsg = msg;
  339. if (imMsg == nil || ![imMsg isKindOfClass:V2TIMMessage.class]) {
  340. return;
  341. }
  342. if ([self.imgCellData.innerMessage.msgID isEqualToString:imMsg.msgID]) {
  343. BOOL hasRiskContent = imMsg.hasRiskContent;
  344. if (hasRiskContent) {
  345. self.imgCellData.innerMessage = imMsg;
  346. [self showRiskAlert];
  347. }
  348. }
  349. }
  350. - (void)showRiskAlert {
  351. UIAlertController *ac = [UIAlertController alertControllerWithTitle:nil
  352. message:TIMCommonLocalizableString(TUIKitPictureCheckRisk)
  353. preferredStyle:UIAlertControllerStyleAlert];
  354. __weak typeof(self) weakSelf = self;
  355. [ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(TUIKitVideoCheckRiskCancel)
  356. style:UIAlertActionStyleCancel
  357. handler:^(UIAlertAction *_Nonnull action) {
  358. __strong typeof(weakSelf) strongSelf = weakSelf;
  359. [strongSelf reloadAllView];
  360. }]];
  361. [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:ac animated:YES completion:nil];
  362. }
  363. - (void)adjustScale {
  364. CGFloat scale = 1;
  365. if (Screen_Width > self.imageView.image.size.width) {
  366. scale = 1;
  367. CGFloat scaleHeight = Screen_Height/ self.imageView.image.size.height;
  368. scale = MIN(scale, scaleHeight);
  369. }
  370. else {
  371. scale = Screen_Width/ self.imageView.image.size.width;
  372. CGFloat scaleHeight = Screen_Height/ self.imageView.image.size.height;
  373. scale = MIN(scale, scaleHeight);
  374. }
  375. self.scrollView.containerView.frame = CGRectMake(0, 0,MIN(Screen_Width,self.imageView.image.size.width), self.imageView.image.size.height);
  376. [self.scrollView pictureZoomWithScale:scale];
  377. }
  378. @end