FilePreviewer.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. //
  2. // FilePreviewer.m
  3. // AIIM
  4. //
  5. // Created by qitewei on 2025/5/14.
  6. //
  7. #import "FilePreviewer.h"
  8. #import <AVKit/AVKit.h>
  9. #import <WebKit/WebKit.h>
  10. #import "ZoomableImageView.h"
  11. @interface FilePreviewer()<UIDocumentInteractionControllerDelegate>
  12. @property (nonatomic, strong) UIDocumentInteractionController *documentController;
  13. @property (nonatomic, strong) UIViewController *webPreviewController;
  14. @property (nonatomic, weak) UIViewController *currentParentViewController;
  15. @property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator;
  16. @property (nonatomic, strong) UIImage *showimge;
  17. @end
  18. @implementation FilePreviewer
  19. + (instancetype)shared {
  20. static FilePreviewer *instance = nil;
  21. static dispatch_once_t onceToken;
  22. dispatch_once(&onceToken, ^{
  23. instance = [[FilePreviewer alloc] init];
  24. // 默认图片下载选项
  25. instance.imageDownloadOptions = SDWebImageRetryFailed | SDWebImageHighPriority;
  26. // 注册WebP解码器(如果需要支持WebP格式)
  27. [SDImageCodersManager.sharedManager addCoder:SDImageAWebPCoder.sharedCoder];
  28. });
  29. return instance;
  30. }
  31. #pragma mark - Public Methods
  32. - (void)previewFileWithLocalPath:(NSString *)localPath
  33. remoteURL:(NSURL *)remoteURL
  34. fromViewController:(UIViewController *)parentViewController {
  35. self.currentParentViewController = parentViewController;
  36. // 优先尝试本地路径
  37. if (localPath && [[NSFileManager defaultManager] fileExistsAtPath:localPath]) {
  38. NSLog(@"本地路径");
  39. [self previewFileAtPath:localPath];
  40. }
  41. // 如果本地路径不存在或无效,尝试远程URL
  42. else if (remoteURL) {
  43. // [self showLoadingIndicator];
  44. NSLog(@"尝试远程URL");
  45. [self previewRemoteFileAtURL:remoteURL];
  46. }
  47. else {
  48. [MBProgressHUD showWithText:@"文件加载中"];
  49. // [self showErrorAlert:@"文件不存在或无法访问"];
  50. }
  51. }
  52. #pragma mark - Private Methods - Preview
  53. - (void)previewFileAtPath:(NSString *)filePath {
  54. NSURL *fileURL = [NSURL fileURLWithPath:filePath];
  55. NSString *fileExtension = [[filePath pathExtension] lowercaseString];
  56. if ([self isImageFile:fileExtension]) {
  57. [self previewImageAtPath:filePath];
  58. } else if ([self isVideoFile:fileExtension]) {
  59. [self previewVideoAtURL:fileURL];
  60. } else {
  61. [self previewDocumentAtURL:fileURL];
  62. }
  63. }
  64. - (void)previewRemoteFileAtURL:(NSURL *)url {
  65. if (!url || ![url scheme]) {
  66. // [self showErrorAlert:@"文件加载中"];
  67. [MBProgressHUD showWithText:@"文件加载中"];
  68. return;
  69. }
  70. NSString *fileExtension = [[url pathExtension] lowercaseString];
  71. if ([self isImageFile:fileExtension]) {
  72. [self previewRemoteImageWithURL:url];
  73. } else if ([self isVideoFile:fileExtension]) {
  74. [self previewVideoAtURL:url];
  75. } else {
  76. [self previewWithWebView:url];
  77. }
  78. }
  79. #pragma mark - Private Methods - Image Preview
  80. // 预览本地图片
  81. - (void)previewImageAtPath:(NSString *)imagePath {
  82. NSURL *imageURL = [NSURL fileURLWithPath:imagePath];
  83. // 使用SDWebImage加载本地图片(支持WebP等格式)
  84. [self showImagePreviewWithURL:imageURL];
  85. }
  86. // 预览远程图片
  87. - (void)previewRemoteImageWithURL:(NSURL *)imageURL {
  88. [self showImagePreviewWithURL:imageURL];
  89. }
  90. // 通用图片预览方法
  91. - (void)showImagePreviewWithURL:(NSURL *)imageURL {
  92. UIViewController *previewController = [[UIViewController alloc] init];
  93. previewController.view.backgroundColor = [UIColor blackColor];
  94. // 使用自定义的可缩放图片视图
  95. ZoomableImageView *zoomableView = [[ZoomableImageView alloc] initWithFrame:previewController.view.bounds];
  96. zoomableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  97. // zoomableView.imageView.contentMode = UIViewContentModeScaleAspectFit;
  98. [previewController.view addSubview:zoomableView];
  99. // 添加关闭按钮
  100. UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeSystem];
  101. // [closeButton setBackgroundImage:kImageMake(@"common_close_white") forState:UIControlStateNormal];
  102. [closeButton setBackgroundColor:globalColor(GCTypeDark1)];
  103. [closeButton setImage:kImageMake(@"common_close_white") forState:UIControlStateNormal];
  104. closeButton.frame = CGRectMake(20, 60, 40, 40);
  105. closeButton.layer.cornerRadius = 5;
  106. closeButton.tag = 100; // 用于稍后隐藏/显示
  107. [closeButton addTarget:self action:@selector(dismissPreviewController) forControlEvents:UIControlEventTouchUpInside];
  108. [previewController.view addSubview:closeButton];
  109. // 添加保存 本地按钮
  110. UIButton *saveButton = [UIButton buttonWithType:UIButtonTypeSystem];
  111. [saveButton setBackgroundColor:globalColor(GCTypeDark1)];
  112. [saveButton setImage:kImageMake(@"xiazaibendi") forState:UIControlStateNormal];
  113. CGFloat width = previewController.view.frame.size.width;
  114. saveButton.frame = CGRectMake(width-60, 60, 40, 40);
  115. saveButton.layer.cornerRadius = 5;
  116. saveButton.tag = 101; // 用于稍后隐藏/显示
  117. [saveButton addTarget:self action:@selector(saveImg) forControlEvents:UIControlEventTouchUpInside];
  118. [previewController.view addSubview:saveButton];
  119. // 添加加载指示器
  120. UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
  121. indicator.center = previewController.view.center;
  122. [indicator startAnimating];
  123. [previewController.view addSubview:indicator];
  124. [self presentViewController:previewController];
  125. self.webPreviewController = previewController;
  126. __weak typeof(self) weakSelf = self;
  127. __weak typeof(zoomableView) weakZoomableView = zoomableView;
  128. __weak typeof(indicator) weakIndicator = indicator;
  129. // 使用SDWebImage加载图片
  130. [zoomableView.imageView sd_setImageWithURL:imageURL
  131. placeholderImage:nil
  132. options:self.imageDownloadOptions
  133. progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
  134. // 加载进度回调
  135. dispatch_async(dispatch_get_main_queue(), ^{
  136. // if (expectedSize > 0) {
  137. // CGFloat progress = (CGFloat)receivedSize / (CGFloat)expectedSize;
  138. // weakIndicator.progress = progress;
  139. // }
  140. });
  141. } completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  142. dispatch_async(dispatch_get_main_queue(), ^{
  143. [weakIndicator stopAnimating];
  144. [weakIndicator removeFromSuperview];
  145. if (error) {
  146. [weakSelf showErrorAlert:@"图片加载失败"];
  147. [weakSelf dismissPreviewController];
  148. } else {
  149. [weakZoomableView setImage:image];
  150. [weakZoomableView setNeedsLayout];
  151. [weakZoomableView layoutIfNeeded];
  152. // 添加单击手势隐藏/显示控制按钮
  153. UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:weakSelf action:@selector(toggleControls:)];
  154. [previewController.view addGestureRecognizer:singleTap];
  155. self->_showimge =image;
  156. }
  157. });
  158. }];
  159. }
  160. #pragma mark - Private Methods - Video Preview
  161. - (void)previewVideoAtURL:(NSURL *)videoURL {
  162. NSLog(@"previewVideoAtURL: %@", videoURL);
  163. AVPlayerViewController *playerViewController = [[AVPlayerViewController alloc] init];
  164. AVPlayer *player = [AVPlayer playerWithURL:videoURL];
  165. playerViewController.player = player;
  166. playerViewController.showsPlaybackControls = YES;
  167. [self presentViewController:playerViewController];
  168. // 尝试切换到耳机输出(如果可用)或扬声器(默认)
  169. NSError *error = nil;
  170. AVAudioSession *session = [AVAudioSession sharedInstance];
  171. // 设置音频会话的类别为播放
  172. [session setCategory:AVAudioSessionCategoryPlayback error:&error];
  173. if (error) {
  174. NSLog(@"设置音频会话类别失败: %@", error.localizedDescription);
  175. } else {
  176. // 尝试将输出端口覆盖为扬声器
  177. [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];// 或者AVAudioSessionPortOverrideSpeaker AVAudioSessionPortOverrideNone 保持默认行为
  178. if (error) {
  179. NSLog(@"设置输出端口为扬声器失败: %@", error.localizedDescription);
  180. } else {
  181. NSLog(@"已成功设置为扬声器播放模式");
  182. }
  183. }
  184. // 激活音频会话
  185. [session setActive:YES error:&error];
  186. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
  187. [player play];
  188. });
  189. // __weak AVPlayerViewController *weakPlayerVC = playerViewController;
  190. // [playerViewController.player addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
  191. //
  192. // [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification
  193. // object:playerViewController.player.currentItem
  194. // queue:[NSOperationQueue mainQueue]
  195. // usingBlock:^(NSNotification *note) {
  196. // [weakPlayerVC.player removeObserver:self forKeyPath:@"status"];
  197. // [[NSNotificationCenter defaultCenter] removeObserver:self
  198. // name:AVPlayerItemDidPlayToEndTimeNotification
  199. // object:weakPlayerVC.player.currentItem];
  200. // }];
  201. }
  202. #pragma mark - Private Methods - Document Preview
  203. - (void)previewDocumentAtURL:(NSURL *)documentURL {
  204. self.documentController = [UIDocumentInteractionController interactionControllerWithURL:documentURL];
  205. self.documentController.delegate = self;
  206. if (![self.documentController presentPreviewAnimated:YES]) {
  207. [self previewWithWebView:documentURL];
  208. }
  209. }
  210. #pragma mark - Private Methods - WebView Preview
  211. - (void)previewWithWebView:(NSURL *)url {
  212. UIViewController *webViewController = [[UIViewController alloc] init];
  213. WKWebView *webView = [[WKWebView alloc] initWithFrame:webViewController.view.bounds];
  214. webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  215. [webViewController.view addSubview:webView];
  216. [self addCloseButtonToController:webViewController];
  217. [self addLoadingIndicatorToView:webViewController.view];
  218. NSURLRequest *request = [NSURLRequest requestWithURL:url];
  219. [webView loadRequest:request];
  220. [self presentViewController:webViewController];
  221. self.webPreviewController = webViewController;
  222. }
  223. #pragma mark - UI Helpers
  224. - (void)addCloseButtonToController:(UIViewController *)controller {
  225. UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeSystem];
  226. [closeButton setTitle:@"关闭" forState:UIControlStateNormal];
  227. [closeButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
  228. closeButton.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
  229. closeButton.frame = CGRectMake(20, 40, 60, 30);
  230. closeButton.layer.cornerRadius = 5;
  231. [closeButton addTarget:self action:@selector(dismissPreviewController) forControlEvents:UIControlEventTouchUpInside];
  232. [controller.view addSubview:closeButton];
  233. }
  234. - (void)showLoadingIndicator {
  235. if (!self.loadingIndicator) {
  236. self.loadingIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
  237. self.loadingIndicator.center = self.currentParentViewController.view.center;
  238. [self.currentParentViewController.view addSubview:self.loadingIndicator];
  239. }
  240. [self.loadingIndicator startAnimating];
  241. }
  242. - (void)hideLoadingIndicator {
  243. [self.loadingIndicator stopAnimating];
  244. [self.loadingIndicator removeFromSuperview];
  245. self.loadingIndicator = nil;
  246. }
  247. - (void)addLoadingIndicatorToView:(UIView *)view {
  248. UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
  249. indicator.center = view.center;
  250. [indicator startAnimating];
  251. [view addSubview:indicator];
  252. }
  253. - (void)showErrorAlert:(NSString *)message {
  254. [self hideLoadingIndicator];
  255. UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"错误"
  256. message:message
  257. preferredStyle:UIAlertControllerStyleAlert];
  258. [alert addAction:[UIAlertAction actionWithTitle:@"确定"
  259. style:UIAlertActionStyleDefault
  260. handler:nil]];
  261. [self.currentParentViewController presentViewController:alert animated:YES completion:nil];
  262. }
  263. - (void)presentViewController:(UIViewController *)viewController {
  264. viewController.modalPresentationStyle = UIModalPresentationFullScreen;
  265. [self.currentParentViewController presentViewController:viewController animated:YES completion:nil];
  266. }
  267. - (void)dismissPreviewController {
  268. [self.webPreviewController dismissViewControllerAnimated:YES completion:nil];
  269. self.webPreviewController = nil;
  270. [self hideLoadingIndicator];
  271. }
  272. - (void)saveImg {
  273. if(_showimge){
  274. UIImageWriteToSavedPhotosAlbum(_showimge, self, @selector(image:didFinishSavingWithError:contextInfo:), NULL);
  275. }
  276. }
  277. - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
  278. if (error) {
  279. // 保存失败,输出错误信息
  280. NSLog(@"Error saving image: %@", error);
  281. } else {
  282. // 保存成功
  283. NSLog(@"Image saved successfully!");
  284. [MBProgressHUD showWithText:@"图片已保存到相册"];
  285. }
  286. }
  287. #pragma mark - Gesture Handlers
  288. - (void)handleDoubleTap:(UITapGestureRecognizer *)recognizer {
  289. UIImageView *imageView = (UIImageView *)recognizer.view;
  290. if (imageView.contentMode == UIViewContentModeScaleAspectFit) {
  291. [UIView animateWithDuration:0.3 animations:^{
  292. imageView.contentMode = UIViewContentModeScaleAspectFill;
  293. imageView.transform = CGAffineTransformMakeScale(2.0, 2.0);
  294. }];
  295. } else {
  296. [UIView animateWithDuration:0.3 animations:^{
  297. imageView.contentMode = UIViewContentModeScaleAspectFit;
  298. imageView.transform = CGAffineTransformIdentity;
  299. }];
  300. }
  301. }
  302. // 切换控制按钮显示/隐藏
  303. - (void)toggleControls:(UITapGestureRecognizer *)gesture {
  304. UIViewController *previewController = (UIViewController *)gesture.view.nextResponder;
  305. UIButton *closeButton = [previewController.view viewWithTag:100];
  306. UIButton *saveButton = [previewController.view viewWithTag:101];
  307. [UIView animateWithDuration:0.3 animations:^{
  308. closeButton.alpha = (closeButton.alpha == 0) ? 1 : 0;
  309. saveButton.alpha = (saveButton.alpha == 0) ? 1 : 0;
  310. }];
  311. }
  312. #pragma mark - File Type Detection
  313. - (BOOL)isImageFile:(NSString *)fileExtension {
  314. NSArray *imageExtensions = @[@"jpg", @"jpeg", @"png", @"gif", @"bmp", @"tiff", @"webp"];
  315. return [imageExtensions containsObject:fileExtension];
  316. }
  317. - (BOOL)isVideoFile:(NSString *)fileExtension {
  318. NSArray *videoExtensions = @[@"mp4", @"mov", @"m4v", @"avi", @"mkv", @"flv", @"wmv"];
  319. return [videoExtensions containsObject:fileExtension];
  320. }
  321. #pragma mark - UIDocumentInteractionControllerDelegate
  322. - (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller {
  323. return self.currentParentViewController;
  324. }
  325. - (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller {
  326. self.documentController = nil;
  327. }
  328. @end