0

I am working on building a PDF document and the images and texts are being written to a view as CALayers. I am needing to vertically center my text within the bounding frame of a CATextLayer. I am using a class I found from 2016 as shown below that overrides the draw function. I was wondering if there are any new tricks to make this work?

As you can see when you run this code the text for cell 2 is not even being displayed, and cell 3 text is not being vertically centered.

Massive thanks to anyone who can help me.

//
//  ViewController.swift
//  CALayers Example
//
//  Created by Thomas Carroll on 8/18/20.
//  Copyright © 2020 Thomas Carroll. All rights reserved.
//

import Cocoa

class ViewController: NSViewController {

    let myLayers = MyLayers()
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.wantsLayer = true
        self.view.layer?.addSublayer(myLayers.insertGrid())
        self.view.layer?.addSublayer(myLayers.insertText())

        // Do any additional setup after loading the view.
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }


}
//
//  MyLayers.swift
//  CALayers Example
//
//  Created by Thomas Carroll on 8/18/20.
//  Copyright © 2020 Thomas Carroll. All rights reserved.
//

import Cocoa

// Set up constant variables
let pageWidth:Float = 72*8.5
let pageHeight:Float = 72*11
// Set up coordinates
let leftX = Int(pageWidth/2-72*2.5)
let col1X = Int(leftX+72)
let col2X = Int(col1X+72)
let col3X = Int(col2X+72)
let col4X = Int(col3X+72)
let rightX = Int(col4X+72)
let bottomY = Int(pageHeight/2-72*2.5)
let row4Y = Int(bottomY+72)
let row3Y = Int(row4Y+72)
let row2Y = Int(row3Y+72)
let row1Y = Int(row2Y+72)
let topY = Int(row1Y+72)

// Set the extension to draw Bezier paths into a CAShapeLayer
extension NSBezierPath {
    // Credit - Henrick - 9/18/2016
    // https://stackoverflow.com/questions/1815568/how-can-i-convert-nsbezierpath-to-cgpath
    public var cgPath: CGPath {
        let path = CGMutablePath()
        var points = [CGPoint](repeating: .zero, count: 3)
        for i in 0 ..< self.elementCount {
            let type = self.element(at: i, associatedPoints: &points)
            switch type {
            case .moveTo:
                path.move(to: points[0])
            case .lineTo:
                path.addLine(to: points[0])
            case .curveTo:
                path.addCurve(to: points[2], control1: points[0], control2: points[1])
            case .closePath:
                path.closeSubpath()
            @unknown default:
                print("Error occured in NSBezierPath extension.")
            }
        }
        return path
    }
}

class MyLayers {
    
    class VerticallyAlignedTextLayer : CATextLayer {

