如何在 Cocoa MacOS objective C 中为 NSSavePanel 创建自定义 NSView?

How to create a custom NSView for NSSavePanel in Cocoa MacOS objective C?

我需要在 NSSavePanel 旁边添加一个带有文本标签的保存扩展选择器。在随附的屏幕截图中,我尝试证明我成功地使用函数 setAccessoryViewNSComboBox 添加到我的面板。但是我不知道如何创建自定义 NSView,其中包括 NSComboBoxNSTextView 或等效项。我在 Internet 上找不到任何教程(或者如果我找到一个它已经非常过时了)显示如何在 MacOS 上的 Cocoa 中的 objective-C 中创建自定义 NSViews。

如何创建包含组合框和文本标签的自定义 NSView?或者如何将两个“库存”NSView 添加到同一个 NSSavePanel?请在您的回答中尽可能详细,因为我的 objective-c 经验非常有限。

按 Cmd-N 将新文件添加到您的项目中。选择一个视图文件以添加具有自定义视图的 xib 文件。

打开xib文件,将控件添加到自定义视图中。按项目 window 工具栏中的添加按钮访问用户界面元素。

使用 NSNib class 加载 xib 文件并获取自定义视图。

您询问了如何在 Objective-C 中创建一个 NSView 并将 NSTextFieldNSComboBox 作为子视图。

基本上,您可以在 Interface Builder 中定义它们,并以编程方式将 Objective-C 中的结果视图设置为 NSSavePanelaccessoryView。或者,自定义 NSView 可以完全在 Objective-C 中创建,这可能是这里更简单的选择。

实例化一个NSView后,您可以使用addSubview:相应地添加一个NSTextField和一个NSComboBox。然后您可以使用 NSLayoutConstraints 设置自动布局,它负责调整 accessoryView 的大小并根据对话框的宽度正确排列子视图。

如果以编程方式创建视图并使用自动布局,则必须将 translatesAutoresizingMaskIntoConstraints 显式设置为 NO

如果您想设置 allowedContentTypes,通过 NSDictionary 将显示的扩展名文本映射到 UTType 可能会有用。

如果您将 NSComboBox 的委托设置为 self,那么您将通过 comboBoxSelectionDidChange: 收到有关 NSComboBox 中用户选择更改的通知。

如果所讨论的内容在代码中得到了适当的实现,self-contained 示例可能看起来像这样:

#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#import "ViewController.h"

@interface ViewController () <NSComboBoxDelegate>

@property (nonatomic, strong) NSSavePanel *savePanel;
@property (nonatomic, strong) NSDictionary<NSString *, UTType*> *typeMapping;

@end


@implementation ViewController

- (instancetype)initWithCoder:(NSCoder *)coder {
    if (self = [super initWithCoder:coder]) {
        _typeMapping = @{
            @"jpeg": UTTypeJPEG,
            @"png": UTTypePNG,
            @"tiff": UTTypeTIFF
        };
    }
    return self;
}

- (NSView *)accessoryView {
    NSTextField *label = [NSTextField labelWithString:@"Filetypes:"];
    label.textColor = NSColor.lightGrayColor;
    label.font = [NSFont systemFontOfSize:NSFont.smallSystemFontSize];
    label.alignment = NSTextAlignmentRight;
    
    NSComboBox *comboBox = [NSComboBox new];
    comboBox.editable = NO;
    for (NSString *extension in self.typeMapping.allKeys) {
        [comboBox addItemWithObjectValue:extension];
    }
    [comboBox setDelegate:self];
    
    NSView *view = [NSView new];
    [view addSubview:label];
    [view addSubview:comboBox];
    
    comboBox.translatesAutoresizingMaskIntoConstraints = NO;
    label.translatesAutoresizingMaskIntoConstraints = NO;
    
    [NSLayoutConstraint activateConstraints:@[
        [label.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:-12],
        [label.widthAnchor constraintEqualToConstant:64.0],
        [label.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:0.0],

        [comboBox.topAnchor constraintEqualToAnchor:view.topAnchor constant:8.0],
        [comboBox.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:8.0],
        [comboBox.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:-8.0],
        [comboBox.trailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:-20.0],
    ]];
    return view;
}

- (void)comboBoxSelectionDidChange:(NSNotification *)notification {
    NSComboBox *comboBox = notification.object;
    NSString *selectedItem = comboBox.objectValueOfSelectedItem;
    NSLog(@"### set allowedContentTypes to %@ (%@)", selectedItem, self.typeMapping[selectedItem]);
    [self.savePanel setAllowedContentTypes:@[ self.typeMapping[selectedItem] ]];
}

- (IBAction)onSave:(id)sender {
    NSWindow *window = NSApplication.sharedApplication.windows.firstObject;
    self.savePanel = [NSSavePanel new];
    self.savePanel.accessoryView = [self accessoryView];
    [self.savePanel beginSheetModalForWindow:window completionHandler:^(NSModalResponse result) {
        if (result != NSModalResponseOK) {
            return;
        }
        NSURL *fileURL = self.savePanel.URL;
        NSLog(@"### selectedFile: %@", fileURL);
    }];
}

- (void)setRepresentedObject:(id)representedObject {
    [super setRepresentedObject:representedObject];
}

@end

最后,上面的演示代码的截图如下所示: