0

Download, Store and View PDF in Swift

Giới thiệu

Xin chào cả nhà, hôm nay mình xin phép được trình bày về cách download PDF file, lưu trữ và hiển thị chúng. Để thực hiện được mình sẽ chia thành 3 chủ đề:

  • DownloadTask
  • File Management
  • PDF View

DownloadTask

Để download file từ một URL, chúng ta cần sử dụng downloadTask. ViewController trong ví dụ dưới đây cần phải conform tới URLSessionDownloadDelegate:

import UIKit

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func downloadButtonPressed(_ sender: Any) {
        guard let url = URL(string: "https://www.tutorialspoint.com/swift/swift_tutorial.pdf") else { return }
        
        let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
        
        let downloadTask = urlSession.downloadTask(with: url)
        downloadTask.resume()
    }
}


extension ViewController:  URLSessionDownloadDelegate {
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        print("downloadLocation:", location)
    }
}

Để xem vị trí của tệp đã tải xuống thì bạn cần kiểm tra trong console bằng cách printed ra đường dẫn của nó. Sau khi mình nhấn tải xuống thì tập tin tải xuống tồn tại chưa đến 1 giây và sau đó bị hệ thống tiêu diệt :v. Hành vi này giống nhau trên cả máy giả lập và vật lý.

File Management

Trong mỗi ứng dụng được tạo trong iOS đều có mỗi sandbox riêng. Nó sẽ có 3 thành phần chính mà một iOS developers nên biết: Bundel Container, Data Container và iCloud Container. Ở đây mình chỉ quan tâm về Data Container để tập trung vào nhiệm vụ đó là - download PDF.

Data container là nơi mà code của mình có thể thao tác với các file được downloaded từ internet sau khi được biên dịch. Dưới đây mình sẽ liệt kê các tính năng quan trọng:

  • File trong Library và tmp sẽ được xoá tự động bởi iOS
  • iTunes sẽ backup tất cả files trong Data Container ngoại trừ Caches, tmp và files chỉ định .isExcludedFromBackup = true. Trong quá trình review nếu Apple mà tìm thấy những file không cần thiết được lưu lại trong iTunes nó sẽ bị rejected.
  • Documents là vị trí lưu trữ các tập tin tải về.

Do đó bước tiếp theo là sao chép file từ tmp qua Document. Mình sẽ làm các bước như sau:

  1. Extracting the original pdf name
  2. Tạo một Url trong Documents.
  3. Xoá file với tên guống để tránh lỗi Copy Error:
“CFNetworkDownload_mdrFNb.tmp” couldn’t be copied to “Documents” because an item with the same name already exists.
  1. Copying nó tới Document
extension ViewController:  URLSessionDownloadDelegate {
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        print("downloadLocation:", location)
        // create destination URL with the original pdf name
        guard let url = downloadTask.originalRequest?.url else { return }
        let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let destinationURL = documentsPath.appendingPathComponent(url.lastPathComponent)
        // delete original copy
        try? FileManager.default.removeItem(at: destinationURL)
        // copy from temp to Document
        do {
            try FileManager.default.copyItem(at: location, to: destinationURL)
            self.pdfURL = destinationURL
        } catch let error {
            print("Copy Error: \(error.localizedDescription)")
        }
    }
}

PDFView

Đến đây thì mình đã lưu thành công file PDF đã tải xuống vào vị trí thích hợp để người dùng có thể truy cập dễ dàng. Đến lúc này để xem nó với PDFView thì mình sẽ dùng PDFKit - đây là 1 thứ viện tiện lợi được Apple cung cấp kể từ khi phát hành iOS 11

@IBAction func openPDFButtonPressed(_ sender: Any) {
    let pdfViewController = PDFViewController()
    pdfViewController.pdfURL = self.pdfURL
    present(pdfViewController, animated: false, completion: nil)
}
import UIKit
import PDFKit

class PDFViewController: UIViewController {
    
    var pdfView = PDFView()
    var pdfURL: URL!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(pdfView)
        
        if let document = PDFDocument(url: pdfURL) {
            pdfView.document = document
        }
        
        DispatchQueue.main.asyncAfter(deadline: .now()+3) {
            self.dismiss(animated: true, completion: nil)
        }
    }
    
    override func viewDidLayoutSubviews() {
        pdfView.frame = view.frame
    }
}

Woa !!! Như vậy chúng ta đã download và opened file PDF successfully. Tuy nhiên size này đang chưa đúng. Với việc chỉnh sửa size cũng như thêm 3 chức năng cho app nữa là: Paging, Outline và Thumbnails.

Tổng kết

Vậy là qua bài này thì mình đã giới thiệu tới các bạn về cách download, lưu trữ và open file PDF trên iOS app. Cảm ơn mọi người đã quan tâm và theo dõi ! Hẹn gặp lại mọi người vào tháng sau

Tài liệu tham khảo


All rights reserved

Bình luận

Đăng nhập để bình luận
Avatar

Em chào anh ạ! Em đang học lập trình, cấu trúc code nhiều khi em vẫn không hiểu lắm. Hiện em đang muốn tải ảnh về từ webview ios. Em có tìm trên mạng đoạn code và chèn đoạn code vào ví dụ của mình. Như sau ạ: import UIKit import WebKit import Network

