Print custom receipts

On this page, we will explore how to print custom receipts using the ePOS SDK for iOS.
Before using the functions below, make sure you have set up the SDK as described in the Setup guide. This is required to access the printingApi instance. If setup is not called, printingApi will be null.

Using our SDK, you can print fully customizable receipts by defining your own template. This lets you tailor the layout and content to match your specific business needs.
To print a custom template, simply call the printCustomTemplate function:

let template = buildCustomTemplate()
teyaPosLinkSDK.printingApi.printCustomTemplate(template: template)

You can build your template using the Template class, which allows you to add rows containing text, images, tables, and other elements. Here’s an example of how to construct a custom template:

func buildCustomTemplate() -> TeyaTemplate {
    // Date/time like "21/02/22 · 10:25"
    let df = DateFormatter()
    df.dateFormat = "dd/MM/yy · HH:mm"
    let nowText = df.string(from: Date())

    var rows: [TeyaReceiptRow] = []

    // Header
    rows.append(
        TeyaReceiptRowItem(
            item: TeyaRowElementText(
                text: "MMF TEST A920",
                bold: true,
                align: TeyaAlign.center
            )
        )
    )
    rows.append(
        TeyaReceiptRowItem(
            item: TeyaRowElementText(
                text: "Ruzova Dolina 6, Bratislava 85104",
                bold: false,
                align: TeyaAlign.center
            )
        )
    )

    rows.append(TeyaReceiptRowSpacer.shared)

    // Optional images row + Approved text pair
    let logo = UIImage(named: "teya_logo")!
    var itemsRow1: [TeyaRowElement] = []
    itemsRow1.append(
        TeyaRowElementImage(
            imageToPrint: TeyaImageKt.create(uiImage: logo),
            align: TeyaAlign.left
        )
    )
    itemsRow1.append(
        TeyaRowElementText(
            text: " Approved",
            bold: true,
            align: TeyaAlign.right
        )
    )
    if !itemsRow1.isEmpty {
        rows.append(TeyaReceiptRowItems(items: itemsRow1))
    }

    // Title + date row
    rows.append(
        TeyaReceiptRowItems(
            items: [
                TeyaRowElementText(text: "CUSTOMER RECEIPT", bold: true, align: TeyaAlign.left),
                TeyaRowElementText(text: nowText, bold: true, align: TeyaAlign.right),
            ]
        )
    )

    rows.append(TeyaReceiptRowSpacer.shared)
    rows.append(TeyaReceiptRowDivider.shared)

    // Items
    rows.append(
        TeyaReceiptRowItems(
            items: [
                TeyaRowElementText(text: "1x COFFEE", bold: true, align: TeyaAlign.left),
                TeyaRowElementText(text: "£ 2.00", bold: true, align: TeyaAlign.right),
            ]
        )
    )
    for _ in 0..<3 {
        rows.append(
            TeyaReceiptRowItems(
                items: [
                    TeyaRowElementText(text: "1x WATER", bold: true, align: TeyaAlign.left),
                    TeyaRowElementText(text: "£ 1.00", bold: true, align: TeyaAlign.right),
                ]
            )
        )
    }

    rows.append(TeyaReceiptRowSpacer.shared)

    rows.append(
        TeyaReceiptRowItems(
            items: [
                TeyaRowElementText(text: "AMOUNT", bold: true, align: TeyaAlign.left),
                TeyaRowElementText(text: "£ 5.00", bold: true, align: TeyaAlign.right),
            ]
        )
    )
    rows.append(
        TeyaReceiptRowItems(
            items: [
                TeyaRowElementText(text: "TIPS", bold: true, align: TeyaAlign.left),
                TeyaRowElementText(text: "£ 2.00", bold: true, align: TeyaAlign.right),
            ]
        )
    )

    rows.append(TeyaReceiptRowDivider.shared)

    rows.append(
        TeyaReceiptRowItems(
            items: [
                TeyaRowElementText(text: "TOTAL", bold: true, align: TeyaAlign.left),
                TeyaRowElementText(text: "£ 7.00", bold: true, align: TeyaAlign.right),
            ]
        )
    )

    rows.append(TeyaReceiptRowDivider.shared)
    rows.append(TeyaReceiptRowSpacer.shared)

    rows.append(TeyaReceiptRowItem(item:
        TeyaRowElementText(text: "Mastercard Contactless ****6787", bold: true, align: TeyaAlign.left)
    ))
    rows.append(TeyaReceiptRowItem(item:
        TeyaRowElementText(text: "AID: A00000000041010", bold: true, align: TeyaAlign.left)
    ))
    rows.append(TeyaReceiptRowItem(item:
        TeyaRowElementText(text: "Approval code: SsF4e", bold: true, align: TeyaAlign.left)
    ))
    rows.append(
        TeyaReceiptRowItems(
            items: [
                TeyaRowElementText(text: "MID: ****5678", bold: true, align: TeyaAlign.left),
                TeyaRowElementText(text: "TID: ****Y002", bold: true, align: TeyaAlign.right),
            ]
        )
    )
    rows.append(TeyaReceiptRowItem(item:
        TeyaRowElementText(text: "Reference: 12345678901234567890", bold: true, align: TeyaAlign.left)
    ))

    rows.append(TeyaReceiptRowSpacer.shared)
    rows.append(TeyaReceiptRowSpacer.shared)

    rows.append(
        TeyaReceiptRowItem(
            item: TeyaRowElementText(text: "Thank you", bold: true, align: TeyaAlign.center)
        )
    )

    return TeyaTemplate(rows: rows)
}