        func calculateMaxLines() -> Int {
            let maxSize = CGSize(width: frame.size.width, height: frame.size.height)
            let font = NSFont(descriptor: self.font!.fontDescriptor, size: self.fontSize)
            let charSize = (font?.capHeight)!
            let text = (self.string ?? "") as! NSString
            let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font!], context: nil)
            let linesRoundedUp = Int(ceil(textSize.height/charSize))
            return linesRoundedUp
        }
        
        override func draw(in context: CGContext) {
            let height = self.bounds.size.height
            let fontSize = self.fontSize
            let lines = CGFloat(calculateMaxLines())
            let yDiff = (height - lines * fontSize) / 2 - lines * fontSize / 10

            context.saveGState()
            context.translateBy(x: 0, y: yDiff) // Use -yDiff when in non-flipped coordinates (like macOS's default)
            super.draw(in: context)
            context.restoreGState()
        }
    }
        
    func insertGrid() -> CALayer {
        
        /*
         Draws a single table grid of 25 boxes (5 high by 5 wide)
         centered on a letter sized page
         */
        
        // Create a new shape layer for the grid
        let gridLayer = CAShapeLayer()
        
        // Create the path
        let gridPath = NSBezierPath()

        // Assign the grid fill and stroke colors
        gridLayer.strokeColor = NSColor.purple.cgColor
        gridLayer.fillColor = NSColor.clear.cgColor

        // Draw the paths for the grid
        // Create the outside box
        gridPath.move(to: CGPoint(x: leftX, y: bottomY)) // Bottom left corner
        gridPath.line(to: CGPoint(x: leftX, y: topY)) // Column 1, left line
        gridPath.line(to: CGPoint(x: rightX, y: topY)) // Row 1, top line
        gridPath.line(to: CGPoint(x: rightX, y: bottomY)) // Column 5 right line
        gridPath.line(to: CGPoint(x: leftX, y: bottomY)) // Row 5 bottom line
        
        // Add in column lines
        gridPath.move(to: CGPoint(x: col1X, y: topY)) // Between columns 1 & 2
        gridPath.line(to: CGPoint(x: col1X, y: bottomY)) // Line between columns 1 & 2
        gridPath.move(to: CGPoint(x: col2X, y: topY)) // Between columns 2 & 3
        gridPath.line(to: CGPoint(x: col2X, y: bottomY)) // Line between columns 2 & 3
        gridPath.move(to: CGPoint(x: col3X, y: topY)) // Between columns 3 & 4
        gridPath.line(to: CGPoint(x: col3X, y: bottomY)) // Line between columns 3 & 4
        gridPath.move(to: CGPoint(x: col4X, y: topY)) // Between columns 4 & 5
        gridPath.line(to: CGPoint(x: col4X, y: bottomY)) // Line between columns 4 & 5
        // Add in row lines
        gridPath.move(to: CGPoint(x: leftX, y: row1Y)) // Between rows 1 & 2
        gridPath.line(to: CGPoint(x: rightX, y: row1Y)) // Line between rows 1 & 2
        gridPath.move(to: CGPoint(x: leftX, y: row2Y)) // Between rows 2 & 3
        gridPath.line(to: CGPoint(x: rightX, y: row2Y)) // Line between rows 2 & 3
        gridPath.move(to: CGPoint(x: leftX, y: row3Y)) // Between rows 3 & 4
        gridPath.line(to: CGPoint(x: rightX, y: row3Y)) // Line between rows 3 & 4
        gridPath.move(to: CGPoint(x: leftX, y: row4Y)) // Between rows 4 & 5
        gridPath.line(to: CGPoint(x: rightX, y: row4Y)) // Line between rows 4 & 5

        // Close the path
        gridPath.close()
        // Add grid to layer (note the use of the cgPath extension)
        gridLayer.path = gridPath.cgPath

        return gridLayer
    }

    func insertText() -> CALayer {
        
        // Create a CALayer to add the textLayer to
        let myCALayer = CALayer()
        // Set up an array to hold the x coordinate for each column
        let colPosX = [leftX, col1X, col2X, col3X, col4X]
        // Set up an array to hold the y coordinate for the first card
        let rowPosY = [row1Y, row2Y, row3Y, row4Y, bottomY]
        // Set some default text to be used in the textLayers
        let cellText = ["This is some cell 1 text", "Cell 2 text", "This is text cell 3"]
        
        for i in (0...2) {
            let textLayer = VerticallyAlignedTextLayer()
            textLayer.string = cellText[i]
            textLayer.fontSize = 14
            // Set the frame to be 1 pixel smaller than the grid cell to provide 1px padding
            textLayer.frame = CGRect(origin: CGPoint(x: Int(colPosX[i])+1, y: Int(rowPosY[i])+1), size: CGSize(width: 70, height: 70))
            textLayer.alignmentMode = .center
            textLayer.isWrapped = true
            textLayer.foregroundColor = NSColor.black.cgColor
            textLayer.backgroundColor = NSColor.clear.cgColor
            textLayer.truncationMode = .none
            myCALayer.addSublayer(textLayer)
        }
        
        return myCALayer
    }
}
SouthernYankee65
  • 1,129
  • 10
  • 22

4 Answers4

0