class ViewController: UIViewController , WKNavigationDelegate, WKDownloadDelegate{

@IBOutlet weak var webview: WKWebView!
override func viewDidLoad() {
    super.viewDidLoad()
    
    monitorNetwork()
}
func monitorNetwork(){
    let monitor = NWPathMonitor()
    monitor.pathUpdateHandler = {
        path in
        if path.status == .satisfied{
            DispatchQueue.main.async {
                self.webview.navigationDelegate = self
                let urlString = URL(string: "http://subdomain1.mywebsite.com.vn")
                let request = URLRequest(url: urlString!)
                self.webview.load(request)
            }
        }else{
            DispatchQueue.main.async {
                self.Alert(Message: "Thiết bị không kết nối mạng")
            }
            
        }
        
    }
    let queue = DispatchQueue(label: "Network")
    monitor.start(queue: queue)
    
}

func Alert (Message: String){
       
    let alert = UIAlertController(title: "Thông báo", message: Message, preferredStyle: UIAlertController.Style.alert)
    alert.addAction(UIAlertAction(title: "Đồng ý", style: UIAlertAction.Style.default, handler: nil))
       self.present(alert, animated: true, completion: nil)
    
       
   } 

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let url = navigationAction.request.url, navigationAction.navigationType == .linkActivated {
        
        if url.absoluteString.hasSuffix(".pdf") || url.absoluteString.hasSuffix(".doc") ||
            url.absoluteString.hasSuffix(".docx") || url.absoluteString.hasSuffix(".xls") ||
            url.absoluteString.hasSuffix(".xlsx") || url.absoluteString.hasSuffix(".jpg") || url.absoluteString.hasSuffix(".jpeg") || url.absoluteString.hasSuffix(".png") || url.absoluteString.hasSuffix(".ppt") || url.absoluteString.hasSuffix(".pptx") {
            
           
           // let fileURL : String = url.absoluteString;
            
          
          
            let downloadTask = URLSession.shared.downloadTask(with: url) {
                urlOrNil, responseOrNil, errorOrNil in
               // check for and handle errors:
                // * errorOrNil should be nil
                // * responseOrNil should be an HTTPURLResponse with statusCode in 200..<299
                
                guard let fileURL = urlOrNil else { return }
                do {
                    let documentsURL = try
                        FileManager.default.url(for: .documentDirectory,
                                                in: .userDomainMask,
                                                appropriateFor: nil,
                                                create: false)
                    let savedURL = documentsURL.appendingPathComponent(fileURL.lastPathComponent)
                    try FileManager.default.moveItem(at: fileURL, to: savedURL)
                } catch {
                    print ("file error: \(error)")
                }
            }
            downloadTask.resume()
            
            
          /*  URLSession.shared.downloadTask(with: url, completionHandler: { (tempURL, response, error) in
                    print("finished fetching \(url.absoluteString)")
                }).resume()
           */
                    
               /*     DispatchQueue.main.async {
                        let url = URL(string: fileURL)
                        let pdfData = try? Data.init(contentsOf: url!)
                        let resDocPath = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last!) as URL
                        let pdfFileName = "\(UUID().uuidString).jpg"
                        let filePath = resDocPath.appendingPathComponent(pdfFileName)
                        
                        // Save to File
                        
                        do {
                            try pdfData?.write(to: filePath,options: .atomic)
                            print("File Saved")
                        } catch {
                            print("Some Error in code")
                        }
                        
                    }
            */
            
            
            
            decisionHandler(.cancel)
            return
            
         
           
        }
       
       
    }
    decisionHandler(.allow)
}

func webView(_ webView: WKWebView, navigationResponse: WKNavigationResponse, didBecome download: WKDownload) {
        download.delegate = self;
    }

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    if navigationResponse.canShowMIMEType {
        decisionHandler(.allow)
    } else {
        decisionHandler(.download)
    }
}


   // Download Delegate (to allow downloading PDF)
   func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String) async -> URL? {
       let urls = FileManager.default.urls(for: .documentDirectory, in: .allDomainsMask)
       // for Japanese names.
       let name = suggestedFilename.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "cannotencode"
       return URL(string: name, relativeTo: urls[0])
   }

} Code file infor.plist như sau:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<dict> <key>CFBundleGetInfoString</key> <string></string> <key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> <key>NSAllowsArbitraryLoadsInWebContent</key> <true/> <key>NSExceptionDomains</key> <dict> <key>http://subdomain1.mywebsite.com.vn</key> <string></string> </dict> </dict> <key>UIApplicationSceneManifest</key> <dict> <key>UIApplicationSupportsMultipleScenes</key> <false/> <key>UISceneConfigurations</key> <dict> <key>UIWindowSceneSessionRoleApplication</key> <array> <dict> <key>UISceneConfigurationName</key> <string>Default Configuration</string> <key>UISceneDelegateClassName</key> <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string> <key>UISceneStoryboardFile</key> <string>Main</string> </dict> </array> </dict> </dict> </dict>

Sau khi chạy hệ thống báo lỗi như sau: App Transport Security has blocked a cleartext HTTP connection to http://subdomain1.mywebsite.com.vn since it is insecure. Use HTTPS instead or add this domain to Exception Domains in your Info.plist.

Mặc dù em đã thêm subdomain vào file infor rồi ạ. NHỜ ANH CHỈ RÙM EM VỚI Ạ. Cái nữa, liệu trong phần kiểm tra .linkActivated trên ( trước đó khi kích vào link xem ảnh chi tiết web chạy về 1 subdomain2 và tải ảnh từ link gốc subdomain2 liệu được không ạ?)

Avatar
0
Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí