// // FilePreviewer.m // AIIM // // Created by qitewei on 2025/5/14. // #import "FilePreviewer.h" #import #import #import "ZoomableImageView.h" @interface FilePreviewer() @property (nonatomic, strong) UIDocumentInteractionController *documentController; @property (nonatomic, strong) UIViewController *webPreviewController; @property (nonatomic, weak) UIViewController *currentParentViewController; @property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator; @property (nonatomic, strong) UIImage *showimge; @end @implementation FilePreviewer + (instancetype)shared { static FilePreviewer *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[FilePreviewer alloc] init]; // 默认图片下载选项 instance.imageDownloadOptions = SDWebImageRetryFailed | SDWebImageHighPriority; // 注册WebP解码器(如果需要支持WebP格式) [SDImageCodersManager.sharedManager addCoder:SDImageAWebPCoder.sharedCoder]; }); return instance; } #pragma mark - Public Methods - (void)previewFileWithLocalPath:(NSString *)localPath remoteURL:(NSURL *)remoteURL fromViewController:(UIViewController *)parentViewController { self.currentParentViewController = parentViewController; // 优先尝试本地路径 if (localPath && [[NSFileManager defaultManager] fileExistsAtPath:localPath]) { NSLog(@"本地路径"); [self previewFileAtPath:localPath]; } // 如果本地路径不存在或无效,尝试远程URL else if (remoteURL) { // [self showLoadingIndicator]; NSLog(@"尝试远程URL"); [self previewRemoteFileAtURL:remoteURL]; } else { [MBProgressHUD showWithText:@"文件加载中"]; // [self showErrorAlert:@"文件不存在或无法访问"]; } } #pragma mark - Private Methods - Preview - (void)previewFileAtPath:(NSString *)filePath { NSURL *fileURL = [NSURL fileURLWithPath:filePath]; NSString *fileExtension = [[filePath pathExtension] lowercaseString]; if ([self isImageFile:fileExtension]) { [self previewImageAtPath:filePath]; } else if ([self isVideoFile:fileExtension]) { [self previewVideoAtURL:fileURL]; } else { [self previewDocumentAtURL:fileURL]; } } - (void)previewRemoteFileAtURL:(NSURL *)url { if (!url || ![url scheme]) { // [self showErrorAlert:@"文件加载中"]; [MBProgressHUD showWithText:@"文件加载中"]; return; } NSString *fileExtension = [[url pathExtension] lowercaseString]; if ([self isImageFile:fileExtension]) { [self previewRemoteImageWithURL:url]; } else if ([self isVideoFile:fileExtension]) { [self previewVideoAtURL:url]; } else { [self previewWithWebView:url]; } } #pragma mark - Private Methods - Image Preview // 预览本地图片 - (void)previewImageAtPath:(NSString *)imagePath { NSURL *imageURL = [NSURL fileURLWithPath:imagePath]; // 使用SDWebImage加载本地图片(支持WebP等格式) [self showImagePreviewWithURL:imageURL]; } // 预览远程图片 - (void)previewRemoteImageWithURL:(NSURL *)imageURL { [self showImagePreviewWithURL:imageURL]; } // 通用图片预览方法 - (void)showImagePreviewWithURL:(NSURL *)imageURL { UIViewController *previewController = [[UIViewController alloc] init]; previewController.view.backgroundColor = [UIColor blackColor]; // 使用自定义的可缩放图片视图 ZoomableImageView *zoomableView = [[ZoomableImageView alloc] initWithFrame:previewController.view.bounds]; zoomableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; // zoomableView.imageView.contentMode = UIViewContentModeScaleAspectFit; [previewController.view addSubview:zoomableView]; // 添加关闭按钮 UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeSystem]; // [closeButton setBackgroundImage:kImageMake(@"common_close_white") forState:UIControlStateNormal]; [closeButton setBackgroundColor:globalColor(GCTypeDark1)]; [closeButton setImage:kImageMake(@"common_close_white") forState:UIControlStateNormal]; closeButton.frame = CGRectMake(20, 60, 40, 40); closeButton.layer.cornerRadius = 5; closeButton.tag = 100; // 用于稍后隐藏/显示 [closeButton addTarget:self action:@selector(dismissPreviewController) forControlEvents:UIControlEventTouchUpInside]; [previewController.view addSubview:closeButton]; // 添加保存 本地按钮 UIButton *saveButton = [UIButton buttonWithType:UIButtonTypeSystem]; [saveButton setBackgroundColor:globalColor(GCTypeDark1)]; [saveButton setImage:kImageMake(@"xiazaibendi") forState:UIControlStateNormal]; CGFloat width = previewController.view.frame.size.width; saveButton.frame = CGRectMake(width-60, 60, 40, 40); saveButton.layer.cornerRadius = 5; saveButton.tag = 101; // 用于稍后隐藏/显示 [saveButton addTarget:self action:@selector(saveImg) forControlEvents:UIControlEventTouchUpInside]; [previewController.view addSubview:saveButton]; // 添加加载指示器 UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge]; indicator.center = previewController.view.center; [indicator startAnimating]; [previewController.view addSubview:indicator]; [self presentViewController:previewController]; self.webPreviewController = previewController; __weak typeof(self) weakSelf = self; __weak typeof(zoomableView) weakZoomableView = zoomableView; __weak typeof(indicator) weakIndicator = indicator; // 使用SDWebImage加载图片 [zoomableView.imageView sd_setImageWithURL:imageURL placeholderImage:nil options:self.imageDownloadOptions progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { // 加载进度回调 dispatch_async(dispatch_get_main_queue(), ^{ // if (expectedSize > 0) { // CGFloat progress = (CGFloat)receivedSize / (CGFloat)expectedSize; // weakIndicator.progress = progress; // } }); } completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { dispatch_async(dispatch_get_main_queue(), ^{ [weakIndicator stopAnimating]; [weakIndicator removeFromSuperview]; if (error) { [weakSelf showErrorAlert:@"图片加载失败"]; [weakSelf dismissPreviewController]; } else { [weakZoomableView setImage:image]; [weakZoomableView setNeedsLayout]; [weakZoomableView layoutIfNeeded]; // 添加单击手势隐藏/显示控制按钮 UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:weakSelf action:@selector(toggleControls:)]; [previewController.view addGestureRecognizer:singleTap]; self->_showimge =image; } }); }]; } #pragma mark - Private Methods - Video Preview - (void)previewVideoAtURL:(NSURL *)videoURL { NSLog(@"previewVideoAtURL: %@", videoURL); AVPlayerViewController *playerViewController = [[AVPlayerViewController alloc] init]; AVPlayer *player = [AVPlayer playerWithURL:videoURL]; playerViewController.player = player; playerViewController.showsPlaybackControls = YES; [self presentViewController:playerViewController]; // 尝试切换到耳机输出(如果可用)或扬声器(默认) NSError *error = nil; AVAudioSession *session = [AVAudioSession sharedInstance]; // 设置音频会话的类别为播放 [session setCategory:AVAudioSessionCategoryPlayback error:&error]; if (error) { NSLog(@"设置音频会话类别失败: %@", error.localizedDescription); } else { // 尝试将输出端口覆盖为扬声器 [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];// 或者AVAudioSessionPortOverrideSpeaker AVAudioSessionPortOverrideNone 保持默认行为 if (error) { NSLog(@"设置输出端口为扬声器失败: %@", error.localizedDescription); } else { NSLog(@"已成功设置为扬声器播放模式"); } } // 激活音频会话 [session setActive:YES error:&error]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ [player play]; }); // __weak AVPlayerViewController *weakPlayerVC = playerViewController; // [playerViewController.player addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil]; // // [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification // object:playerViewController.player.currentItem // queue:[NSOperationQueue mainQueue] // usingBlock:^(NSNotification *note) { // [weakPlayerVC.player removeObserver:self forKeyPath:@"status"]; // [[NSNotificationCenter defaultCenter] removeObserver:self // name:AVPlayerItemDidPlayToEndTimeNotification // object:weakPlayerVC.player.currentItem]; // }]; } #pragma mark - Private Methods - Document Preview - (void)previewDocumentAtURL:(NSURL *)documentURL { self.documentController = [UIDocumentInteractionController interactionControllerWithURL:documentURL]; self.documentController.delegate = self; if (![self.documentController presentPreviewAnimated:YES]) { [self previewWithWebView:documentURL]; } } #pragma mark - Private Methods - WebView Preview - (void)previewWithWebView:(NSURL *)url { UIViewController *webViewController = [[UIViewController alloc] init]; WKWebView *webView = [[WKWebView alloc] initWithFrame:webViewController.view.bounds]; webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [webViewController.view addSubview:webView]; [self addCloseButtonToController:webViewController]; [self addLoadingIndicatorToView:webViewController.view]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; [webView loadRequest:request]; [self presentViewController:webViewController]; self.webPreviewController = webViewController; } #pragma mark - UI Helpers - (void)addCloseButtonToController:(UIViewController *)controller { UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeSystem]; [closeButton setTitle:@"关闭" forState:UIControlStateNormal]; [closeButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; closeButton.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5]; closeButton.frame = CGRectMake(20, 40, 60, 30); closeButton.layer.cornerRadius = 5; [closeButton addTarget:self action:@selector(dismissPreviewController) forControlEvents:UIControlEventTouchUpInside]; [controller.view addSubview:closeButton]; } - (void)showLoadingIndicator { if (!self.loadingIndicator) { self.loadingIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge]; self.loadingIndicator.center = self.currentParentViewController.view.center; [self.currentParentViewController.view addSubview:self.loadingIndicator]; } [self.loadingIndicator startAnimating]; } - (void)hideLoadingIndicator { [self.loadingIndicator stopAnimating]; [self.loadingIndicator removeFromSuperview]; self.loadingIndicator = nil; } - (void)addLoadingIndicatorToView:(UIView *)view { UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; indicator.center = view.center; [indicator startAnimating]; [view addSubview:indicator]; } - (void)showErrorAlert:(NSString *)message { [self hideLoadingIndicator]; UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"错误" message:message preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]]; [self.currentParentViewController presentViewController:alert animated:YES completion:nil]; } - (void)presentViewController:(UIViewController *)viewController { viewController.modalPresentationStyle = UIModalPresentationFullScreen; [self.currentParentViewController presentViewController:viewController animated:YES completion:nil]; } - (void)dismissPreviewController { [self.webPreviewController dismissViewControllerAnimated:YES completion:nil]; self.webPreviewController = nil; [self hideLoadingIndicator]; } - (void)saveImg { if(_showimge){ UIImageWriteToSavedPhotosAlbum(_showimge, self, @selector(image:didFinishSavingWithError:contextInfo:), NULL); } } - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { if (error) { // 保存失败,输出错误信息 NSLog(@"Error saving image: %@", error); } else { // 保存成功 NSLog(@"Image saved successfully!"); [MBProgressHUD showWithText:@"图片已保存到相册"]; } } #pragma mark - Gesture Handlers - (void)handleDoubleTap:(UITapGestureRecognizer *)recognizer { UIImageView *imageView = (UIImageView *)recognizer.view; if (imageView.contentMode == UIViewContentModeScaleAspectFit) { [UIView animateWithDuration:0.3 animations:^{ imageView.contentMode = UIViewContentModeScaleAspectFill; imageView.transform = CGAffineTransformMakeScale(2.0, 2.0); }]; } else { [UIView animateWithDuration:0.3 animations:^{ imageView.contentMode = UIViewContentModeScaleAspectFit; imageView.transform = CGAffineTransformIdentity; }]; } } // 切换控制按钮显示/隐藏 - (void)toggleControls:(UITapGestureRecognizer *)gesture { UIViewController *previewController = (UIViewController *)gesture.view.nextResponder; UIButton *closeButton = [previewController.view viewWithTag:100]; UIButton *saveButton = [previewController.view viewWithTag:101]; [UIView animateWithDuration:0.3 animations:^{ closeButton.alpha = (closeButton.alpha == 0) ? 1 : 0; saveButton.alpha = (saveButton.alpha == 0) ? 1 : 0; }]; } #pragma mark - File Type Detection - (BOOL)isImageFile:(NSString *)fileExtension { NSArray *imageExtensions = @[@"jpg", @"jpeg", @"png", @"gif", @"bmp", @"tiff", @"webp"]; return [imageExtensions containsObject:fileExtension]; } - (BOOL)isVideoFile:(NSString *)fileExtension { NSArray *videoExtensions = @[@"mp4", @"mov", @"m4v", @"avi", @"mkv", @"flv", @"wmv"]; return [videoExtensions containsObject:fileExtension]; } #pragma mark - UIDocumentInteractionControllerDelegate - (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller { return self.currentParentViewController; } - (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller { self.documentController = nil; } @end