Пример кода для создания "метки" NSTextField?
В моем настольном приложении Mac OS X я хотел бы программно создать "метку" NSTextField, которая имеет то же поведение и свойства, что и обычная метка, созданная в Interface Builder.
Я обычно использую (и очень люблю) IB, но в этом случае это должно быть сделано программно.
Как ни старайся, я не могу найти комбинацию вызовов методов, которые программно будут вызывать такое же поведение label-y, как и "Метка", перетаскиваемая из палитры IB View Library.
Может кто-нибудь предоставить или указать пример кода, как это сделать программно? Спасибо.
6 ответов
Метка на самом деле является экземпляром NSTextField, подкласса NSView. Итак, поскольку это NSView, его нужно добавить в другое представление.
Вот рабочий код:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSTextField *textField;
textField = [[NSTextField alloc] initWithFrame:NSMakeRect(10, 10, 200, 17)];
[textField setStringValue:@"My Label"];
[textField setBezeled:NO];
[textField setDrawsBackground:NO];
[textField setEditable:NO];
[textField setSelectable:NO];
[view addSubview:textField];
}
macOS 10.12 и новее
Начиная с macOS 10.12 (Sierra), появилось три новых NSTextField
конструкторы:
NSTextField(labelWithString:)
, который в комментарии к заголовочному файлу гласит: "Создает неразворачиваемое, не редактируемое, не выбираемое текстовое поле, которое отображает текст системным шрифтом по умолчанию"NSTextField(wrappingLabelWithString:)
, который в комментарии к заголовочному файлу гласит: "Создает переносимое, не редактируемое, выбираемое текстовое поле, отображающее текст системным шрифтом по умолчанию".NSTextField(labelWithAttributedString:)
, который в комментарии к заголовочному файлу гласит: "Создает не редактируемое, не выбираемое текстовое поле, в котором отображается атрибутивный текст. Режим разрыва строки в этом поле определяется атрибутом NSParagraphStyle приписанной строки ".
Я протестировал те, которые берут простую (не приписываемую строку), и они создают текстовые поля, которые похожи, но не точно совпадают с текстовыми полями, созданными в раскадровке или XIB.
Важным отличием является то, что оба конструктора создают текстовое поле с textBackgroundColor
(обычно чисто белый) в качестве цвета фона, в то время как текстовое поле раскадровки использует controlColor
(обычно около 90% белого).
Неважно, что оба конструктора также устанавливают свои шрифты, вызывая NSFont.systemFont(ofSize: 0)
(который производит другой NSFont
объект, чем мой код ниже, но они обертывают тот же базовый шрифт основного текста).
wrappingLabelWithString:
конструктор устанавливает поле isSelectable
в true
, (Это задокументировано в заголовочном файле.)
macOS 10.11 и ранее
Я сравнил четыре NSTextField
экземпляры: один создан путем перетаскивания метки на раскадровку, другой - путем перетаскивания метки обертки на раскадровку, а два - в коде. Затем я тщательно изменил свойства меток, созданных в коде, пока все их свойства не стали точно такими же, как метки, созданные в раскадровке. Эти два метода являются результатом:
extension NSTextField {
/// Return an `NSTextField` configured exactly like one created by dragging a “Label” into a storyboard.
class func newLabel() -> NSTextField {
let label = NSTextField()
label.isEditable = false
label.isSelectable = false
label.textColor = .labelColor
label.backgroundColor = .controlColor
label.drawsBackground = false
label.isBezeled = false
label.alignment = .natural
label.font = NSFont.systemFont(ofSize: NSFont.systemFontSize(for: label.controlSize))
label.lineBreakMode = .byClipping
label.cell?.isScrollable = true
label.cell?.wraps = false
return label
}
/// Return an `NSTextField` configured exactly like one created by dragging a “Wrapping Label” into a storyboard.
class func newWrappingLabel() -> NSTextField {
let label = newLabel()
label.lineBreakMode = .byWordWrapping
label.cell?.isScrollable = false
label.cell?.wraps = true
return label
}
}
Если вы используете один из этих методов, не забудьте установить фрейм поля или отключить его translatesAutoresizingMaskIntoConstraints
и добавить ограничения.
Вот код, который я использовал для сравнения различных текстовых полей, если вы хотите проверить:
import Cocoa
class ViewController: NSViewController {
@IBOutlet var label: NSTextField!
@IBOutlet var multilineLabel: NSTextField!
override func loadView() {
super.loadView()
}
override func viewDidLoad() {
super.viewDidLoad()
let codeLabel = NSTextField.newLabel()
let codeMultilineLabel = NSTextField.newWrappingLabel()
let labels = [label!, codeLabel, multilineLabel!, codeMultilineLabel]
for keyPath in [
"editable",
"selectable",
"allowsEditingTextAttributes",
"importsGraphics",
"textColor",
"preferredMaxLayoutWidth",
"backgroundColor",
"drawsBackground",
"bezeled",
"bezelStyle",
"bordered",
"enabled",
"alignment",
"font",
"lineBreakMode",
"usesSingleLineMode",
"formatter",
"baseWritingDirection",
"allowsExpansionToolTips",
"controlSize",
"highlighted",
"continuous",
"cell.opaque",
"cell.controlTint",
"cell.backgroundStyle",
"cell.interiorBackgroundStyle",
"cell.scrollable",
"cell.truncatesLastVisibleLine",
"cell.wraps",
"cell.userInterfaceLayoutDirection"
] {
Swift.print(keyPath + " " + labels.map({ ($0.value(forKeyPath: keyPath) as? NSObject)?.description ?? "nil" }).joined(separator: " "))
}
}
}
Это может быть сложно получить право. У меня нет рецепта для точной реплики под рукой, но когда я застрял в подобной ситуации, вот что я делаю:
- Создайте элемент пользовательского интерфейса в IB.
- Добавьте к нему розетку из моего класса контроллеров.
- Взломать GDB в awakeFromNib или что-то еще.
- Из приглашения gdb "p *whatOutlet" ... это покажет вам содержимое структуры C метки NSTextField, которую IB установил.
Просматривая все бесчисленные значения, вы можете получить множество предположений о том, что вы не хотите устанавливать. Обычно это волшебная комбинация настроек рамок и границ, которая доставляет вас туда, куда вы хотите.
Вы можете попробовать использовать nib2objc, чтобы получить все свойства, которые устанавливает IB
Разобрали AppKit в Objective-C:
BOOL TMPSierraOrLater() {
static BOOL result = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
result = [NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){ 10, 12, 0 }];
});
return result;
}
@implementation NSTextField (TMP)
+ (instancetype)TMP_labelWithString:(NSString *)stringValue {
if (TMPSierraOrLater()) {
return [self labelWithString:stringValue];
}
NSParameterAssert(stringValue);
NSTextField *label = [NSTextField TMP_newBaseLabelWithoutTitle];
label.lineBreakMode = NSLineBreakByClipping;
label.selectable = NO;
[label setContentHuggingPriority:(NSLayoutPriorityDefaultLow + 1) forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentHuggingPriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
label.stringValue = stringValue;
[label sizeToFit];
return label;
}
+ (instancetype)TMP_wrappingLabelWithString:(NSString *)stringValue {
if (TMPSierraOrLater()) {
return [self wrappingLabelWithString:stringValue];
}
NSParameterAssert(stringValue);
NSTextField *label = [NSTextField TMP_newBaseLabelWithoutTitle];
label.lineBreakMode = NSLineBreakByWordWrapping;
label.selectable = YES;
[label setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentHuggingPriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
label.stringValue = stringValue;
label.preferredMaxLayoutWidth = 0;
[label sizeToFit];
return label;
}
+ (instancetype)TMP_labelWithAttributedString:(NSAttributedString *)attributedStringValue {
if (CRKSierraOrLater()) {
return [self labelWithAttributedString:attributedStringValue];
}
NSParameterAssert(attributedStringValue);
NSTextField *label = [NSTextField TMP_newBaseLabelWithoutTitle];
[label setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentHuggingPriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];
[label setContentCompressionResistancePriority:NSLayoutPriorityDefaultHigh forOrientation:NSLayoutConstraintOrientationVertical];
label.attributedStringValue = attributedStringValue;
[label sizeToFit];
return label;
}
#pragma mark - Private API
+ (instancetype)TMP_newBaseLabelWithoutTitle {
NSTextField *label = [[self alloc] initWithFrame:CGRectZero];
label.textColor = NSColor.labelColor;
label.font = [NSFont systemFontOfSize:0.0];
label.alignment = NSTextAlignmentNatural;
label.baseWritingDirection = NSWritingDirectionNatural;
label.userInterfaceLayoutDirection = NSApp.userInterfaceLayoutDirection;
label.enabled = YES;
label.bezeled = NO;
label.bordered = NO;
label.drawsBackground = NO;
label.continuous = NO;
label.editable = NO;
return label;
}
@end
В частности, вы захотите setBordered:NO
и установите стиль рамки на тот стиль, который я забыл. Также setEditable:NO
и опционально setSelectable:NO
, Этого должно быть достаточно.