0

I have a GenerateExpenseReportViewController class to generate a PDF report based on an array of expenses retrieved from a DBManager. The PDF is generated using PDFKit and the contents of the report are laid out in a table format.

The table contains headers for "Amount", "Category", "Payment Type", and "Expense Date". The data for each expense is then inserted into a row below its respective header.

The code first grabs references to the expenses array and initializes a new PDFDocument, PDFPage, and PDFView. It then generates the table headers by calculating the size and location of each header cell.

Next, it iterates over the expenses array, generating a row for each expense. The data for each row is inserted into a cell using the same technique as the headers. If there are too many rows for a single PDFPage, the code will create a new one and continue adding rows.

Finally, the generated PDFDocument is set to the PDFView document property. The problem is header always sits on the bottom of the table, how to make it sit at the top?

import UIKit
import PDFKit

class GenerateExpenseReportViewController: UIViewController {
    var db = DBManager()
    var expenses = Array<Expense>()
    let pdfView = PDFView()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(pdfView)
        expenses = db.read()

        let newDocument = PDFDocument()
        let page = PDFPage()
        let tableWidth = page.bounds(for: .mediaBox).width - 2 * 50 // Subtracting the left and right margins
        let tableHeight: CGFloat = 20
        let tableRect = CGRect(x: 50, y: page.bounds(for: .mediaBox).height - 100 - CGFloat(expenses.count) * tableHeight, width: tableWidth, height: CGFloat(expenses.count) * tableHeight)

        // Create table headers
        let headers = ["Amount", "Category", "Payment Type", "Expense Date"]
        let headerFont = UIFont.boldSystemFont(ofSize: 12)
        let headerCellWidth = tableWidth / CGFloat(headers.count)

        for (index, header) in headers.enumerated() {
            let headerCellRect = CGRect(x: tableRect.origin.x + CGFloat(index) * headerCellWidth, y: tableRect.origin.y - tableHeight, width: headerCellWidth, height: tableHeight)
            let headerAnnotation = PDFAnnotation(bounds: headerCellRect, forType: .freeText, withProperties: nil)
            headerAnnotation.font = headerFont
            headerAnnotation.alignment = .center
            headerAnnotation.contents = header
            headerAnnotation.color = UIColor.white
            page.addAnnotation(headerAnnotation)
        }

        newDocument.insert(page, at: 0)

        var currentPageIndex = 0
        var currentRowIndex = expenses.count - 1 // Start from the last row

        while currentRowIndex >= 0 {
            let currentPage = newDocument.page(at: currentPageIndex)

            let expense = expenses[currentRowIndex]

            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "d MMM yy"
            let expenseDateString = dateFormatter.string(from: expense.expenseDate)

            let rowData = ["\(expense.amount)", expense.category, expense.paymentType, expenseDateString]
            let rowFont = UIFont.systemFont(ofSize: 12)

            let rowCellWidth = tableWidth / CGFloat(rowData.count)

            for (index, data) in rowData.enumerated() {
                let rowCellRect = CGRect(x: tableRect.origin.x + CGFloat(index) * rowCellWidth, y: tableRect.origin.y + CGFloat(expenses.count - currentRowIndex + 1) * tableHeight, width: rowCellWidth, height: tableHeight)
                let rowAnnotation = PDFAnnotation(bounds: rowCellRect, forType: .freeText, withProperties: nil)
                rowAnnotation.font = rowFont
                rowAnnotation.alignment = .center
                rowAnnotation.contents = data
                rowAnnotation.color = UIColor.white
                currentPage?.addAnnotation(rowAnnotation)
            }

            currentRowIndex -= 1

            // Check if the current page is full
            let availableHeight = page.bounds(for: .mediaBox).height - 250 - CGFloat(expenses.count + 1) * tableHeight
            if availableHeight < tableHeight {
                currentPageIndex += 1
                currentRowIndex = expenses.count - 1

                let newPage = PDFPage()
                newDocument.insert(newPage, at: newDocument.pageCount)
            }
        }

        pdfView.document = newDocument
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        pdfView.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)
    }
}

0 Answers0