25
votes

In a certain height NSTextField , the layout would be like this if text's size is small

enter image description here

the text is flowing to the top ,how to make the text center like this:

enter image description here

6
Possible duplicate of stackoverflow.com/questions/1235219/…, take a look at it!Vervious
@Vervious Thanks, it works, but if the text in NSTextField is selected, it back to top-aligned again.Mil0R3
@Veelian Any luck with this? I get the same thing where when the user enters text it is top-aligned and then when they click on say another field then it jumps back to centeredkdbdallas
@Vervious Sorry for not good solution for it.Mil0R3

6 Answers

11
votes
class CustomTextFieldCell: NSTextFieldCell {

    func adjustedFrame(toVerticallyCenterText rect: NSRect) -> NSRect {
        // super would normally draw text at the top of the cell
        var titleRect = super.titleRect(forBounds: rect)

        let minimumHeight = self.cellSize(forBounds: rect).height
        titleRect.origin.y += (titleRect.height - minimumHeight) / 2
        titleRect.size.height = minimumHeight

        return titleRect
    }

    override func edit(withFrame rect: NSRect, in controlView: NSView, editor textObj: NSText, delegate: Any?, event: NSEvent?) {
        super.edit(withFrame: adjustedFrame(toVerticallyCenterText: rect), 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) {
        super.select(withFrame: adjustedFrame(toVerticallyCenterText: rect), in: controlView, editor: textObj, delegate: delegate, start: selStart, length: selLength)
    }

    override func drawInterior(withFrame cellFrame: NSRect, in controlView: NSView) {
        super.drawInterior(withFrame: adjustedFrame(toVerticallyCenterText: cellFrame), in: controlView)
    }

    override func draw(withFrame cellFrame: NSRect, in controlView: NSView) {
        super.draw(withFrame: cellFrame, in: controlView)
    }
}
8
votes

I'm basically using Alex's answer, with one amendment: We should decrease the height of the titleRect if we increase the y-origin. Otherwise our titleRect will end below the end its superview, possibly hiding any content below the NSTextField.

The .h:

#import <Cocoa/Cocoa.h>

@interface VerticallyCenteredTextFieldCell : NSTextFieldCell

@end

The .m:

#import "VerticallyCenteredTextFieldCell.h"

@implementation VerticallyCenteredTextFieldCell

- (NSRect) titleRectForBounds:(NSRect)frame {

CGFloat stringHeight = self.attributedStringValue.size.height;
NSRect titleRect = [super titleRectForBounds:frame];
CGFloat oldOriginY = frame.origin.y;
titleRect.origin.y = frame.origin.y + (frame.size.height - stringHeight) / 2.0;
titleRect.size.height = titleRect.size.height - (titleRect.origin.y - oldOriginY);
return titleRect;
}

- (void) drawInteriorWithFrame:(NSRect)cFrame inView:(NSView*)cView {
[super drawInteriorWithFrame:[self titleRectForBounds:cFrame] inView:cView];
}

@end

Also, VerticallyCenteredTextFieldCell is a subclass of NSTextFieldCell. If you subclass it, change the class of the NSTextFieldCell in Interface Builder, not the NSTextField (I didn't know this).

enter image description here

6
votes

Just subclass NSTextFieldCell (or swizzle it) ... and implement...

- (NSRect) titleRectForBounds:(NSRect)frame {  

  CGFloat stringHeight       = self.attributedStringValue.size.height;
   NSRect titleRect          = [super titleRectForBounds:frame];
          titleRect.origin.y = frame.origin.y + 
                              (frame.size.height - stringHeight) / 2.0;
   return titleRect;
 }
- (void) drawInteriorWithFrame:(NSRect)cFrame inView:(NSView*)cView {
  [super drawInteriorWithFrame:[self titleRectForBounds:cFrame] inView:cView];
 }
3
votes

Adding a little to Erik's and Hejazi's answer, you also need to implement selectWithFrame, here is the solution in Swift 2.2:

class VerticallyCenteredTextFieldCell: NSTextFieldCell {
    override func titleRectForBounds(rect: NSRect) -> NSRect {
        var titleRect = super.titleRectForBounds(rect)

        let minimumHeight = self.cellSizeForBounds(rect).height
        titleRect.origin.y += (titleRect.height - minimumHeight) / 2
        titleRect.size.height = minimumHeight

        return titleRect
    }

    override func drawInteriorWithFrame(cellFrame: NSRect, inView controlView: NSView) {
        super.drawInteriorWithFrame(titleRectForBounds(cellFrame), inView: controlView)
    }

    override func selectWithFrame(aRect: NSRect, inView controlView: NSView, editor textObj: NSText, delegate anObject: AnyObject?, start selStart: Int, length selLength: Int) {
        super.selectWithFrame(titleRectForBounds(aRect), inView: controlView, editor: textObj, delegate: anObject, start: selStart, length: selLength);
    }
}
2
votes

Swift version of Erik Wegener's answer (also modified to work with multiple lines too):

class VerticallyCenteredTextFieldCell : NSTextFieldCell {
    override func titleRect(forBounds rect: NSRect) -> NSRect {
        var titleRect = super.titleRect(forBounds: rect)

        let minimumHeight = self.cellSize(forBounds: rect).height
        titleRect.origin.y += (titleRect.height - minimumHeight) / 2
        titleRect.size.height = minimumHeight

        return titleRect
    }

    override func drawInterior(withFrame cellFrame: NSRect, in controlView: NSView) {
        super.drawInterior(withFrame: titleRect(forBounds: cellFrame), in: controlView)
    }
}
0
votes

My solution is based to calculate font height using NSLayoutManager. This way allow more performance that calculate height using methods of attributed string.

- (NSRect)titleRectForBounds:(NSRect)theRect {
    NSRect titleFrame = [super titleRectForBounds:theRect];

    CGFloat fontHeight = [[[NSLayoutManager alloc] init] defaultLineHeightForFont:self.font];
    if (fontHeight < titleFrame.size.height) {
        titleFrame.origin.y = theRect.origin.y + (theRect.size.height - fontHeight) * 0.5f;
        titleFrame.size.height = fontHeight;
    }
    return titleFrame;
}

- (void) drawInteriorWithFrame:(NSRect)frame inView:(NSView *)view {
    [super drawInteriorWithFrame:[self titleRectForBounds:frame] inView:view];
}