Controlling Positioning with CoreText

Can someone please tell me how to make the text begin at a certain point on the page, vertically? I have a header and I want this text to be printed 10 points underneath that header variable called theGreetingBottom (so the Y point where it begins should be theGreetingBottom - 10) on the FIRST page, then at the top (minus margin [72]) on every page thereafter... in other words, basically:

if isFirstPage {
    print starting 10 points underneath theGreetingBottom
} else {
    print at pageHeight - margin.y

Here is the current code I have:

class PDFCreator {

lazy var pageWidth : CGFloat  = {
    return 8.5 * 72.0

lazy var pageHeight : CGFloat = {
    return 11 * 72.0

lazy var pageRect : CGRect = {
    CGRect(x: 0, y: 0, width: pageWidth, height: pageHeight)

lazy var marginPoint : CGPoint = {
    return CGPoint(x: 10, y: 10)

lazy var marginSize : CGSize = {
    return CGSize(width: self.marginPoint.x * 2 , height: self.marginPoint.y * 2)

func prepareData() -> Data {
    let pdfMetaData = [
      kCGPDFContextCreator: "PDF Creator",
      kCGPDFContextAuthor: "Pratik Sodha",
      kCGPDFContextTitle: "My PDF"

    let format = UIGraphicsPDFRendererFormat()
    format.documentInfo = pdfMetaData as [String: Any]

    let renderer = UIGraphicsPDFRenderer(bounds: pageRect, format: format)

    let data = renderer.pdfData { (context) in

        self.addText("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.", context: context)

    return data

func addText(_ text : String, context : UIGraphicsPDFRendererContext) -> CGFloat {

    // 1
    let textFont = UIFont.systemFont(ofSize: 60.0, weight: .regular)

    // 2
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.alignment = .natural
    paragraphStyle.lineBreakMode = .byWordWrapping

    // 3
    let textAttributes = [
        NSAttributedString.Key.paragraphStyle: paragraphStyle,
        NSAttributedString.Key.font: textFont

    let currentText = CFAttributedStringCreate(nil,
                                               text as CFString,
                                               textAttributes as CFDictionary)
    let framesetter = CTFramesetterCreateWithAttributedString(currentText!)

    var currentRange = CFRangeMake(0, 0)
    var currentPage = 0
    var done = false
    repeat {

        /* Mark the beginning of a new page.*/

        /*Draw a page number at the bottom of each page.*/
        currentPage += 1

        /*Render the current page and update the current range to
          point to the beginning of the next page. */
        currentRange = renderPage(currentPage,
                                  withTextRange: currentRange,
                                  andFramesetter: framesetter)

        /* If we're at the end of the text, exit the loop. */
        if currentRange.location == CFAttributedStringGetLength(currentText) {
            done = true

    } while !done

    return CGFloat(currentRange.location + currentRange.length)

func renderPage(_ pageNum: Int, withTextRange currentRange: CFRange, andFramesetter framesetter: CTFramesetter?) -> CFRange {
    var currentRange = currentRange
    // Get the graphics context.
    let currentContext = UIGraphicsGetCurrentContext()

    // Put the text matrix into a known state. This ensures
    // that no old scaling factors are left in place.
    currentContext?.textMatrix = .identity

    // Create a path object to enclose the text. Use 72 point
    // margins all around the text.
    let frameRect = CGRect(x: self.marginPoint.x, y: self.marginPoint.y, width: self.pageWidth - self.marginSize.width, height: self.pageHeight - self.marginSize.height)
    let framePath = CGMutablePath()
    framePath.addRect(frameRect, transform: .identity)

    // Get the frame that will do the rendering.
    // The currentRange variable specifies only the starting point. The framesetter
    // lays out as much text as will fit into the frame.
    let frameRef = CTFramesetterCreateFrame(framesetter!, currentRange, framePath, nil)

    // Core Text draws from the bottom-left corner up, so flip
    // the current transform prior to drawing.
    currentContext?.translateBy(x: 0, y: self.pageHeight)
    currentContext?.scaleBy(x: 1.0, y: -1.0)

    // Draw the frame.
    CTFrameDraw(frameRef, currentContext!)

    // Update the current range based on what was drawn.
    currentRange = CTFrameGetVisibleStringRange(frameRef)
    currentRange.location += currentRange.length
    currentRange.length = CFIndex(0)

    return currentRange

func drawPageNumber(_ pageNum: Int) {

    let theFont = UIFont.systemFont(ofSize: 20)

    let pageString = NSMutableAttributedString(string: "Page \(pageNum)")
    pageString.addAttribute(NSAttributedString.Key.font, value: theFont, range: NSRange(location: 0, length: pageString.length))

    let pageStringSize =  pageString.size()

    let stringRect = CGRect(x: (pageRect.width - pageStringSize.width) / 2.0,
                            y: pageRect.height - (pageStringSize.height) / 2.0 - 15,
                            width: pageStringSize.width,
                            height: pageStringSize.height)

    pageString.draw(in: stringRect)


@mattcurtis Any clue on how to solve this, if you've got a moment?

Have you examined your drawPageNumber function? It's doing all the things you'd need to do in order to draw text at a particular point on screen. It actually seems to be pretty well documented.

Yeah, I took a look at that but it functions slightly different, I believe. Maybe I'm wrong, I'm learning. I was able to get it working correctly by adjusting the frameRect height.

Thank you!!