Sample code for creating a NSTextField “label”?

前端 未结 6 1013
灰色年华
灰色年华 2020-12-24 01:53

In my desktop Mac OS X app, I\'d like to programatically create a NSTextField \"label\" which has the same behavior and properties as a typical label created in Interface Bu

相关标签:
6条回答
  • 2020-12-24 02:20

    Specifically, you will want to setBordered:NO, and set the bezel style to whatever that bezel style is which I forgot. Also setEditable:NO, and optionally setSelectable:NO. That should suffice.

    0 讨论(0)
  • 2020-12-24 02:21

    A label is actually an instance of NSTextField, a subclass of NSView. So, since it is a NSView, it has to be added to another view.

    Here's a working code:

    - (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];
    }
    
    0 讨论(0)
  • 2020-12-24 02:25

    macOS 10.12 and Later

    Starting with macOS 10.12 (Sierra), there are three new NSTextField constructors:

    • NSTextField(labelWithString:), which the header file comment says “Creates a non-wrapping, non-editable, non-selectable text field that displays text in the default system font.”

    • NSTextField(wrappingLabelWithString:), which the header file comment says “Creates a wrapping, non-editable, selectable text field that displays text in the default system font.”

    • NSTextField(labelWithAttributedString:), which the header file comment says “Creates a non-editable, non-selectable text field that displays attributed text. The line break mode of this field is determined by the attributed string's NSParagraphStyle attribute.”

    I tested the ones that take a plain (non-attributed string), and they create text fields that are similar to, but not precisely the same as, the text fields created in a storyboard or xib.

    The important difference is that both constructors create a text field with textBackgroundColor (normally pure white) as its background color, while the storyboard text field uses controlColor (normally about 90% white).

    Unimportantly, both constructors also set their fonts by calling NSFont.systemFont(ofSize: 0) (which produces a different NSFont object than my code below, but they wrap the same underlying Core Text font).

    The wrappingLabelWithString: constructor sets the field's isSelectable to true. (This is documented in the header file.)


    macOS 10.11 and Earlier

    I compared four NSTextField instances: one created by dragging a “Label” to a storyboard, another created by dragging a “Wrapping Label” to a storyboard, and two in code. Then I carefully modified properties of the code-created labels until all their properties were exactly the same as the storyboard-created labels. These two methods are the result:

    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
        }
    
    }
    

    If you use one of these methods, don't forget to set your field's frame, or turn off its translatesAutoresizingMaskIntoConstraints and add constraints.


    Here is the code I used to compare the different text fields, in case you want to check:

    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: " "))
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-24 02:31

    Disassembled AppKit in 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 讨论(0)
  • 2020-12-24 02:34

    You could try using nib2objc to get all the properties that IB sets

    0 讨论(0)
  • 2020-12-24 02:36

    This can be tricky to get right. I don't have the recipe for an exact replica handy, but when I've been stuck in a similar situation, here's what I do:

    1. Create a UI element in IB.
    2. Add an outlet to it from my controller class.
    3. Break in gdb in awakeFromNib or whatever.
    4. From the gdb prompt, "p *whateverOutlet" ... this will show you the C struct contents of the label NSTextField that IB set up.

    By looking at all the myriad values in there, you can get a lot of guesses about what you're neglecting to set. Usually it ends up being some magic combination of bezel and border settings, that gets you where you want to be.

    0 讨论(0)
提交回复
热议问题