Result:
Custom receipt template

As shown in the example above, you can combine different row types and elements to create a receipt that fits your requirements. Below are the details of the available row types and element types.

Row Types

For the rows, you can use various ReceiptRow types such as Item, Items, Spacer, Divider, and Table.

Item

A single row containing only one element.

TeyaReceiptRowItem(
    item: // element
)

Items

A row that can contain multiple elements side by side, allowing for more complex layouts within a single row.
All items will be evenly distributed across the row, meaning each item will have equal width. Custom weights for items inside the Items row aren’t supported yet. If you need to use weights, consider using a Table instead.

TeyaReceiptRowItems(
    items: [
        // list of elements
    ]
)
You can’t have more than 10 elements in a single `Items` row.

Spacer

An empty row that adds vertical space between other rows, improving readability and visual separation.

TeyaReceiptRowSpacer.shared

Divider

A dashed line that separates sections of the receipt, helping to organize the content.

TeyaReceiptRowDivider.shared

Table

A structured section with a header and multiple data rows, ideal for listing items with columns (e.g., product name and price).
If you set a column’s weight to 0f or less, its width will automatically adjust to fit its content.

TeyaReceiptRowTable(
    headerCells: [
        TeyaTableHeaderCell(element: /* Text element */, weight: 1),
        TeyaTableHeaderCell(element: /* Text element */, weight: 1),
    ],
    rows: [
        TeyaTableRow(
            cells: [
                // Text elements
            ]
        ),
        TeyaTableRow(
            cells: [
                // Text elements
            ]
        )
    ]
)

Table limitations:

  • You can put only Text elements inside table cells.
  • The number of header cells must match the number of cells in each row.
  • The number of columns in a table cannot exceed 10.

Element types

Within each row, you can use different RowElement types to define the content:

Text

Displays a line of text on the receipt. You can set alignment (left, center, right) and make it bold. Used for headings, messages, or item names.

TeyaRowElementText(
    text: "Welcome!", // The text to display
    bold: true, // Whether the text should be bold
    align: TeyaAlign.left // Text alignment: TeyaAlign.left, TeyaAlign.center, TeyaAlign.right (aligns within its container)
)
The text parameter cannot be empty or blank.

Image

