Canonical way of loading compound UI components from a NIB into some other UI?

  • Hi,

    I've been scratching around to find the _right_ way of loading some
    compound UI from one Nib into a view within another Nib.  In other
    words, I wish to define a compound view hierarchy as a 'custom
    control' and then load this into the location represented by a custom
    view in some other Nib, such as in a window.

    So, lets say I have a my compound control hierarchy, consisting of a
    scroll view at the top, which then contains a number of other views.
    These are all stored in a Nib called ScrollingControls.nib.  Now, in
    my main Nib, I might have a NSWindow and I wish to place a custom view
    within this window that will define the location of the root view in
    my ScrollingControls hierarchy.

    It seems to me that there are a number of gotchas, or at least
    'inelegance' (or maybe just work!!) in how I would have to go about
    this.
    1. If I choose to make the placeholder custom control in my NSWindow
    into an actual instance of the some top-level view class in my
    ScrollingControls (i.e. in this case it would be natural for this to
    be the scroll view), then I have to make this root view in
    ScrollingControls be a special case - it would need to be a subclass
    of NSScrollView that knows how to load the remaining part of the
    compound view from a Nib file - yet this view itself would NOT reside
    in this Nib.  This has the right encapsulation (the subtree that is
    loaded is the business of the root of the 'control' itself), but I
    dislike the asymmetry of having to have the top level view exist
    outside of the rest of Nib that defines the complete view hierarchy I
    want.
    2. If I choose to have ALL the ScrollingControls view hierarchy,
    including the top level NSScrollView exist in ScrollingControls.nib,
    then I cannot see how to elegantly make the placeholder custom view in
    the NSWindow automatically load the view subtree from
    ScrollingControls.nib and replace the placeholder view.  There are
    certainly various workarounds that come to mind, such as having a
    special NSView subclass in the NSWindow, which does the requisite
    loading of the entire ScrollingControls view tree, and that then
    performs a "replaceSubview", to substitute itself with the intended
    top of the view hierarchy at that point (i.e. the NSScrollView).  This
    sort of thing seems somewhat awkward however.

    So, in a nutshell, I'm looking for the canonical pattern for loading a
    view hierarchy from a Nib into a placeholder custom view, without
    having to add an extra (essentially spurious) view layer to do this
    loading.  I've hunted around for some code to demonstrate the right
    pattern for this, but most examples deal with loading a single custom
    view (without an attendant hierarchy) .

    At this point, it seems to me that I cannot avoid having to break the
    symmetry, and having the root control that is referenced from the
    placeholder custom control be different to the rest of the hierarchy
    loaded from Nib (which is what it has to control in it's
    awakeFromNib).  Yet, it also seems to me that a number of items in the
    Interface Builder palette are compound (such as the NSCollectionView),
    and this is the behaviour that I'd like - to be simply able to include
    a subtree of views directly into some UI.  Maybe there's some jiggery-
    pokery going on in the case of Interface Builder plug-ins to achieve
    this, and I certainly don't want to have to go to the bother of
    creating a plug-in to achieve what I want.  I'm hoping I'm missing
    something simple here!

    -- Lwe
  • On Dec 5, 2007, at 4:45 PM, Luke Evans wrote:

    > I've been scratching around to find the _right_ way of loading some
    > compound UI from one Nib into a view within another Nib.  In other
    > words, I wish to define a compound view hierarchy as a 'custom
    > control' and then load this into the location represented by a
    > custom view in some other Nib, such as in a window.
    >
    > So, lets say I have a my compound control hierarchy, consisting of a
    > scroll view at the top, which then contains a number of other
    > views.  These are all stored in a Nib called ScrollingControls.nib.
    > Now, in my main Nib, I might have a NSWindow and I wish to place a
    > custom view within this window that will define the location of the
    > root view in my ScrollingControls hierarchy.

    I have something similar in my apps.  I have a single window in my
    main nib.  I then have multiple nibs which contains the full UI of a
    particular "screen" in an NSPanel**.  As the user moves from screen to
    screen, I load up the appropriate nib, and set the window's content
    view to be the content view of the NSPanel.  The content view of the
    panel is always a single instance of a custom container view (that
    forms the root of the contained UI).  Each nib also has a subclass of
    a base controller class that manages everything with that particular UI.

    You could definitely create a similar nib to contain a re-usable UI
    that can be dropped in anywhere.  In my case though, only a single
    instance of the UI is ever on-screen at any given time.  It sounds
    like you need to load up multiple instances?  Thus, my approach may
    not work, or may need to be modified.

    ** It's not necessary to put your stuff in a panel; you can drag in a
    view itself and embed your UI in it.  That view will then be directly
    used as the window's content view (or whatever content view you
    want).  In my specific case, I had need to have the view sit inside an
    NSPanel instance for other reasons.

    > It seems to me that there are a number of gotchas, or at least
    > 'inelegance' (or maybe just work!!) in how I would have to go about
    > this.
    > 1. If I choose to make the placeholder custom control in my NSWindow
    > into an actual instance of the some top-level view class in my
    > ScrollingControls (i.e. in this case it would be natural for this to
    > be the scroll view), then I have to make this root view in
    > ScrollingControls be a special case - it would need to be a subclass
    > of NSScrollView that knows how to load the remaining part of the
    > compound view from a Nib file - yet this view itself would NOT
    > reside in this Nib.  This has the right encapsulation (the subtree
    > that is loaded is the business of the root of the 'control' itself),
    > but I dislike the asymmetry of having to have the top level view
    > exist outside of the rest of Nib that defines the complete view
    > hierarchy I want.

    Not sure what you mean here about having to have a subclass to "load
    the remaining part of the compound view".

    > 2. If I choose to have ALL the ScrollingControls view hierarchy,
    > including the top level NSScrollView exist in ScrollingControls.nib,
    > then I cannot see how to elegantly make the placeholder custom view
    > in the NSWindow automatically load the view subtree from
    > ScrollingControls.nib and replace the placeholder view.  There are
    > certainly various workarounds that come to mind, such as having a
    > special NSView subclass in the NSWindow, which does the requisite
    > loading of the entire ScrollingControls view tree, and that then
    > performs a "replaceSubview", to substitute itself with the intended
    > top of the view hierarchy at that point (i.e. the NSScrollView).
    > This sort of thing seems somewhat awkward however.

    Still not sure what you mean here about loading.  To provide further
    details of my multiple-nib approach above...

    In my application controller class, I have a generic "loadScreen"
    method.  It takes a name and I dynamically build a nib name from it.
    I then create an instance of a window controller subclass and
    ultimately initialize it with initWithWindowNibName:owner:

    Note that this window control subclass approach may not be needed.  I
    needed to do this in order to correctly deal with top-level objects in
    the nib.  If you need to go this route, I can point you to some old
    threads that have more detail.

    > So, in a nutshell, I'm looking for the canonical pattern for loading
    > a view hierarchy from a Nib into a placeholder custom view, without
    > having to add an extra (essentially spurious) view layer to do this
    > loading.  I've hunted around for some code to demonstrate the right
    > pattern for this, but most examples deal with loading a single
    > custom view (without an attendant hierarchy) .
    >
    > At this point, it seems to me that I cannot avoid having to break
    > the symmetry, and having the root control that is referenced from
    > the placeholder custom control be different to the rest of the
    > hierarchy loaded from Nib (which is what it has to control in it's
    > awakeFromNib).  Yet, it also seems to me that a number of items in
    > the Interface Builder palette are compound (such as the
    > NSCollectionView), and this is the behaviour that I'd like - to be
    > simply able to include a subtree of views directly into some UI.
    > Maybe there's some jiggery-pokery going on in the case of Interface
    > Builder plug-ins to achieve this, and I certainly don't want to have
    > to go to the bother of creating a plug-in to achieve what I want.
    > I'm hoping I'm missing something simple here!

    ___________________________________________________________
    Ricky A. Sharp        mailto:<rsharp...>
    Instant Interactive(tm)  http://www.instantinteractive.com
  • > So, lets say I have a my compound control hierarchy, consisting of a
    > scroll view at the top, which then contains a number of other
    > views.  These are all stored in a Nib called ScrollingControls.nib.
    > Now, in my main Nib, I might have a NSWindow and I wish to place a
    > custom view within this window that will define the location of the
    > root view in my ScrollingControls hierarchy.

    I'm just one guy, so I wouldn't call it canonical, but when I've had
    to do this, I drop a NSScrollView on my window and make an outlet for
    it in my window controller. Then, when the time is right (usually -
    awakeFromNib for me), I use said outlet to replace the scroll view's
    document view.

    In this example, my nib with all the controls in it has an NSView at
    the top level, not an NSScrollView. I find this allows for greater
    flexibility and reusability as 1) I might not always want my controls
    to scroll, and 2) the scroll view is used to having its document view
    swapped out on it and has machinery in place to deal with this. But
    YMMV.

    Finally, if your nib of controls is even slightly complex and you're
    targeting 10.5, read up on NSViewController. It's like a window
    controller, only for views. It can really help organizing the loading
    of nibs, memory management, etc. Also, it has one of those
    representedObject bindings which again enhances flexibility and
    reusability. I find it ups the "elegance factor" of doing this sort of
    thing by at least 2 points. ;-)

    Cheers,
    -Joshua Emmons
  • Thanks for the comments so far.

    On reflection, I guess I'm trying to do something that I feel ought to
    be a simple encapsulation of a compound custom control that I can
    simply drag into interfaces and expect to have all the content
    instantiated - much as the compound palette items in IB work.  I'm not
    so interested in loading individual parts of the UI at runtime (as one
    would be if one was dynamically replacing parts of the view tree for
    some requirement in the application).

    Thus, it would be nice if Interface Builder had a proxy view that
    represented the automatic loading of a NIB, replacement of the
    placeholder/proxy with a named view in the NIB, and automatic setting
    up of the size and owner of this view to match information on the proxy.

    The tip on NSViewController was useful though (thanks), and this led
    me to the Apple sample project "SourceView", which is certainly a
    great pattern if it was my intention to build a dynamic interface that
    swaps part of the view tree in and out.  I note however that this
    approach still keeps a 'connection point' custom view in the permanent
    part of the view tree, and adds/removes the root of the changeable
    part of the view tree underneath that.  I could certainly use this
    technique to load my unitary subtree as a child beneath a placeholder
    view, but this was partly what I was trying to avoid (given that this
    represents a redundant part of the view tree - if you have a perfectly
    good root view for the compound control you are loading from separate
    Nib).

    So, my current thinking has evolved to a couple of possibilities:
    1. Research how Interface Builder 3.0 supports plug-ins.  This might
    be overkill and require a lot of work, but in many ways this is what
    I'm trying to do, i.e. build a more abstract, compound control that I
    can just drag into other interfaces to get a 'static instance' copied
    in.
    2. Examine the possibility of creating the sort of "view loader" proxy
    view that I've outlined above, which would enable me to drag a
    correctly located/sized placeholder into interfaces with appropriate
    properties to have the view hierarchy from a named NIb swapped in when
    the proxy's -awakeFromNib is called.

    -- Lwe
  • On Dec 5, 2007, at 6:55 PM, Luke Evans wrote:

    > So, my current thinking has evolved to a couple of possibilities:
    > 1. Research how Interface Builder 3.0 supports plug-ins.  This might
    > be overkill and require a lot of work, but in many ways this is what
    > I'm trying to do, i.e. build a more abstract, compound control that
    > I can just drag into other interfaces to get a 'static instance'
    > copied in.

    IB 3.x Plug-Ins aren't that bad.  Look for my MyIBPlugin sample at <http://www.instantinteractive.com/private/samplecode/MyIBPlugin.zip> as a starting point.  It doesn't contain an exact sample of what
    you want, but it's very easy:

    (1) Your library template NIB will contain a library template object
    which will have its draggedView outlet connected to a pre-built
    instance of your NSView and subviews.

    You then basically have a shortcut of sorts.  You can then drag in
    that single library template object into whatever and a copy of your
    pre-built NSView will be dropped into place.  You can then of course
    inspect any objects in that hierarchy and make alterations as needed.

    Of course you'll need to set up any bindings and/or make connections
    to controllers for each instance you drag in.

    ___________________________________________________________
    Ricky A. Sharp        mailto:<rsharp...>
    Instant Interactive(tm)  http://www.instantinteractive.com
  • By way of an update and perhaps a conclusion.

    Following my nose somewhat (and emboldened by the fact that "IB 3.x
    Plug-Ins aren't that bad"!), I've produced my first plug-in to solve
    my 'problem'.
    In the spirit of reusability I've tried to solve the class problem
    that was bugging me (namely the unpacking of a view hierarchy from
    another NIB underneath a placeholder view, without the redundancy of
    leaving a level of view as the 'connection point', and as far as
    possible preserving encapsulation - the benefit of keeping all your
    'compound control' in one NIB file.

    So, I now have a IB plug in that implements one custom control called
    "SurrogateView".  This manifests at design-time as a red rectangle
    with a diagonal cross, possessed of two string properties:
    viewControllerClassName and nibName.

    At runtime, when the SurrogateView gets -awakeFromNIB, it attempts to
    instantiate the named NSViewController subclass, and initialises it
    with the nib file.  It then obtains the loaded view, sets its own
    frame size onto this view and then deftly replaces itself as a subview
    of its parent with the loaded view.

    Seems to work like a charm (so far!).

    I'm still left with a nagging doubt about having to do this - or
    perhaps I should say surprise that there isn't a standard way to do
    this sort of thing, as it seems to me that one should quite commonly
    try to encapsulate useful bits of UI in separate NIBs and load them
    into other view hierarchies as needed.  Of course, if you are
    developing a dynamic UI, then you'll be writing the code to replace
    subviews anyway, but when you just want to include a single 'static'
    instance of a compound control, then you don't want to be messing with
    code (in the same way that IB is supposed to let you capture prebuilt
    view hierarchies in the first place).

    Anyway, a final thanks for the pointers - much appreciated.

    -- Lwe

    On 5-Dec-07, at 5:13 PM, Ricky Sharp wrote:
    >>
    >
    > IB 3.x Plug-Ins aren't that bad.  Look for my MyIBPlugin sample at <http://www.instantinteractive.com/private/samplecode/MyIBPlugin.zip
    > > as a starting point.  It doesn't contain an exact sample of what
    > you want, but it's very easy:
    >
    > ...
    > You then basically have a shortcut of sorts.  You can then drag in
    > that single library template object into whatever and a copy of your
    > pre-built NSView will be dropped into place.  You can then of course
    > inspect any objects in that hierarchy and make alterations as needed.
    >
    > Of course you'll need to set up any bindings and/or make connections
    > to controllers for each instance you drag in.
    >
previous month december 2007 next month
MTWTFSS
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            
Go to today