私のデスクトップMac OS Xアプリでは、Interface Builderで作成された一般的なラベルと同じ動作とプロパティを持つNSTextFieldの「ラベル」をプログラムで作成したいと思います。
私は通常IBを使用しますが、この場合はmustをプログラムで実行します。
私が試してみると、IB View Libraryパレットからドラッグされた「Label」と同じlabel-y動作をプログラムで生成するメソッド呼び出しの組み合わせを見つけることができないようです。
誰でもプログラムでこれを行う方法のサンプルコードを提供または指摘できますか? THX。
ラベルは、実際にはNSViewのサブクラスである NSTextField のインスタンスです。したがって、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以降(Sierra)、3つの新しいNSTextField
コンストラクターがあります。
NSTextField(labelWithString:)
。ヘッダーファイルのコメントには、「デフォルトのシステムフォントでテキストを表示する、ラッピング、編集、選択ができないテキストフィールドが作成されます。」
NSTextField(wrappingLabelWithString:)
。ヘッダーファイルのコメントには、「デフォルトのシステムフォントでテキストを表示する、ラップ可能で編集不可能な選択可能なテキストフィールドを作成する」と書かれています。
NSTextField(labelWithAttributedString:)
。ヘッダーファイルのコメントには、「属性付きテキストを表示する編集不可、選択不可のテキストフィールドを作成します。このフィールドの改行モードは、属性付き文字列のNSParagraphStyle属性によって決定されます。
プレーン(属性のない文字列)を使用するものをテストし、ストーリーボードまたはxibで作成されたテキストフィールドに似ていますが、正確に同じではないテキストフィールドを作成します。
重要な違いは、両方のコンストラクターがtextBackgroundColor
(通常は純粋な白)を背景色としてテキストフィールドを作成し、ストーリーボードのテキストフィールドがcontrolColor
(通常約90%の白)を使用することです。
重要なことではありませんが、両方のコンストラクターはNSFont.systemFont(ofSize: 0)
(以下の私のコードとは異なるNSFont
オブジェクトを生成しますが、基盤となる同じコアテキストフォントをラップします)を呼び出してフォントを設定します。
wrappingLabelWithString:
コンストラクターは、フィールドのisSelectable
をtrue
に設定します。 (これはヘッダーファイルに記載されています。)
4つのNSTextField
インスタンスを比較しました。1つは「ラベル」をストーリーボードにドラッグして作成され、もう1つは「ラッピングラベル」をストーリーボードにドラッグして作成され、2つはコードです。次に、コード作成ラベルのプロパティをすべてストーリーボード作成ラベルとまったく同じになるまで慎重に変更しました。次の2つの方法が結果です。
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: " "))
}
}
}
これを正しく行うには注意が必要です。正確なレプリカのレシピは手元にありませんが、同様の状況に陥っているときは、次のようにします。
そこにある無数の値をすべて見ると、設定し忘れているものについて多くの推測を得ることができます。通常は、ベゼルと境界線の設定の魔法の組み合わせになり、目的の場所に移動します。
nib2objc を使用して、IBが設定するすべてのプロパティを取得できます。
具体的には、setBordered:NO
、ベゼルのスタイルを忘れたベゼルのスタイルに設定します。また、setEditable:NO
、およびオプションでsetSelectable:NO
。それで十分です。
Objective-Cで逆アセンブルされたAppKit:
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