Scroll view clip view not moving to new object inserted in contained NSCollectionView - when are resizing events in the view hierarchy complete?

  • My application has UI to define a vertical set of objects (let's
    called them 'strips'), where the last item in each strip is supposed
    to be on display in the stack, but the viewport displaying this 'last
    object' can be scrolled backward (left) to display the previous
    similarly size objects in the strip.

    To achieve this, I have a view hierarchy set up as follows:
    - An outer NSScrollView is set up only scroll vertically (through the
    stack of strips).
    - The content of this view is an NSCollectionView, which is set to
    have 1 column only.  The items of this view are the strips.  The
    strips instances are managed by an NSArrayController.
    - Each strip is itself an NSScrollView, set up to scroll only
    horizontally (through 'tiles' that make up the strip)
    - The content of this view is an NSCollectionView, which is set to
    have 1 row only.  The items of this view are the tiles.  The tile
    instances are managed by an NSArrayController.

    I'm assuming that NSCollectionViews are general enough to be used in
    such a configuration - though I don't see many examples of their use
    other than as more straight-forward containers.  Nevertheless, it
    seems that they should be OK.

    Now, my exact problem is the following...
    - My application creates a new model object that is displayed in a
    tile, and the NSArrayController correctly spots this and extends the
    strip to include the new object.  I can scroll the strip forward and
    backward, and I see all the tiles I expect, including the new one.
    - When this happens, I want the strip to scroll to the new object
    (which is always added at the end of the array).  I figured that I
    could achieve this by placing the following code in the NSView
    subclass representing a tile:

    - (void)viewDidMoveToSuperview {
    // We should now be a member of the document view of the strip scroll
    view
    // If the represented object is the last item added, ask it to tell
    the scroll view to move to display its bounds
    id <ACItem> item = [tileItem representedObject];
    if ([item isLastestItem]) {  // Items know whether they are the last
    (current) item
      // Ask our nearest scroller (the strip scroller) to scroll to our
    bounds
      [self scrollRectToVisible:[self bounds]];
    }
    }

    Now, what happens is the scroll view moves to show the _penultimate_
    view in the strip, not the last one.  This suggests that at the time
    the new tile view became added to the document view (its
    NSCollectionView), the scroll view doesn't not think it has the scope
    in its document view to slide the clip view over the newly added
    area.  Instead, it goes (I'm guessing) to the nearest part of the
    strip it can, namely the previous 'rightmost' area of the penultimate
    tile.

    I've just written modified the above code, to set an NSTimer to call a
    method that trivially does:
    [self scrollRectToVisible:[self bounds]];

    ... and sure enough, the scrolling now works properly.  So perhaps I
    have a sort of workaround.

    However, I'm remain unsure about the following:
    1. Though this is the first and only scenario in which I have used
    "viewDidMoveToSuperview", it looks like this message is sent too early
    for me to expect the antecedents in the view hierarchy (including the
    scrolling apparatus) to have updated to reflect the change in size of
    the NSCollectionView.
    2. When would be a better time to ask call the new NSView's
    scrollRectToVisible:?  Presumably, there should be a 'good moment'
    known to this object itself somehow (i.e. receipt of some message to
    say that all the resizing effects up the hierarchy are completed and
    its safe to ask for scrolling operations).  Clearly, if I have to call
    into the view hierarchy from 'outside' (i.e. just after the call that
    adds the new item to my model), then I'm going to have to set up some
    way to get a model item's view object, and this would seem to violate
    the nice encapsulation/separation of view an model.

    -- Lwe
  • On 1/16/08, Luke Evans <luke...> wrote:
    > 2. When would be a better time to ask call the new NSView's
    > scrollRectToVisible:?  Presumably, there should be a 'good moment'
    > known to this object itself somehow (i.e. receipt of some message to
    > say that all the resizing effects up the hierarchy are completed and
    > its safe to ask for scrolling operations).  Clearly, if I have to call
    > into the view hierarchy from 'outside' (i.e. just after the call that
    > adds the new item to my model), then I'm going to have to set up some
    > way to get a model item's view object, and this would seem to violate
    > the nice encapsulation/separation of view an model.

    If I'm not mistaken, NSScrollView (or NSClipView) registers for the
    document view's NSViewFrameDidChangeNotification, which is going to
    happen in the next run through the runloop after your view gets added
    to the NSCollectionView.  Perhaps you should have a controller of some
    sort register for this notification from the collection view as well
    and have that object scroll the scroll view by calling
    -[NSCollectionView scrollRectToVisible:].

    HTH,
    --Kyle Sluder
  • Hi Kyle,

    Thanks.

    I see the docs on NSView notifications indeed say:

    An NSView instance that bases its own display on the layout of its
    subviews should register itself as an observer for those subviews and
    update itself any time they’re moved or resized. Both NSScrollView and
    NSClipView instances cooperate in this manner to adjust the scroll
    view's scrollers.

    In fact, I do have a subclass of NSCollectionView I'm working with
    (just to control the loading of item NIBs at the moment), and I could
    have this object (representing a strip) register for its own frame
    change notifications (...it seems odd to me how it doesn't get a
    message to inform itself of these changes by default, but I'm new to
    the Cocoa philosophy, so guess that must make some kind of sense).

    With such a change though, I'll be left scrabbling to find which of my
    tiles is actually the new one (the tiles know this of course, but the
    NSCollectionView they happen to be in would have to enumerate its
    contents to find the tile that has just been added - and hence lives
    at the right-hand end of the strip).  It may be better to have ALL the
    tiles register for such changes to their container's frame size, and
    then the right-most (newly added one) can respond by doing the
    scrollRectToVisible with its own bounds.

    I'm sure this would work, but I'm surprised there isn't a better way
    (i.e. without adding plumbing that requires prior knowledge of other
    instances in the view hierarchy to work) to know when your superviews
    should be in state that fully represents your own existence and hence
    a time when such a call could be made.

    --Lwe

    On 16-Jan-08, at 2:47 PM, Kyle Sluder wrote:
    >
    > If I'm not mistaken, NSScrollView (or NSClipView) registers for the
    > document view's NSViewFrameDidChangeNotification, which is going to
    > happen in the next run through the runloop after your view gets added
    > to the NSCollectionView.  Perhaps you should have a controller of some
    > sort register for this notification from the collection view as well
    > and have that object scroll the scroll view by calling
    > -[NSCollectionView scrollRectToVisible:].
    >
    > HTH,
    > --Kyle Sluder
    >
previous month january 2008 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