Пример кода для создания "метки" 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: " "))
        }
    }
}

Это может быть сложно получить право. У меня нет рецепта для точной реплики под рукой, но когда я застрял в подобной ситуации, вот что я делаю:

  1. Создайте элемент пользовательского интерфейса в IB.
  2. Добавьте к нему розетку из моего класса контроллеров.
  3. Взломать GDB в awakeFromNib или что-то еще.
  4. Из приглашения 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, Этого должно быть достаточно.

Другие вопросы по тегам