programmatically loading object from subclass of NSView from nib

后端 未结 5 745
闹比i
闹比i 2020-12-24 02:56

How do I correctly load an object that is a subclass of NSView using a Xib?

I want it to be loaded dynamically not from the beginning so I made a MyView.Xib From MyD

相关标签:
5条回答
  • 2020-12-24 03:35

    Note that a nib file contains:

    • One or more top-level objects. For example, an instance of MyView;
    • A file’s owner placeholder. This is an object that exists prior to the nib file being loaded. Since it exists before the nib file is loaded, it cannot be the same as the view in the nib file.

    Scenario 1: NSNib

    Let’s consider that your nib file has only one top-level object whose class is MyView, and the file’s owner class is MyAppDelegate. In that case, considering that the nib file is loaded in an instance method of MyAppDelegate:

    NSNib *nib = [[[NSNib alloc] initWithNibNamed:@"MyView" bundle:nil] autorelease];
    NSArray *topLevelObjects;
    if (! [nib instantiateWithOwner:self topLevelObjects:&topLevelObjects]) // error
    
    MyView *myView = nil;
    for (id topLevelObject in topLevelObjects) {
        if ([topLevelObject isKindOfClass:[MyView class]) {
            myView = topLevelObject;
            break;
        }
    }
    
    // At this point myView is either nil or points to an instance
    // of MyView that is owned by this code
    

    It looks like the first argument of -[NSNib instantiateNibWithOwner:topLevelObjects:] can be nil so you wouldn’t have to specify an owner since it seems that you aren’t interested in having one:

    if (! [nib instantiateWithOwner:nil topLevelObjects:&topLevelObjects]) // error
    

    Although this works, I wouldn’t rely on it since it’s not documented.

    Note that you have ownership of the top level objects, hence you are responsible for releasing them when they’re no longer needed.

    Scenario 2: NSViewController

    Cocoa provides NSViewController to manage views; usually, views loaded from a nib file. You should create a subclass of NSViewController containing whichever outlets are needed. When editing the nib file, set the view outlet and whichever other outlets you may have defined. You should also set the nib file’s owner so that its class is this subclass of NSViewController. Then:

    MyViewController *myViewController = [[MyViewController alloc] initWithNibName:@"MyView" bundle:nil];
    

    NSViewController automatically loads the nib file and sets the corresponding outlets when you send it -view. Alternatively, you can force it to load the nib file by sending it -loadView.

    0 讨论(0)
  • 2020-12-24 03:38
    class CustomView: NSView {
    
    @IBOutlet weak var view: NSView!
    @IBOutlet weak var textField: NSTextField!
    
    required init(coder: NSCoder) {
        super.init(coder: coder)!
    
        let frameworkBundle = Bundle(for: classForCoder)
        assert(frameworkBundle.loadNibNamed("CustomView", owner: self, topLevelObjects: nil))
        addSubview(view)
    }
    

    }

    0 讨论(0)
  • 2020-12-24 03:40

    You can refer to this category

    https://github.com/peterpaulis/NSView-NibLoading-/tree/master

    + (NSView *)loadWithNibNamed:(NSString *)nibNamed owner:(id)owner class:(Class)loadClass {
    
        NSNib * nib = [[NSNib alloc] initWithNibNamed:nibNamed bundle:nil];
    
        NSArray * objects;
        if (![nib instantiateWithOwner:owner topLevelObjects:&objects]) {
            NSLog(@"Couldn't load nib named %@", nibNamed);
            return nil;
        }
    
        for (id object in objects) {
            if ([object isKindOfClass:loadClass]) {
                return object;
            }
        }
        return nil;
    }
    
    0 讨论(0)
  • 2020-12-24 03:49

    Here is a way to write the NSView subclass so the view itself comes fully from a separate xib and has everything set up correctly. It relies on the fact that init can change the value of self.

    Create a new 'view' xib using Xcode. Set the File Owner's class to NSViewController, and set its view outlet to your target view. Set the class of the target view to your NSView subclass. Layout your view, connect outlets etc.

    Now, in your NSView subclass, implement the designated initializers:

    - (id)initWithFrame:(NSRect)frameRect
    {
        NSViewController *viewController = [[NSViewController alloc] init];
        [[NSBundle mainBundle] loadNibNamed:@"TheNib" owner:viewController topLevelObjects:NULL];
    
        id viewFromXib = viewController.view;
        viewFromXib.frame = frameRect;
        self = viewFromXib;
        return self;
    }
    

    And the same with initWithCoder: so that it will also work when using your NSView subclass in an another xib.

    0 讨论(0)
  • 2020-12-24 03:55

    I found the answer by @Bavarious very useful, but it still needed some squirrelling on my part to make it work for my use case - load one of several xib's view definitions into an existing "container" view. Here's my workflow:

    1) In the .xib, create the view in IB as expected.

    2) DO NOT add an object for the new view's viewController, but rather

    3) Set the .xib's File's Owner to new view's viewController

    4) Connect any outlets & actions to File's Owner, and, specifically, .view

    5) Call loadInspector (see below).

    enum InspectorType {
        case None, ItemEditor, HTMLEditor
    
        var vc: NSViewController? {
            switch self {
            case .ItemEditor:
                return ItemEditorViewController(nibName: "ItemEditor", bundle: nil)
            case .HTMLEditor:
                return HTMLEditorViewController(nibName: "HTMLEditor", bundle: nil)
            case .None:
                return nil
            }
        }
    }
    
    class InspectorContainerView: NSView {
    
        func loadInspector(inspector: InspectorType) -> NSViewController? {
            self.subviews = [] // Get rid of existing subviews.
            if let vc = inspector.vc {
                vc.loadView()
                self.addSubview(vc.view)
                return vc
            }
            Swift.print("VC NOT loaded from \(inspector)")
            return nil
        }
    }
    
    0 讨论(0)
提交回复
热议问题