import UIKit import PDFKit import VisionKit import PhotosUI final class DocumentsTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { struct Constants { static let reuseIdentifier = String(describing: DocumentsTableViewController.self) static let storyboardName = "Documents" static let rowHeight: CGFloat = 112 static let cellReuseIdentifier = String(describing: DocumentsTableViewCell.self) static let topBottom: CGFloat = 120 static let margin: CGFloat = 64 } static func buildViewController() -> DocumentsTableViewController { let controller = UIStoryboard(name: Constants.storyboardName, bundle: .main).instantiateViewController(withIdentifier: Constants.reuseIdentifier) return controller as! DocumentsTableViewController } @IBOutlet weak var tableView: UITableView! @IBOutlet weak var AllFolderView: AllFolderTableViewCell! @IBOutlet weak var fixedTableSheet: UIView! @IBOutlet weak var searchForFilesView: SearchFilesView! @IBOutlet weak var noFilesLabel: UILabel! private var viewModels: [File] = [] private var searchedViewModel: [File] = [] private var sortType: SortyFileType = .date var options:ImageScannerOptions! var scannedItem = ScannedItem(originalImage: UIImage()) var images: [UIImage] = [] // private let noDocumentsimageView = UIImageView(image: UIImage(named: "box")) private var renameAlertController: UIAlertController? private var renameFileName: String? private var selectedFolder: AppConfigurator.Folder? private var isSearching: Bool = false private var pageViewControllers: [UIViewController] = [] private var scanSession: MultiPageScanSession? @IBOutlet weak var allFolderView_height: NSLayoutConstraint! private var localFileManager: LocalFileManager? override func viewDidLoad() { super.viewDidLoad() let defaults = UserDefaults.standard let decoder = JSONDecoder() if let savedData = defaults.object(forKey: "pdfDocumentObjects") as? Data { if let loadedPDFDocumentObjects = try? decoder.decode([PDFDocumentObject].self, from: savedData) { // loadedPDFDocumentObjects is your array of PDFDocumentObject instances for pdfDocument in loadedPDFDocumentObjects { // if pdfDocument.fileURL == viewModel.fileURL { print("*****count****:\(pdfDocument.session.scannedItems.count)") // } } } } scanSession = MultiPageScanSession() options = ImageScannerOptions() navigationItem.title = .fileManager.localized let settingsButton = UIBarButtonItem(image: UIImage(named: "settings"), style: .done, target: self, action: #selector(openSettings)) navigationItem.rightBarButtonItem = settingsButton tableView.register(UINib(nibName: "DocumentsTableViewCell", bundle: nil), forCellReuseIdentifier: "DocumentsTableViewCell") tableView.tableFooterView = UIView() tableView.delegate = self tableView.dataSource = self tableView.reloadData() let savedFolders = AppConfigurator().getFolders() for item in savedFolders { if item.isSelected == true { selectedFolder = item } } if let folder = selectedFolder { AllFolderView?.set(selectedFolder: folder, delegate: self) } searchForFilesView.delegate = self localFileManager = LocalFileManager() tabBarController?.delegate = UIApplication.shared.delegate as? UITabBarControllerDelegate NotificationCenter.default.addObserver(self, selector: #selector(middleButtonTapped(_:)), name: NSNotification.Name.MiddleButtonTapped, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(fetchViewModels), name: NSNotification.Name.ReloadViewModels, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(rightButtonTapped(_:)), name: NSNotification.Name.rightButtonTapped, object: nil) if UserDefaults.standard.startsAppWithCamera { openCamera() } fixedTableSheet.layer.cornerRadius = 30 fixedTableSheet.layer.shadowColor = UIColor.gray.cgColor fixedTableSheet.layer.shadowOffset = CGSize(width: 0, height: 2) // Shadow position fixedTableSheet.layer.shadowOpacity = 0.7 // Shadow opacity fixedTableSheet.layer.shadowRadius = 4.0 fixedTableSheet.backgroundColor = .white // or any non-clear color fixedTableSheet.clipsToBounds = false noFilesLabel.set(text: .noFilesToShow.localized, color: .mainText, font: .medium(22)) noFilesLabel.isHidden = true fetchViewModels() navigationController?.navigationBar.prefersLargeTitles = false } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) scanSession?.removeAll() self.tabBarController?.tabBar.isHidden = false fetchViewModels() } deinit { NotificationCenter.default.removeObserver(self) } @objc private func fetchViewModels() { if let folder = selectedFolder { guard let directoryURL = localFileManager?.getFolderUrl(folder: folder), let viewModels = localFileManager?.filesForDirectory(directoryURL, sortType: sortType) else { return } self.viewModels = viewModels self.searchedViewModel = viewModels AllFolderView?.set(selectedFolder: folder, delegate: self) if viewModels.isEmpty { noFilesLabel.isHidden = false } else { noFilesLabel.isHidden = true } tableView.reloadData() } } @objc private func openSettings() { if #available(iOS 16.0, *) { let liveScan = DataScannerViewController(recognizedDataTypes: [.text()], qualityLevel: .balanced, isHighlightingEnabled: true) liveScan.delegate = self try? liveScan.startScanning() self.present(liveScan, animated: true) } else { let settings = SettingViewController() self.navigationController?.pushViewController(settings, animated: false) } } func extractImage(from pdfDocument: PDFDocument, at pageIndex: Int) -> ScannedItem? { guard pageIndex < pdfDocument.pageCount, let page = pdfDocument.page(at: pageIndex) else { return nil } var pageBounds = page.bounds(for: .mediaBox) // Calculate the scale factor to downsample the image let maxDimension: CGFloat = 4096.0 // Adjust this value as needed let scaleFactor = min(maxDimension / pageBounds.width, maxDimension / pageBounds.height) // Downsample the image pageBounds.size.width *= scaleFactor pageBounds.size.height *= scaleFactor let renderer = UIGraphicsImageRenderer(size: pageBounds.size) let image = renderer.image { (context) in UIColor.white.set() context.fill(pageBounds) context.cgContext.translateBy(x: 0.0, y: pageBounds.size.height) context.cgContext.scaleBy(x: 1.0, y: -1.0) context.cgContext.drawPDFPage(page.pageRef!) } self.scannedItem = ScannedItem(originalImage: image,renderImage: image) return scannedItem } // MARK: - Table view data source func numberOfSections(in tableView: UITableView) -> Int { // #warning Incomplete implementation, return the number of sections return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows return isSearching ? searchedViewModel.count :viewModels.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: Constants.cellReuseIdentifier, for: indexPath) as? DocumentsTableViewCell else { return UITableViewCell() } // Configure the cell... let viewModel = isSearching ? searchedViewModel[indexPath.row ] : viewModels[indexPath.row] cell.configure(with: viewModel, image: localFileManager?.getThumbnail(for: viewModel.fileURL)) return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return Constants.rowHeight } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return UIView() } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { 20 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let viewModel = isSearching ? searchedViewModel[indexPath.row] : viewModels[indexPath.row] var session: MultiPageScanSession? let defaults = UserDefaults.standard let decoder = JSONDecoder() if let savedData = defaults.object(forKey: "pdfDocumentObjects") as? Data { if let loadedPDFDocumentObjects = try? decoder.decode([PDFDocumentObject].self, from: savedData) { // loadedPDFDocumentObjects is your array of PDFDocumentObject instances for pdfDocument in loadedPDFDocumentObjects { if pdfDocument.name == viewModel.displayName { session = pdfDocument.session } } } } let controller = DocumentPreviewViewController.buildViewController() controller.file = viewModel controller.session = session controller.selectedFolder = self.selectedFolder controller.hidesBottomBarWhenPushed = true navigationController?.pushViewController(controller, animated: true) } func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { return .none } func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { // Trash action let trash = UIContextualAction(style: .destructive, title: .delete.localized) { [weak self] (_, _, completionHandler) in self?.deleteFile(at: indexPath) completionHandler(true) } trash.backgroundColor = .systemRed trash.image = UIImage(systemName: "trash")?.tint(with: .white) // Rename action let rename = UIContextualAction(style: .normal, title: .rename.localized) { [weak self] (_, _, completionHandler) in self?.renameFile(at: indexPath) completionHandler(true) } rename.backgroundColor = UIColor.black.withAlphaComponent(0.5) rename.image = UIImage(systemName: "square.and.pencil")?.tint(with: .white) let move = UIContextualAction(style: .normal, title: .move.localized) { [weak self] (_, _, completionHandler) in if let viewModel = self?.isSearching == true ? self?.searchedViewModel[indexPath.row] : self?.viewModels[indexPath.row] { self?.moveto(file: viewModel) } completionHandler(true) } let configuration = UISwipeActionsConfiguration(actions: [trash, rename, move]) configuration.performsFirstActionWithFullSwipe = true return configuration } @objc private func middleButtonTapped(_ notification: NSNotification) { openCamera() } @objc private func rightButtonTapped(_ notification: NSNotification) { openGallery() } private func openCamera() { let scannerOptions = ImageScannerOptions(scanMultipleItems: true, allowAutoScan: false, allowTapToFocus: false, defaultColorRenderOption:.color) let scannerViewController = ScannerViewController(scanSession: nil, options: scannerOptions) scannerViewController.delegate = self scannerViewController.selectedFolder = self.selectedFolder scannerViewController.hidesBottomBarWhenPushed = true self.navigationController?.pushViewController(scannerViewController, animated: false) } private func openGallery() { var configuration = PHPickerConfiguration() configuration.selectionLimit = 0 // 0 means no limit configuration.filter = .images // Only show images let picker = PHPickerViewController(configuration: configuration) picker.delegate = self picker.modalPresentationStyle = .fullScreen present(picker, animated: true, completion: nil) } private func renameFile(at indexPath: IndexPath) { let viewModel = isSearching ? searchedViewModel[indexPath.row] : viewModels[indexPath.row] renameAlertController = UIAlertController(title: .renameDocument.localized, message: nil, preferredStyle: .alert) renameAlertController!.addTextField { [weak self] textField in guard let self = self else { return } textField.placeholder = viewModel.displayName textField.text = viewModel.displayName textField.clearButtonMode = .always self.renameFileName = viewModel.displayName textField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged) } let continueAction = UIAlertAction(title: .rename.localized, style: .default) { [weak self] _ in guard let self = self else { return } guard let textFields = self.renameAlertController?.textFields else { return } if let newName = textFields.first?.text, newName != viewModel.displayName { do { try self.localFileManager?.renameFile(at: viewModel.fileURL, withName: newName) self.fetchViewModels() } catch { return } } } renameAlertController!.addAction(continueAction) renameAlertController!.addAction(UIAlertAction(title: .cancel.localized, style: .cancel, handler: nil)) renameAlertController?.actions.first?.isEnabled = false present(renameAlertController!, animated: true) } func moveto(file: File) { let foldersViewController = FoldersViewController() foldersViewController.selectedFolder = self.selectedFolder foldersViewController.movedFile = file foldersViewController.delegate = self self.present(foldersViewController, animated: true) } private func deleteFile(at indexPath: IndexPath) { let viewModel = isSearching ? searchedViewModel[indexPath.row] : viewModels[indexPath.row] if UserDefaults.standard.askOnSwipeDelete { let alertController = UIAlertController(title: Bundle.appName, message: .deleteDocumentMsg.localized, preferredStyle: .alert) let okAction = UIAlertAction(title: .yesDelete.localized, style: .destructive, handler: { [weak self] action in guard let self = self else { return } self.delete(file: viewModel.fileURL, at: indexPath) }) alertController.addAction(okAction) alertController.addAction(UIAlertAction(title: .cancel.localized, style: .cancel, handler: nil)) alertController.popoverPresentationController?.sourceView = tableView.cellForRow(at: indexPath) present(alertController, animated: true) } else { delete(file: viewModel.fileURL, at: indexPath) } } private func delete(file fileURL: URL, at indexPath: IndexPath) { do { try self.localFileManager?.filesManager.removeItem(at: fileURL) try self.localFileManager?.removeThumbnail(for: fileURL) if isSearching { self.searchedViewModel.remove(at: indexPath.row) } else { self.viewModels.remove(at: indexPath.row) } self.tableView.deleteRows(at: [indexPath], with: .fade) self.fetchViewModels() } catch { delete(file: fileURL) self.fetchViewModels() return } } private func delete(file fileURL: URL) { do { try self.localFileManager?.filesManager.removeItem(at: fileURL) try self.localFileManager?.removeThumbnail(for: fileURL) self.tableView.reloadData() } catch { return } } } /// Tells the delegate that the user picked a still image. extension DocumentsTableViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { picker.dismiss(animated: true) { [weak self] in guard let self = self else { return } guard let image = info[UserDefaults.standard.isPhotoEditigOn ? UIImagePickerController.InfoKey.editedImage : UIImagePickerController.InfoKey.originalImage] as? UIImage else { return } if let folder = selectedFolder { PDFManager.createPDFDocument(from: image, localFileManager: self.localFileManager, folder: folder) self.fetchViewModels() self.tabBarController?.selectedIndex = 0 } } } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true) { [weak self] in self?.tabBarController?.selectedIndex = 0 } } } extension DocumentsTableViewController: ImageScannerControllerDelegate { func imageScannerController(_ scanner: ImageScannerController, didFinishWithSession session: MultiPageScanSession) { if let folder = selectedFolder { PDFManager.savePDF(session: session, folder: folder, fileManager: localFileManager, name: "") { scanner.dismiss(animated: true) { self.fetchViewModels() } } } } func imageScannerControllerDidCancel(_ scanner: ImageScannerController) { scanner.dismiss(animated: true) } func imageScannerController(_ scanner: ImageScannerController, didFailWithError error: Error) { scanner.dismiss(animated: true) } @objc func textFieldDidChange(_ textField: UITextField) { guard let text = textField.text, let renameFileName = renameFileName, renameFileName != text, !text.trimmingCharacters(in: .whitespaces).isEmpty else { renameAlertController?.actions.first?.isEnabled = false return } renameAlertController?.actions.first?.isEnabled = true } } extension DocumentsTableViewController: ScannerViewControllerDelegate { func scannerViewController(_ scannerViewController: ScannerViewController, reviewItems inSession: MultiPageScanSession) { let multipageScanViewController = MultiPageScanSessionViewController(scanSession: inSession) multipageScanViewController.delegate = self self.navigationController?.pushViewController(multipageScanViewController, animated: true) } func scannerViewController(_ scannerViewController: ScannerViewController, didFail withError: Error) { scannerViewController.dismiss(animated: true) } func scannerViewControllerDidCancel(_ scannerViewController: ScannerViewController) { scannerViewController.dismiss(animated: true) } } extension DocumentsTableViewController: AllFolderTableViewCellDelegate { func addFolderTapped() { alert(confirm: .addFolder, destructive: .cancel, message: .addFolderMessage){ folderName in let newFolder = AppConfigurator.Folder(name: folderName, savedName: folderName.replacingOccurrences(of: " ", with: "_"), isSelected: false) var currentFolders = AppConfigurator().getFolders() currentFolders.append(newFolder) if let encoded = try? JSONEncoder().encode(currentFolders) { UserDefaults.standard.set(encoded, forKey: "folders") self.AllFolderView?.refresh() self.dismiss(animated: true) } } } func cellTapped(folder: AppConfigurator.Folder) { selectedFolder = folder fetchViewModels() } } extension DocumentsTableViewController: SearchFilesViewDelegate { func searchfor(text: String) { if text.count == 0 { isSearching = false UIView.animate(withDuration: 0.5) { self.allFolderView_height.constant = 240 self.AllFolderView.isHidden = false self.noFilesLabel.isHidden = true } } else { isSearching = true self.searchedViewModel = self.viewModels.filter { $0.displayName.lowercased().contains(text.lowercased()) } if searchedViewModel.isEmpty { noFilesLabel.isHidden = false } else { noFilesLabel.isHidden = true } } tableView.reloadData() } } extension DocumentsTableViewController: PHPickerViewControllerDelegate { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { images.removeAll() picker.dismiss(animated: true) { let dispatchGroup = DispatchGroup() for result in results { dispatchGroup.enter() // Enter the group before each task result.itemProvider.loadObject(ofClass: UIImage.self) { (object, error) in defer { dispatchGroup.leave() } // Leave the group after each task, regardless of whether it was successful if let error = error { print("Error loading image: \(error)") } else if let image = object as? UIImage { DispatchQueue.main.async { self.images.append(image) } } } } dispatchGroup.notify(queue: .main) { if !self.images.isEmpty { if let folder = self.selectedFolder { PDFManager.createMultiImagesPDFDocument(from: self.images, localFileManager: self.localFileManager, folder: folder) } } self.fetchViewModels() self.tabBarController?.selectedIndex = 0 } } } } extension DocumentsTableViewController: MultiPageScanSessionViewControllerDelegate { public func multiPageScanSessionViewController(_ multiPageScanSessionViewController: MultiPageScanSessionViewController, finished session: MultiPageScanSession) { if let folder = selectedFolder { PDFManager.savePDF(session: session, folder: folder, fileManager: localFileManager, name: "") { self.fetchViewModels() self.navigationController?.popViewController(animated: true) } } } } extension DocumentsTableViewController: UIPageViewControllerDataSource { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let index = pageViewControllers.firstIndex(of: viewController), index > 0 else { return nil } return pageViewControllers[index - 1] } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let index = pageViewControllers.firstIndex(of: viewController), index < pageViewControllers.count - 1 else { return nil } return pageViewControllers[index + 1] } } @available(iOS 16.0, *) extension DocumentsTableViewController: DataScannerViewControllerDelegate { func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) { switch item { case .text(let text): print(text.transcript) let alertController = UIAlertController(title: Bundle.appName, message: text.transcript, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: .cancel.localized, style: .cancel, handler: nil)) self.dismiss(animated: true) { self.present(alertController, animated: true) } default: break } } } extension DocumentsTableViewController: FoldersViewControllerDelegate { func move(file: File, from: [AppConfigurator.Folder], to: AppConfigurator.Folder) { delete(file: file.fileURL) guard let saveUrl = localFileManager?.getFolderUrl(folder: to) else { return } let urlPDFtoSaveInAllDoc = saveUrl.appendingPathComponent(file.displayName) do { try file.pdfDocument?.dataRepresentation()?.write(to: urlPDFtoSaveInAllDoc) self.selectedFolder = to fetchViewModels() for (index,item) in self.AllFolderView.folders.enumerated() { if item.savedName == selectedFolder?.savedName { self.AllFolderView.collectionView.scrollToItem(at: IndexPath(row: index, section: 0), at: .centeredHorizontally, animated: true) } } } catch { print(error.localizedDescription) // handle error } } }