skip to Main Content

enter image description here

I need to add a save extension selector with a text label next to it to my NSSavePanel. In the screenshot attached I try to demonstrate that I succeeded in adding an NSComboBox to my panel with the function setAccessoryView. However I have no idea how to create a custom NSView, which includes both an NSComboBox and an NSTextView or equivalent. I found no tutorials on the internet (or if I found one it was extremely outdated) showing how to create custom NSViews in objective-C in Cocoa on MacOS.

How can I create a custom NSView containing a combobox and a text label? Or how can I add two "stock" NSViews to the same NSSavePanel? Please be as detailed in your answer as possible, as I have very limited objective-c experience.

2

Answers


  1. Press Cmd-N to add a new file to your project. Choose a View file to add a xib file that has a custom view.

    enter image description here

    Open the xib file and add the controls to the custom view. Press the Add button in the project window toolbar to access the user interface elements.

    Use the NSNib class to load the xib file and get the custom view.

    Login or Signup to reply.
  2. You asked how to create an NSView in Objective-C with an NSTextField and an NSComboBox as subviews.

    Basically, you could define them in Interface Builder and programmatically set the resulting view in Objective-C as the accessoryView of the NSSavePanel. Alternatively, the custom NSView could be created entirely in Objective-C, which is probably the easier option here.

    After instantiating an NSView, you can use addSubview: to add an NSTextField and an NSComboBox accordingly. Then you can use NSLayoutConstraints to set up Auto Layout, which takes care of sizing the accessoryView and arranging the subviews properly based on the width of the dialog.

    If you create the views programmatically and use Auto Layout, you must explicitly set translatesAutoresizingMaskIntoConstraints to NO.

    Should you want to set the allowedContentTypes, a textual mapping of the displayed extension to UTType via a NSDictionary might be useful.

    If you set the delegate of the NSComboBox to self, then you will be informed about changes of the user selection in the NSComboBox via comboBoxSelectionDidChange:.

    If the things discussed are implemented appropriately in code, it might look something like this for a self-contained example:

    #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
    

    Finally, a screenshot of the above demo code in action looks like this:
    demo screenshot

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search