Here's what I get on my system. enter image description here

apodidae
  • 1,988
  • 2
  • 5
  • 9
  • Notice how the cell at row1/col1 is not centered? None of them are really centered. If there's nothing better than this, then I may have to write my own to center the text by positioning the frame to center the text vertically. – SouthernYankee65 Aug 19 '20 at 17:14
  • And my Cell 2 text is not appearing. I tried entering a fourth element to the text string array "hello" like you did, but that isn't displaying for me either. – SouthernYankee65 Aug 19 '20 at 17:16
  • I'm willing to post the code if that will help (uses CommandLine or you could create Xcode app with it). – apodidae Aug 19 '20 at 17:22
  • Sure. Maybe it'll give me an idea what I'm doing wrong. – SouthernYankee65 Aug 20 '20 at 03:17
0

All that I did was to add a few print statements to func draw() so that I could see what was going on and then add a negative sign in front of the YDiff calculation. It appears to me that it's not showing the text because the Y coordinate is causing it to be translated somewhere outside of your boxes. Furthermore the remark behind .translateBy says to use a negative YDiff.

override func draw(in context: CGContext) {
    let height = self.bounds.size.height
    print("height = \(height)")
    let fontSize = self.fontSize
    let lines = CGFloat(calculateMaxLines())
    print("lines = \(lines)")
    let yDiff = -(height - lines * fontSize) / 2 - lines * fontSize / 10
    print("yDiff = \(yDiff)")
    context.saveGState()
    context.translateBy(x: 0, y: yDiff) // Use -yDiff when in non-flipped coordinates (like macOS's default)
    super.draw(in: context)
    print("draw called.")
    context.restoreGState()
    }

apodidae
  • 1,988
  • 2
  • 5
  • 9
  • In the yDiff calculation, changing the second divisor from 10 to 5 should get text closer to the vertical center. – apodidae Aug 19 '20 at 19:09
  • Yes, dividing by five rather than 10 makes it more centered in the cell. Also, the example had the negative yDiff it the context.translateBy method call. By putting it as a negative at the let yDiff statement it now appears on screen in the correct location. When ```let yDiff = -(height - lines * fontSize) / 2 - lines * fontSize / 5``` yDiff equals -9.8. But if I not put the negative in that call and then put the negative in the ```context.translateBy(x:0, y: -yDiff)``` call it equals -23.8. – SouthernYankee65 Aug 20 '20 at 03:54
  • I didn't add the negative to both; just let yDiff = -(). I left context.translateBy as it was. – apodidae Aug 20 '20 at 04:18
0

The following code was what I used, but it appears that you have it working. The only changes were to func draw() as noted above. You may run this code from Terminal or create your own Xcode app by using the demo's main section and replacing Xcode's AppDelegate with the demo's code plus adding the other classes.

/*
  To run in Terminal: swiftc calayer.swift  -framework Cocoa -o calayer && ./calayer 
*/

import Cocoa

// Set up constant variables
let pageWidth:Float = 72*8.5
let pageHeight:Float = 72*11
// Set up coordinates
let leftX = Int(pageWidth/2-72*2.5)
let col1X = Int(leftX+72)
let col2X = Int(col1X+72)
let col3X = Int(col2X+72)
let col4X = Int(col3X+72)
let rightX = Int(col4X+72)
let bottomY = Int(pageHeight/2-72*2.5)
let row4Y = Int(bottomY+72)
let row3Y = Int(row4Y+72)
let row2Y = Int(row3Y+72)
let row1Y = Int(row2Y+72)
let topY = Int(row1Y+72)

