How to detect when an NSTextView has finished being loaded into a window?

  • Hello,

    I'm trying to detect when an NSTextView has finished being loaded into its window in a situation where -viewDidMoveToWindow won't work - is there another way of doing this that I'm overlooking?

    Here's the situation, and the reason -viewDidMoveToWindow won't work:

    I've spent the last week debugging a nasty crashing bug that affected my page layout code in rare circumstances when it was loaded on project open (http://www.cocoabuilder.com/archive/cocoa/298810-tracking-exc-bad-access-wh
    en-zombies-don-work.html?q=exc_bad_access#298810
    - many thanks to those who helped!). It turned out there were a couple of issues involved, one to do with layout, and the other to do with the timing of the page layout view getting swapped into the window, which is what I'm trying to solve now. This is how things are currently set up:

    When a project in my program is opened, a text view is created programmatically. The user can choose whether to view text in a single text view or in a multiple pages view. Thus, in my NSTextView subclass, I have overridden -viewDidMoveToWindow to send out a notification that tells listeners when the text view has finished being moved to a window. I have my controller listen for this notification, and when it detects that the text view has moved to a window, it restores the user's previous settings. One of these settings is whether to use a multiple page (i.e. multiple text container) set up, and if this is set to YES then the text view gets swapped out and replaced with the page layout view.

    (The reason it's done like this, by the way, rather than just creating the page layout view instead of the text view programmatically in the first place, is that it turns out you cannot create a multiple text view and load it with text off-screen. If you do, when you move it on to the screen it will work but you get lots of drawing artefacts, such as the insertion point appearing in multiple places. I had a technical support request out with Apple on this, and it took a good while to work out that the fix was to ensure the page layout view was only created and laid out once everything had been moved to a window. Therefore I wait until the regular single text view - which isn't affected by such artefacts - is safely loaded into a window before trying to swap in the page layout view.)

    However, the problem is that it turns out it's not safe to swap out a view from -viewDidMoveToWindow (which I suppose shouldn't come as a big surprise). I had assumed -viewDidMoveToWindow would be final, and that the view would have finished moving to the window and getting set up by the time this method is called, but it's not the case. -viewDidMoveToWindow seems to be called from the private NSView method _setWindow:. NSTextView overrides _setWindow: and calls another private method, -_requestUpdateOfDragTypeRegistration, *after* -viewDidMoveToWindow has been called. -_requestUpdateOfDragTypeRegistration in turn calls -performSelector:withObject:afterDelay: on self (with the selector -updateDragTypeRegistration).

    Thus, if I swap out the text view in -viewDidMoveToWindow:, the text view still tries to finish the rest of the _setWindow: method afterwards, and ends up calling -performSelector:withObject:afterDelay: on self after it has been deallocated.

    So, I'm trying to find a more final way of only setting up the page view after the window is on screen and everything necessary has been moved to that window, so that the text view has completely finished loading. Overriding _setWindow: and posting the notification at the end of that method works, but obviously that is a private method and if I want to get on the Mac App Store I'm going to need to avoid such solutions. I've also tried only posting the notification after a delay of 0, which works and avoids the crash, but is a little slow - you can see the original text view getting loaded on screen before being swapped for the multiple page view.

    By this point, I've probably over-complicated everything and am missing something obvious, so I'd be grateful for any suggestions. (Obviously -awakeFromNib and -windowDidLoad come before anything is guaranteed to be on screen, otherwise I would have used those from the get-go.)

    Many thanks and all the best,
    Keith
  • On Mon, 21 Feb 2011 06:22:35 -0800 (PST), Keith Blount <keithblount...> said:
    > I've also tried only posting the notification after a delay of 0, which works and avoids the crash, but is a little slow - you can see the original text view getting loaded on screen before being swapped for the multiple page view.
    >

    If the nub of the remaining problem is what "you can see", I wonder why you don't simply have these text views be hidden. That way they are in the window (so that they are loaded correctly) but the user sees nothing. Or, if that doesn't work, cover them with something else (another view, or even a secondary window). You can cover the intervening time with a progress indicator of some sort. m.

    --
    matt neuburg, phd = <matt...>, <http://www.apeth.net/matt/>
    A fool + a tool + an autorelease pool = cool!
    AppleScript: the Definitive Guide - Second Edition!
    Hi Matt,

    Many thanks for the reply. Unfortunately that doesn't really work, as if it is hidden in any way then it is off screen and therefore the artefacts bug rears its ugly head. (The whole thing is in a tab view, and even if the text view is in a different tab to the visible one while it is set up, the artefacts bug appears.)

    I may try rewiring it to load when the scroll view makes it to the window instead of the text view, although if NSScrollView does anything extra in _setWindow: I'll most likely have the same problem, of course. What would be ideal would be if I could find a way of knowing when the NSTextView has finished loading into the window completely, and _setWindow: has finished, but I don't think there's a way of doing that unfortunately... So I probably need to re-examine my wiring and set-up.

    Thanks again and all the best,
    Keith

    --- On Mon, 2/21/11, Matt Neuburg <matt...> wrote:

    > From: Matt Neuburg <matt...>
    > Subject: Re: How to detect when an NSTextView has finished being loaded into a window?
    > To: "Keith Blount" <keithblount...>
    > Cc: <cocoa-dev...>
    > Date: Monday, February 21, 2011, 6:16 PM
    > On Mon, 21 Feb 2011 06:22:35 -0800
    > (PST), Keith Blount <keithblount...>
    > said:
    >> I've also tried only posting the notification after a
    > delay of 0, which works and avoids the crash, but is a
    > little slow - you can see the original text view getting
    > loaded on screen before being swapped for the multiple page
    > view.
    >>
    >
    > If the nub of the remaining problem is what "you can see",
    > I wonder why you don't simply have these text views be
    > hidden. That way they are in the window (so that they are
    > loaded correctly) but the user sees nothing. Or, if that
    > doesn't work, cover them with something else (another view,
    > or even a secondary window). You can cover the intervening
    > time with a progress indicator of some sort. m.
    >
    > --
    > matt neuburg, phd = <matt...>,
    > <http://www.apeth.net/matt/>
    > A fool + a tool + an autorelease pool = cool!
    > AppleScript: the Definitive Guide - Second Edition!
    > http://www.apeth.net/matt/default.html#applescriptthings
  • On Mon, Feb 21, 2011 at 1:04 PM, Keith Blount <keithblount...> wrote:
    > Hi Matt,
    >
    > Many thanks for the reply. Unfortunately that doesn't really work, as if it is hidden in any way then it is off screen and therefore the artefacts bug rears its ugly head. (The whole thing is in a tab view, and even if the text view is in a different tab to the visible one while it is set up, the artefacts bug appears.)

    Are you running on Snow Leopard?

    Also, from your original description, it sounds like you intend for
    the text view to possibly be deallocated during -viewDidMoveToWindow.
    This sounds like a recipe for disaster. Instead, you should try
    hooking up additional text views/containers to the same layout
    manager. Your first text view will therefore always exist; in
    single-view mode it will be the only text view, whereas in page layout
    mode it will be the first in the chain of text views sharing the
    layout manager and text storage.

    --Kyle Sluder
  • Many thanks for the reply. Yes, you're right - the text view could get deallocated in -viewDidMoveToWindow which is indeed a recipe for disaster and was causing the issues. (I should have realised that in the first place, but by the time the page layout set-up got moved to a method responding to a notification sent from -viewDidMoveToWindow, I was at the end of my tether trying to put it in the right place to avoid the artefact bug, and as the program got away with not crashing for most users, I never noticed the potential problem with this move.)

    The way the page view gets swapped for the text view and then the original text view gets removed entirely is actually based on code straight from TextEdit, so I think it's more the placement of the code than the way it's done.

    I seem to have fixed the issue, although my solution is a bit of an ugly mash-up. In my controller I now retain the original text view in the method responding to the notification it receives from -viewDidMoveToWindow and then release it again after a delay of 0, i.e. at the end of the run loop. That way I ensure it's kept around long enough so that it doesn't get deallocated while it's still calling methods on itself (e.g. from _setWindow:). I'm also now sending the notification from the end of _setWindow: if that method is available and it's not the App Store version, otherwise after a delay of 0 from -viewDidMoveToWindow. The latter still has the issue of the slight delay where the user can see the original text view appear on screen for an instant before it gets swapped out for the page view, but that's better than a crash. Taken together, these ensure that the page view only gets loaded when there is a window for it to get loaded into, and that the
    old text view is only deallocated after it's been finished with.

    Part of the issue is the complex arrangement of views in my app. TextEdit can call -setHasMultiplePages: in -windowDidLoad, because the page view will move directly to the window; in my app the page view may be hidden away in a tab view at this point as there may be a different sort of view selected for viewing on project open - showing an image, or a PDF file, for instance - meaning that if I call -setHasMultiplePages: in -windowDidLoad, the page view may not have a window and so the artefacts bug will rear its head. The next obvious place for switching to multiple pages view was -tabView:didSelectTabViewItem:, so that it only got called when the text was displayed in the tab view. However, that's no good either because my app has hide-able split panes, so there's no guarantee that the tab view is on screen and part of a window when everything gets set up... This is why I ended up only switching to the multiple page view when the program knows that the
    basic text view has made it to the window - that's the only time I'm guaranteed a window for the page view.

    Hmm, although I'm now wondering if maybe I should just set the scroll view's documentView to nil when it's off-window and do all this when the scroll view moves to the window...

    Thanks again and all the best,
    Keith

    --- On Tue, 2/22/11, Kyle Sluder <kyle.sluder...> wrote:

    > From: Kyle Sluder <kyle.sluder...>
    > Subject: Re: How to detect when an NSTextView has finished being loaded into a window?
    > To: "Keith Blount" <keithblount...>
    > Cc: "Matt Neuburg" <matt...>, <cocoa-dev...>
    > Date: Tuesday, February 22, 2011, 3:19 AM
    > On Mon, Feb 21, 2011 at 1:04 PM,
    > Keith Blount <keithblount...>
    > wrote:
    >> Hi Matt,
    >>
    >> Many thanks for the reply. Unfortunately that doesn't
    > really work, as if it is hidden in any way then it is off
    > screen and therefore the artefacts bug rears its ugly head.
    > (The whole thing is in a tab view, and even if the text view
    > is in a different tab to the visible one while it is set up,
    > the artefacts bug appears.)
    >
    > Are you running on Snow Leopard?
    >
    > Also, from your original description, it sounds like you
    > intend for
    > the text view to possibly be deallocated during
    > -viewDidMoveToWindow.
    > This sounds like a recipe for disaster. Instead, you should
    > try
    > hooking up additional text views/containers to the same
    > layout
    > manager. Your first text view will therefore always exist;
    > in
    > single-view mode it will be the only text view, whereas in
    > page layout
    > mode it will be the first in the chain of text views
    > sharing the
    > layout manager and text storage.
    >
    > --Kyle Sluder
    >
previous month february 2011 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            
Go to today