1

I have been trying to find a way to export a View in SWIFTUI in A4 size pages PDF. The view cannot fit in one single page. So far I have managed to edit (with the help of ChatGPT) the code found in PDF Creator GitHub (See below)

But although I get multiple page PDF as a result only the first page is populated, the rest are just blank.

Does anyone faced something similar before, if yes how did you manage to export to PDF in A4 pages...

Thanks for any tips and help!

extension View{
    
    func sharePDF<Content: View> (@ViewBuilder content: @escaping () -> Content, fileName: String) {
        exportPDF(content: content, completion: { status , url in
            if let url = url, status {
                ShareSheet.instance.share(items: [url])
            } else {
                print("⚠️ Failed to make PDF")
            }
        }, fileName: fileName)
    }
    
    // MARK: Extracting View's Height and width with the Help of Hosting Controller and ScrollView
    fileprivate func convertToScrollView<Content: View>(@ViewBuilder content: @escaping ()->Content)->UIScrollView{
        
        let scrollView = UIScrollView()
        
        // MARK: Converting SwiftUI View to UIKit View
        let hostingController = UIHostingController(rootView: content()).view!
        hostingController.translatesAutoresizingMaskIntoConstraints = false
        
        // MARK: Constraints
        let constraints = [
        
            hostingController.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            hostingController.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            hostingController.topAnchor.constraint(equalTo: scrollView.topAnchor),
            hostingController.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            
            // Width Anchor
            hostingController.widthAnchor.constraint(equalToConstant: screenBounds().width)
        ]
        scrollView.addSubview(hostingController)
        scrollView.addConstraints(constraints)
        scrollView.layoutIfNeeded()
        
        return scrollView
    }
    
    // MARK: Export to PDF
    // MARK: Completion Handler will Send Status and URL
    fileprivate func exportPDF<Content: View>(@ViewBuilder content: @escaping () -> Content, completion: @escaping (Bool, URL?) -> (), fileName: String) {
        // MARK: Temp URL
        let documentDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
        // MARK: To Generate New File whenever it's generated
        let outputFileURL = documentDirectory.appendingPathComponent("\(fileName)\(UUID().uuidString).pdf")

        // MARK: PDF View
        let scrollView = convertToScrollView {
            content()
        }
        scrollView.tag = 1009
        scrollView.frame = CGRect(x: 0, y: 0, width: 595.2, height: 841.8) // A4 size in points (72 points per inch)

        let pageSize = scrollView.frame.size
        let contentSize = scrollView.contentSize
        let pageCount = Int(ceil(contentSize.height / pageSize.height))

        // Create PDF Context
        UIGraphicsBeginPDFContextToFile(outputFileURL.path, .zero, nil)

        // CHATGPT:
        for index in 0..<pageCount {
            // Begin new PDF page
            UIGraphicsBeginPDFPageWithInfo(CGRect(origin: .zero, size: pageSize), nil)

            // Calculate the visible frame for each page
            let visibleFrame = CGRect(x: 0, y: -pageSize.height * CGFloat(index), width: pageSize.width, height: pageSize.height)

            // Capture the screenshot of the visible content synchronously
            scrollView.clipToRect(visibleFrame) {
                // Take a screenshot of the visible content
                let screenshot = scrollView.takeScreenshot()

                // Draw the screenshot into the PDF context
                screenshot.draw(at: .zero)
            }
        }

        completion(true, outputFileURL)

        
        // End PDF Context
        UIGraphicsEndPDFContext()

        completion(true, outputFileURL)

        // Removing the added View
        getRootController().view.subviews.forEach { view in
            if view.tag == 1009 {
                print("Removed")
                view.removeFromSuperview()
            }
        }
    }
    
    fileprivate func screenBounds()->CGRect{
        return UIScreen.main.bounds
    }
    
    fileprivate func getRootController()->UIViewController{
        guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else{
            return .init()
        }
        
        guard let root = screen.windows.first?.rootViewController else{
            return .init()
        }
        
        return root
    }
    
    fileprivate func getSafeArea()->UIEdgeInsets{
        guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else{
            return .zero
        }
        
        guard let safeArea = screen.windows.first?.safeAreaInsets else{
            return .zero
        }
        
        return safeArea
    }
}

extension UIView {
    func takeScreenshot() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale)
        drawHierarchy(in: bounds, afterScreenUpdates: true)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image ?? UIImage()
    }

    func clipToRect(_ rect: CGRect, perform: () -> Void) {
        guard let context = UIGraphicsGetCurrentContext() else { return }

        context.saveGState()
        context.clip(to: rect)
        perform()
        context.restoreGState()
    }
}

I tried the code shared above and I got only one page populated, the rest are blank.

Ioannis
  • 21
  • 3
  • You can make multiple page PDF if you decide to "design" each page separately. In my case my content is dynamic thus the pages will not be created correctly if the size of the content exceeds 842points (which is A4 page height). – Ioannis Jun 04 '23 at 05:29
  • I tried your approach (multiple versions of it) but coudln't get something that works. The Code above has two parts, one is that makes the VIEW into a ScrollView and the second part is that it tries to create a pdf page for each 842 points. In principal it should work..my guess is that there is something wrong with either the ScrollView implementation(first part) or the screenshot taking(second part).. – Ioannis Jun 04 '23 at 12:40

0 Answers0