// Set the extension to draw Bezier paths into a CAShapeLayer
extension NSBezierPath {
 // Credit - Henrick - 9/18/2016
 // https://stackoverflow.com/questions/1815568/how-can-i-convert-nsbezierpath-to-cgpath
 public var cgPath: CGPath {
   let path = CGMutablePath()
   var points = [CGPoint](repeating: .zero, count: 3)
   for i in 0 ..< self.elementCount {
     let type = self.element(at: i, associatedPoints: &points)
       switch type {
         case .moveTo:
           path.move(to: points[0])
         case .lineTo:
           path.addLine(to: points[0])
         case .curveTo:
           path.addCurve(to: points[2], control1: points[0], control2: points[1])
         case .closePath:
           path.closeSubpath()
         @unknown default:
           print("Error occured in NSBezierPath extension.")
         }
    }
   return path
  }
}

class MyLayers {
    
  class VerticallyAlignedTextLayer : CATextLayer {

  func calculateMaxLines() -> Int {
   let maxSize = CGSize(width: frame.size.width, height: frame.size.height)
   let font = NSFont(descriptor: self.font!.fontDescriptor, size: self.fontSize)
   let charSize = (font?.capHeight)!
   let text = (self.string ?? "") as! NSString
   let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font!], context: nil)
   let linesRoundedUp = Int(ceil(textSize.height/charSize))
   return linesRoundedUp
   }
        
   override func draw(in context: CGContext) {
    let height = self.bounds.size.height
    print("height = \(height)")
    let fontSize = self.fontSize
    let lines = CGFloat(calculateMaxLines())
    print("lines = \(lines)")
    //let yDiff = -(height - lines * fontSize) / 2 - lines * fontSize / 10
    let yDiff = -(height - lines * fontSize) / 2 - lines * fontSize / 5
    print("yDiff = \(yDiff)")
    context.saveGState()
    context.translateBy(x: 0, y: yDiff) // Use -yDiff when in non-flipped coordinates (like macOS's default)
    super.draw(in: context)
    print("draw called.")
    print("==========================")
    context.restoreGState()
    }
  }
        
 func insertGrid() -> CALayer {
        
  /*
    Draws a single table grid of 25 boxes (5 high by 5 wide)
    centered on a letter sized page
  */
        
  // Create a new shape layer for the grid
   let gridLayer = CAShapeLayer()
        
  // Create the path
   let gridPath = NSBezierPath()

  // Assign the grid fill and stroke colors
  gridLayer.strokeColor = NSColor.purple.cgColor
  gridLayer.fillColor = NSColor.clear.cgColor

  // Draw the paths for the grid
  // Create the outside box
  gridPath.move(to: CGPoint(x: leftX, y: bottomY)) // Bottom left corner
  gridPath.line(to: CGPoint(x: leftX, y: topY)) // Column 1, left line
  gridPath.line(to: CGPoint(x: rightX, y: topY)) // Row 1, top line
  gridPath.line(to: CGPoint(x: rightX, y: bottomY)) // Column 5 right line
  gridPath.line(to: CGPoint(x: leftX, y: bottomY)) // Row 5 bottom line
        
  // Add in column lines
  gridPath.move(to: CGPoint(x: col1X, y: topY)) // Between columns 1 & 2
  gridPath.line(to: CGPoint(x: col1X, y: bottomY)) // Line between columns 1 & 2
  gridPath.move(to: CGPoint(x: col2X, y: topY)) // Between columns 2 & 3
  gridPath.line(to: CGPoint(x: col2X, y: bottomY)) // Line between columns 2 & 3
  gridPath.move(to: CGPoint(x: col3X, y: topY)) // Between columns 3 & 4
  gridPath.line(to: CGPoint(x: col3X, y: bottomY)) // Line between columns 3 & 4
  gridPath.move(to: CGPoint(x: col4X, y: topY)) // Between columns 4 & 5
  gridPath.line(to: CGPoint(x: col4X, y: bottomY)) // Line between columns 4 & 5
  // Add in row lines
  gridPath.move(to: CGPoint(x: leftX, y: row1Y)) // Between rows 1 & 2
  gridPath.line(to: CGPoint(x: rightX, y: row1Y)) // Line between rows 1 & 2
  gridPath.move(to: CGPoint(x: leftX, y: row2Y)) // Between rows 2 & 3
  gridPath.line(to: CGPoint(x: rightX, y: row2Y)) // Line between rows 2 & 3
  gridPath.move(to: CGPoint(x: leftX, y: row3Y)) // Between rows 3 & 4
  gridPath.line(to: CGPoint(x: rightX, y: row3Y)) // Line between rows 3 & 4
  gridPath.move(to: CGPoint(x: leftX, y: row4Y)) // Between rows 4 & 5
  gridPath.line(to: CGPoint(x: rightX, y: row4Y)) // Line between rows 4 & 5

  // Close the path
  gridPath.close()
  // Add grid to layer (note the use of the cgPath extension)
  gridLayer.path = gridPath.cgPath

  return gridLayer
 }

 func insertText() -> CALayer {
        
 // Create a CALayer to add the textLayer to
 let myCALayer = CALayer()
 // Set up an array to hold the x coordinate for each column
 let colPosX = [leftX, col1X, col2X, col3X, col4X]
 // Set up an array to hold the y coordinate for the first card
 let rowPosY = [row1Y, row2Y, row3Y, row4Y, bottomY]
 // Set some default text to be used in the textLayers
 // let cellText = ["This is some cell 1 text", "Some cell 2 text", "This is text cell 3"]
 let cellText = ["This is some cell 1 text", "Cell 2 text", "This is text cell 3", "hello"]
        
 for i in (0...3) {
  let textLayer = VerticallyAlignedTextLayer()
  textLayer.string = cellText[i]
  print(cellText[i])
  textLayer.fontSize = 14
  // Set the frame to be 1 pixel smaller than the grid cell to provide 1px padding
  textLayer.frame = CGRect(origin: CGPoint(x: Int(colPosX[i])+1, y: Int(rowPosY[i])+1), size: CGSize(width: 70, height: 70))
  textLayer.alignmentMode = .center
  textLayer.isWrapped = true
  textLayer.foregroundColor = NSColor.black.cgColor
  textLayer.backgroundColor = NSColor.clear.cgColor
  textLayer.truncationMode = .none
  myCALayer.addSublayer(textLayer)
  }  
  print("=======================")    
  return myCALayer
 }
}


