web-dev-qa-db-fra.com

Exemple de code pour la création d'un "label" NSTextField?

Dans mon application de bureau Mac OS X, je souhaite créer par programme une "étiquette" NSTextField ayant le même comportement et les mêmes propriétés qu'une étiquette typique créée dans Interface Builder.

J'utilise habituellement (et ressemble beaucoup à) IB, mais dans ce cas il doit être fait par programme.

Malgré tous mes efforts, je n'arrive pas à trouver la combinaison d'appels de méthode qui produira par programme le même comportement label-y qu'un label, tiré de la palette Bibliothèque de vues IB.

Quelqu'un peut-il fournir ou indiquer un exemple de code indiquant comment procéder par programme? THX.

49
Todd Ditchendorf

Une étiquette est en fait une instance de NSTextField , une sous-classe de NSView. Donc, puisqu'il s'agit d'une NSView, il faut l'ajouter à une autre vue.

Voici un code de travail:

- (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 et versions ultérieures

À partir de macOS 10.12 (Sierra), il existe trois nouveaux constructeurs NSTextField:

  • NSTextField(labelWithString:), dont le commentaire dans le fichier d'en-tête indique "Crée un champ de texte non modifiable, non modifiable, qui affiche le texte dans la police système par défaut." 

  • NSTextField(wrappingLabelWithString:), dont le commentaire dans le fichier d'en-tête indique "Crée un champ de texte enveloppant, non modifiable et sélectionnable, qui affiche le texte dans la police système par défaut."

  • NSTextField(labelWithAttributedString:), dont le commentaire dans le fichier d'en-tête indique «Crée un champ de texte non modifiable et non sélectionnable qui affiche le texte attribué. Le mode de saut de ligne de ce champ est déterminé par l'attribut NSParagraphStyle de la chaîne attribuée. ”

J'ai testé ceux qui prennent une chaîne (chaîne non attribuée) et ils créent des champs de texte similaires, mais pas exactement identiques, aux champs de texte créés dans un storyboard ou un xib.

La différence importante est que les deux constructeurs créent un champ de texte avec textBackgroundColor (normalement du blanc pur) comme couleur d’arrière-plan, tandis que le champ de texte du storyboard utilise controlColor (normalement environ 90% de blanc).

Sans importance, les deux constructeurs définissent également leurs polices en appelant NSFont.systemFont(ofSize: 0) (ce qui produit un objet NSFont différent de celui de mon code ci-dessous, mais ils enveloppent la même police Core Text sous-jacente).

Le constructeur wrappingLabelWithString: définit la variable isSelectable du champ sur true. (Ceci est documenté dans le fichier d'en-tête.)


macOS 10.11 et versions antérieures

J'ai comparé quatre instances NSTextField: une créée en faisant glisser une «étiquette» vers un story-board, une autre créée en faisant glisser une «étiquette d'habillage» vers un storyboard, et deux dans le code. Ensuite, j'ai soigneusement modifié les propriétés des étiquettes créées par code jusqu'à ce que toutes leurs propriétés soient exactement les mêmes que celles des étiquettes créées par le storyboard. Ces deux méthodes sont le résultat:

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
    }

}

Si vous utilisez l'une de ces méthodes, n'oubliez pas de définir le cadre de votre champ, ou désactivez sa translatesAutoresizingMaskIntoConstraints et ajoutez des contraintes.


Voici le code que j'ai utilisé pour comparer les différents champs de texte, au cas où vous voudriez vérifier:

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: " "))
        }
    }
}
17
rob mayoff

Cela peut être difficile à obtenir. Je n'ai pas la recette pour une réplique exacte à portée de main, mais quand je suis coincé dans une situation similaire, voici ce que je fais:

  1. Créez un élément d'interface utilisateur dans IB.
  2. Ajoutez-lui un point de vente à partir de ma classe de contrôleur.
  3. Briser dans gdb dans awakeFromNib ou autre chose.
  4. Dans l'invite gdb, "p * WhateverOutlet" ..., le contenu de la structure C du libellé NSTextField que l'IB a configuré s'affiche.

En examinant toutes les myriades de valeurs qu’il contient, vous pouvez deviner beaucoup de choses sur ce que vous négligez de définir. Habituellement, il s’agit d’une combinaison magique de paramètres de cadre et de bordure, qui vous permet de vous rendre où vous voulez.

8
danielpunkass

Vous pouvez essayer d’utiliser nib2objc pour obtenir toutes les propriétés définies par IB.

5
g-Off

Plus précisément, vous souhaiterez setBordered:NO et définir le style de la lunette sur le style que j'ai oublié Aussi setEditable:NO, et éventuellement setSelectable:NO. Cela devrait suffire.

2
Steven Degutis

AppKit désassemblé en 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
0
Vadim