In a certain height NSTextField , the layout would be like this if text's size is small
the text is flowing to the top ,how to make the text center like this:
In a certain height NSTextField , the layout would be like this if text's size is small
the text is flowing to the top ,how to make the text center like this:
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)
}
}
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).
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];
}
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);
}
}
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)
}
}
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];
}