class AppDelegate: NSObject, NSApplicationDelegate {
var window:NSWindow!

@objc func myBtnAction(_ sender:AnyObject ) {
  NSSound.beep()
}

func buildMenu() {
 let mainMenu = NSMenu()
 NSApp.mainMenu = mainMenu
 // **** App menu **** //
 let appMenuItem = NSMenuItem()
 mainMenu.addItem(appMenuItem)
 let appMenu = NSMenu()
 appMenuItem.submenu = appMenu
 appMenu.addItem(withTitle: "Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q") 
}
    
func buildWnd() {
    
let _wndW : CGFloat = 650
let _wndH : CGFloat = 700

 window = NSWindow(contentRect:NSMakeRect(0,0,_wndW,_wndH),styleMask:[.titled, .closable, .miniaturizable, .resizable], backing:.buffered, defer:false)
 window.center()
 window.title = "Swift Test Window"
 window.makeKeyAndOrderFront(window)

 let view = NSView()
 let myLayers = MyLayers()
 view.frame = NSMakeRect(20, 60, _wndW - 40, _wndH - 80)
 view.wantsLayer = true
 view.layer?.addSublayer(myLayers.insertGrid())
 view.layer?.addSublayer(myLayers.insertText())
 window.contentView!.addSubview (view)

// **** Quit btn **** //
let quitBtn = NSButton (frame:NSMakeRect( _wndW - 50, 10, 40, 40 ))
 quitBtn.bezelStyle = .circular
 quitBtn.autoresizingMask = [.minXMargin,.maxYMargin]
 quitBtn.title = "Q"
 quitBtn.action = #selector(NSApplication.terminate)
 window.contentView!.addSubview(quitBtn)
}
 
func applicationDidFinishLaunching(_ notification: Notification) {
 buildMenu()
 buildWnd()
}

func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
 return true
}

}
let appDelegate = AppDelegate()

