ixany
ixany

Reputation: 6040

NSTextField margin and padding? (Swift)

I am wondering if there’s a possibility to set a margin or padding for NSTextField?

I achieved a more or less custom looking textField (the first one in this screenshot)...

enter image description here

... using this code:

myTextField.wantsLayer = true
myTextField.layer?.cornerRadius = 2.0
myTextField.layer?.borderWidth = 1.0
myTextField.layer?.borderColor = CGColor(red: 0.69, green: 0.69, blue: 0.69, alpha: 1.0)

However, it feels like I have to add some padding on the left side so that the numbers are not too close to the border. Is that even possible?

Upvotes: 3

Views: 4996

Answers (3)

Yoshiharu Yamada
Yoshiharu Yamada

Reputation: 111

In addition to the @robotspacer 's great answer, I also added a little bit more implementation for working with a wrapped textfield.

class PaddingTextFieldCell: NSTextFieldCell {
    
    /// The paadding size. The default value is `CGSize(width:0, height: 0)`
    @IBInspectable var padding: CGSize = .init(width: 0, height: 0)
    
    override func cellSize(forBounds rect: NSRect) -> NSSize {
        var size = super.cellSize(forBounds: rect)
        size.height += padding.height * 2
        size.width += padding.width * 2
        return size
    }
    
    override func drawingRect(forBounds rect: NSRect) -> NSRect {
        let rect = super.drawingRect(forBounds: rect)
        return rect.insetBy(dx: padding.width, dy: padding.height)
    }
    
    override func titleRect(forBounds rect: NSRect) -> NSRect {
        let rect = super.titleRect(forBounds: rect)
        return rect.insetBy(dx: padding.width, dy: padding.height)
    }
    
    override func edit(withFrame rect: NSRect, in controlView: NSView, editor textObj: NSText, delegate: Any?, event: NSEvent?) {
        let insetRect = rect.insetBy(dx: padding.width, dy: padding.height)
        super.edit(withFrame: insetRect, in: controlView, editor: textObj, delegate: delegate, event: event)
    }
    
    override func select(withFrame rect: NSRect, in controlView: NSView, editor textObj: NSText, delegate: Any?, start selStart: Int, length selLength: Int) {
        let insetRect = rect.insetBy(dx: padding.width, dy: padding.height)
        super.select(withFrame: insetRect, in: controlView, editor: textObj, delegate: delegate, start: selStart, length: selLength)
    }
    
    override func drawInterior(withFrame cellFrame: NSRect, in controlView: NSView) {
        let insetRect = cellFrame.insetBy(dx: padding.width, dy: padding.height)
        super.drawInterior(withFrame: insetRect, in: controlView)
    }
    
}

I've added drawingRect(forBounds:). This works fine with a wrapped textfield even where I implement auto-growing.

Upvotes: 0

robotspacer
robotspacer

Reputation: 2762

There's no simple and obvious way to do this, but like many things in AppKit, it's not too hard once you figure out exactly what you need to subclass. I came across this example by Hem Dutt and in my testing on macOS 10.12 this approach seems to work well. In short, you just need to subclass NSTextFieldCell and override a few methods to change the text frame. Here's a variation in Swift 3:

class CustomTextFieldCell: NSTextFieldCell {

    private static let padding = CGSize(width: 4.0, height: 2.0)

    override func cellSize(forBounds rect: NSRect) -> NSSize {
        var size = super.cellSize(forBounds: rect)
        size.height += (CustomTextFieldCell.padding.height * 2)
        return size
    }

    override func titleRect(forBounds rect: NSRect) -> NSRect {
        return rect.insetBy(dx: CustomTextFieldCell.padding.width, dy: CustomTextFieldCell.padding.height)
    }

    override func edit(withFrame rect: NSRect, in controlView: NSView, editor textObj: NSText, delegate: Any?, event: NSEvent?) {
        let insetRect = rect.insetBy(dx: CustomTextFieldCell.padding.width, dy: CustomTextFieldCell.padding.height)
        super.edit(withFrame: insetRect, in: controlView, editor: textObj, delegate: delegate, event: event)
    }

    override func select(withFrame rect: NSRect, in controlView: NSView, editor textObj: NSText, delegate: Any?, start selStart: Int, length selLength: Int) {
        let insetRect = rect.insetBy(dx: CustomTextFieldCell.padding.width, dy: CustomTextFieldCell.padding.height)
        super.select(withFrame: insetRect, in: controlView, editor: textObj, delegate: delegate, start: selStart, length: selLength)
    }

    override func drawInterior(withFrame cellFrame: NSRect, in controlView: NSView) {
        let insetRect = cellFrame.insetBy(dx: CustomTextFieldCell.padding.width, dy: CustomTextFieldCell.padding.height)
        super.drawInterior(withFrame: insetRect, in: controlView)
    }

}

I made one notable change from the original—overriding cellSize(forBounds:) to increase the minimum height of the cell. I'm using Auto Layout to size the cell automatically, so without that override, my text was getting clipped.

Upvotes: 12

creeperspeak
creeperspeak

Reputation: 5523

I usually do this just by putting the textField in a container view. You can give the view any corner radius, border, etc you want, and then just constrain the textField to the inside with whatever padding you want. Just make sure you change the textField's border style to 'none' or you'll be able to see it inside your view.

There might be a way to change the content inset like you can with a button, but I've never been able to find it.

Upvotes: 3

Related Questions