Prints a UIImage (e.g., logo or icon) on the receipt. If you are converting an image to a UIImage, ensure the image is in PNG or JPEG format.

TeyaRowElementImage(
    imageToPrint: TeyaImageKt.create(uiImage: /* your UIImage */),
    align: TeyaAlign.left // Alignment of the image within its container
)

QrCode

Renders a QR code with a specified URL or string. You can set its alignment. Useful for adding links, payment info, or promotions.

TeyaRowElementQrCode(
    url: "https://teya.com", // The URL or string to encode in the QR code
    align: TeyaAlign.left // Alignment of the qr code within its container
)
The url parameter cannot be empty or blank.

Limitations

When building custom templates, keep the following limitations in mind:

  • A template can have a maximum of 150 rows.
  • Some row and element types have specific limitations, which are described in their individual sections.
  • The template is converted to JSON before being sent to the printer, and its total size must not exceed 250 KB.
    • Images are converted to Base64 strings, which can significantly increase the size.

If a limitation can be detected immediately, the SDK throws an exception when creating a Template object. If the backend detects a limitation, an error is returned in TeyaPrintStateDetails as part of the printing status updates.

You can also print images directly using the SDK.
To print an image, use the printCustomImage function and pass a UIImage as a parameter:

let uiImage = // Your UIImage to print
teyaPosLinkSDK.printingApi.printCustomImage(image: TeyaImageKt.create(uiImage: uiImage));

The recommended width is 384 px. Images wider than this are scaled down to fit the printer width.
If you're converting an image to a UIImage, ensure that the image you want to print is in a compatible format (PNG or JPEG) and properly scaled for the printer.

When printing images, keep the following limitations in mind:

  • The maximum height for the image is 10000 px.
  • The recommended width for images is 384 px. If you pass an image wider than this, it will be scaled down to fit the printer width.
  • We are converting the UIImage to image data before sending it to the printer, and the total size of this data must not exceed 200 KB.
    • If this limit is exceeded, an error will be returned in TeyaPrintStateDetails with a corresponding message.

If a limitation can be detected immediately, the SDK throws an exception when calling TeyaImageKt.create() function. If the backend detects a limitation, an error is returned in TeyaPrintStateDetails as part of the printing status updates.

Subscribe to printing status

Both printCustomTemplate and printCustomImage functions return a TeyaPrintingStatusSubscription object. You can use this object to subscribe to printing status updates.

First, let's create a small Swift wrapper for the TeyaPrintingStatusSubscriptionListener protocol:

private final class PrintingStatusSubscriptionListener: TeyaPrintingStatusSubscriptionListener {
    func onPrintingStateChanged(printStateDetails: TeyaPrintStateDetails) {
        print("Printing state changed: \(printStateDetails)")
    }
}

Then, you can subscribe to printing status updates like this:

teyaPosLinkSDK.printingApi.printCustomImage(image: TeyaImageKt.create(uiImage: uiImage)).subscribe(
    printingListener: PrintingStatusSubscriptionListener()
)

Printing Statuses

When you subscribe to printing status, you can track the following statuses:

Status Final? Description
Pending No Initial temporary state before connecting to the printer.
ConnectingToPrinter No The system is connecting to the printer to initiate the printing action.
Printing No The card machine is actively printing.
Printed Yes The printing action completed successfully.
Failed Yes The printing action failed.

If printStateDetails.status is PrintStatus.Printed, the print was successful.

If printStateDetails.status is PrintStatus.Failed, check the printFailure property for the specific error reason:

Enum Value Description
NetworkError Connection error occurred.
OutOfPaper The card machine ran out of paper during the printing process.
Busy The printer is currently busy.
Overheated The printer has overheated.
CoverOpen The printer cover is open.
LowBattery The card machine battery is too low to complete the print.
Other An unknown issue occurred.

Note: For more exact error handling, refer to the errorMessage property in TeyaPrintStateDetails, which may contain additional information about the failure e.g "Image size exceeds limit".