// **** main.swift **** //
let app = NSApplication.shared
app.delegate = appDelegate
app.setActivationPolicy(.regular)
app.activate(ignoringOtherApps:true)
app.run()


apodidae
  • 1,988
  • 2
  • 5
  • 9
0

Swift 5.3

Okay, I finally figured out how to do what I needed. Specifically, I needed to change the fontSize of the textLayer so it would fit within the bounding box horizontally and vertically while also being vertically centered within the bounding box. To do this, I found some code that checks a boundingRect of an attributed string. Also note that I changed the yDiff calculation where the fontSize is being divided by 6.5 rather than 10 and it positions the text better vertically for me.

class MyLayers {
    
    class VerticallyAlignedTextLayer : CATextLayer {

        func calculateMaxLines() -> Int {
            let maxSize = CGSize(width: frame.size.width, height: frame.size.width)
            let font = NSFont(descriptor: self.font!.fontDescriptor, size: self.fontSize)
            let charSize = floor(font!.capHeight)
            let text = (self.string ?? "") as! NSString
            let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font!], context: nil)
            let linesRoundedUp = Int(floor(textSize.height/charSize))
            return linesRoundedUp
        }
        
        override func draw(in context: CGContext) {
            let height = self.bounds.size.height
            let fontSize = self.fontSize
            let lines = CGFloat(calculateMaxLines())
            let yDiff = -(height - lines * fontSize) / 2 - lines * fontSize / 6.5 // Use -(height - lines * fontSize) / 2 - lines * fontSize / 6.5 when in non-flipped coordinates (like macOS's default)

            context.saveGState()
            context.translateBy(x: 0, y: yDiff)
            super.draw(in: context)
            context.restoreGState()
        }
    }
        
    func insertGrid() -> CALayer {
        
        /*
         Draws a single table grid of 25 boxes (5 high by 5 wide)
         centered on a letter sized page
         */
        
        // Create a new shape layer for the grid
        let gridLayer = CAShapeLayer()
        
        // Create the path
        let gridPath = NSBezierPath()

        // Assign the grid fill and stroke colors
        gridLayer.strokeColor = NSColor.purple.cgColor
        gridLayer.fillColor = NSColor.white.cgColor

        // Draw the paths for the grid
        // Create the outside box
        gridPath.move(to: CGPoint(x: leftX, y: bottomY)) // Bottom left corner
        gridPath.line(to: CGPoint(x: leftX, y: topY)) // Column 1, left line
        gridPath.line(to: CGPoint(x: rightX, y: topY)) // Row 1, top line
        gridPath.line(to: CGPoint(x: rightX, y: bottomY)) // Column 5 right line
        gridPath.line(to: CGPoint(x: leftX, y: bottomY)) // Row 5 bottom line
        //gridPath.close()
        //gridLayer.path = gridPath.cgPath
        
        // Add in column lines
        gridPath.move(to: CGPoint(x: col1X, y: topY)) // Between columns 1 & 2
        gridPath.line(to: CGPoint(x: col1X, y: bottomY)) // Line between columns 1 & 2
        gridPath.move(to: CGPoint(x: col2X, y: topY)) // Between columns 2 & 3
        gridPath.line(to: CGPoint(x: col2X, y: bottomY)) // Line between columns 2 & 3
        gridPath.move(to: CGPoint(x: col3X, y: topY)) // Between columns 3 & 4
        gridPath.line(to: CGPoint(x: col3X, y: bottomY)) // Line between columns 3 & 4
        gridPath.move(to: CGPoint(x: col4X, y: topY)) // Between columns 4 & 5
        gridPath.line(to: CGPoint(x: col4X, y: bottomY)) // Line between columns 4 & 5
        // Add in row lines
        gridPath.move(to: CGPoint(x: leftX, y: row1Y)) // Between rows 1 & 2
        gridPath.line(to: CGPoint(x: rightX, y: row1Y)) // Line between rows 1 & 2
        gridPath.move(to: CGPoint(x: leftX, y: row2Y)) // Between rows 2 & 3
        gridPath.line(to: CGPoint(x: rightX, y: row2Y)) // Line between rows 2 & 3
        gridPath.move(to: CGPoint(x: leftX, y: row3Y)) // Between rows 3 & 4
        gridPath.line(to: CGPoint(x: rightX, y: row3Y)) // Line between rows 3 & 4
        gridPath.move(to: CGPoint(x: leftX, y: row4Y)) // Between rows 4 & 5
        gridPath.line(to: CGPoint(x: rightX, y: row4Y)) // Line between rows 4 & 5

        // Close the path
        gridPath.close()
        // Add grid to layer (note the use of the cgPath extension)
        gridLayer.path = gridPath.cgPath

        return gridLayer
    }
    
    func sizeOfRect(string: NSString, fontSize: CGFloat) -> Int {
        /* Credit to Jake Marsh - 12/10/2015
            https://littlebitesofcocoa.com/144-drawing-multiline-strings
         
            Return the height of a boundingRect for a specified string at a specified fontSize
         */
        let cellFontSize:CGFloat = fontSize
        let cellFont:NSFont = NSFont.systemFont(ofSize: cellFontSize, weight: .regular)
        let cellParagraphStyle = NSMutableParagraphStyle()
        let cellTextAttributes = [NSAttributedString.Key.font: cellFont, NSAttributedString.Key.paragraphStyle: cellParagraphStyle]
        let cellDrawingOptions: NSString.DrawingOptions = [
        .usesLineFragmentOrigin, .usesFontLeading]
        cellParagraphStyle.lineHeightMultiple = 1.0
        cellParagraphStyle.lineBreakMode = .byWordWrapping
        
        return Int(string.boundingRect(with: CGSize(width: 70, height: CGFloat.infinity), options: cellDrawingOptions, attributes: cellTextAttributes).height)
    }

    func insertText() -> CALayer {
        
        // Create a CALayer to add the textLayer to
        let myCALayer = CALayer()
        // Set up an array to hold the x coordinate for each column
        let colPosX = [leftX, col1X, col2X, col3X, col4X]
        // Set up an array to hold the y coordinate for the first card
        let rowPosY = [row1Y, row2Y, row3Y, row4Y, bottomY]
        // Set some default text to be used in the textLayers
        let cellText = ["This is some cell 1 text that is kind of long", "Cell 2 text", "This is text cell 3", "Some really really long text"]
        
        for i in (0...3) {
            // Create a vertically centered textLayer
            let textLayer = VerticallyAlignedTextLayer()
            // Set up the initial font size for the text
            var fontSize:CGFloat = 14
            // Assign a string to the textLayer
            textLayer.string = cellText[i]
            // Check the vertical hieght of a rectangle that would contain the text based on the current fontSize.  If the text is taler than the specific box height, reduce the fontSize my a half point until it is within the specified height of the box.
            while sizeOfRect(string: cellText[i] as NSString, fontSize: fontSize) > 68 {
                fontSize -= 0.5
            }
            // Assign the adjusted fontSize to the textLayer
            textLayer.fontSize = fontSize
            // Set the frame to be 4 pixel smaller than the grid cell to provide 2px padding
            textLayer.frame = CGRect(origin: CGPoint(x: Int(colPosX[i])+2, y: Int(rowPosY[i])+2), size: CGSize(width: 68, height: 68))
            textLayer.alignmentMode = .center
            textLayer.isWrapped = true
            textLayer.foregroundColor = NSColor.black.cgColor
            textLayer.backgroundColor = NSColor.clear.cgColor
            textLayer.truncationMode = .none
            myCALayer.addSublayer(textLayer)
        }
        
        return myCALayer
        
    }

}
SouthernYankee65
  • 1,129
  • 10
  • 22
  • 1
    Only one of the four is vertically centered on my system, but you're the one who has to be happy with it. Glad you got it fixed. – apodidae Aug 21 '20 at 21:00