SpriteKit: Is there a way to centre a SKLabelNode according to its baseline

后端 未结 1 1702
温柔的废话
温柔的废话 2021-01-20 21:02

A label has four different verticalAlignmentMode: .Baseline, .Bottom, .Center and .Top.

I would like

1条回答
  •  迷失自我
    2021-01-20 21:20

    So I've had the same issue and with the help with of this question and answer I found a decent solution.

    I wanted to align the text of label nodes with custom button nodes (a label node wrapped in a shape node to draw the outline).

    I also wanted to align toggle (on/off) buttons with the baseline of the label node and use the same height as the xHeight of the text in the label.

    The red lines show that all labels & buttons are properly aligned.

    So in order to do this, I've done the following.

    I created a Font class that can be used to create a font object from the fontName and fontSize in a SKLabelNode.

    Internally it will create either a NSFont (macOS) or UIFont (iOS). And set the ascender, descender properties. For convenience there's also a getter that returns the maximum possible height of the font.

    import Foundation
    
    #if os(macOS)
    import Cocoa
    #endif
    
    #if os(iOS)
    import UIKit
    #endif
    
    enum FontError: LocalizedError {
        case invalidFont(String, CGFloat)
    
        var errorDescription: String? {
            switch self {
            case .invalidFont(let name, let size):
                return "Could not create font with name \(name) and size: \(size)"
            }
        }
    }
    
    class Font {
        private(set) var name: String
        private(set) var size: CGFloat
    
        private(set) var ascender: CGFloat
        private(set) var descender: CGFloat
        private(set) var xHeight: CGFloat
    
        var maxHeight: CGFloat {
            return self.ascender + fabs(self.descender)
        }
    
        init(name: String, size: CGFloat) throws {
            // check if font can be created, otherwise crash
            // set ascender, descender, etc...
            #if os(macOS)
            guard let font = NSFont(name: name, size: size) else {
                throw FontError.invalidFont(name, size)
            }
    
            self.ascender = font.ascender
            self.descender = font.descender
            self.xHeight = font.xHeight
            #endif
    
            #if os(iOS)
            guard let font = UIFont(name: name, size: size) else {
                throw FontError.invalidFont(name, size)
            }
            self.ascender = font.ascender
            self.descender = font.descender
            self.xHeight = font.xHeight
            #endif
    
            self.name = name
            self.size = size
        }
    }
    

    I then use the maxHeight in my label position calculations. So for example in my custom ButtonNode (a SKShapeNode that contains a SKLabelNode) I do the following (simplified):

    // in the constructor of my custom 
    // button node ...
    let height = 30
    
    self.label = SKLabelNode(text: "Back")
    // in order to position properly, set 
    // vertical alignment to baseline
    self.label.verticalAlignmentMode = .baseline
    
    let font = try Font(name: self.label.fontName!, size: self.label.fontSize)
    // depending on the font you might want to add 
    // an additional y offset to place labels 
    // higher or lower within the node
    let yOffset = (height - font.maxHeight) / 2)
    let labelWidth = self.label.calculateAccumulatedFrame().width
    
    let labelFrame = CGRect(
        x: 0,
        y: 0,
        width: labelWidth,
        height: height
    )
    
    self.path = CGPath(roundedRect: labelFrame, cornerWidth: 5, cornerHeight: 5, transform: nil)
    
    self.label.position = CGPoint(x: labelFrame.width / 2, y: yOffset)
    

    P.S.: If you prefer to not create a separate Font class and you still want the code to work properly on iOS and macOS, you could just create an typealias for NSFont and UIFont. Since you can use the same constructor, you'd only need to add an extension to calculate the max height of the font (ascender + abs(descender)).

    I prefer to create a class so I can throw an error if the font could not be created properly.

    0 讨论(0)
提交回复
热议问题