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
>



