Changing Subview Order Prevents Dragging

  • I've been experimenting with a UI for a program that manages many small, draggable views, that can be moved around in the same superview. The following mouse events work well for providing the dragging basics. However, when I drag a view relative to the other views I also want it to end up on top if there is overlap. If I add the three lines of code that are commented out in the mouseDown method, the mouseDragged method stops being called, so the views won't move. On the other hand, if I add those three lines to the mouseUp method, the views move fine, and they transition to the top fine, but only at the very end of the drag, which looks strange as the view pops up through all the other views that were over it.

    Can anyone suggest why adding the three lines in mouseDown prevents dragging? Using ARC.

    Thanks,

    Tom Wetmore

    - (void) mouseDown: (NSEvent*) event
    {
        //NSView* superView = [self superview];
        //[self removeFromSuperview];
        //[superView addSubview: self];

        _startClick = [event locationInWindow];
        _startFrame = [self frame];
        _dragging = YES;
    }

    - (void) mouseDragged: (NSEvent*) event
    {
        if (_dragging) {
            NSPoint clickPoint = [event locationInWindow];
            NSRect frame = [self frame];
            frame.origin.x = _startFrame.origin.x + clickPoint.x - _startClick.x;
            frame.origin.y = _startFrame.origin.y + clickPoint.y - _startClick.y;
    [self setFrame: frame];
            [self setNeedsDisplayInRect: frame];
            [self autoscroll: event];
        }
    }

    - (void) mouseUp: (NSEvent*) event
    {
        _dragging = NO;

        //NSView* superView = [self superview];
        //[self removeFromSuperview];
        //[superView addSubview: self];
    }
  • On 14/05/2013, at 12:27 PM, Thomas Wetmore <ttw4...> wrote:

    > Can anyone suggest why adding the three lines in mouseDown prevents dragging? Using ARC.

    When you call -removeFromSuperview, the view is deleted, as there are no more references to it. The other methods are not called because the object ceases to exist.

    You need to retain it, remove it, add it to the superview then release it. ARC won't automatically help you out in this case because you're deallocing self, indirectly, which is not a good idea.

    [self retain];
    [self removeFromSuperview];
    [superview addSubview:self];
    [self release];

    --Graham
  • On 14 May, 2013, at 10:41 AM, Graham Cox <graham.cox...> wrote:

    >
    > On 14/05/2013, at 12:27 PM, Thomas Wetmore <ttw4...> wrote:
    >
    >> Can anyone suggest why adding the three lines in mouseDown prevents dragging? Using ARC.
    >
    >
    > When you call -removeFromSuperview, the view is deleted, as there are no more references to it. The other methods are not called because the object ceases to exist.
    >
    > You need to retain it, remove it, add it to the superview then release it. ARC won't automatically help you out in this case because you're deallocing self, indirectly, which is not a good idea.
    >

    That depends on whether the framework which is calling mouseUp:/mouseDown: retains the object on which it's calling it. If the framework is using ARC and a normal strong reference, then it would be retained and that would stop it deallocing, if it's not using ARC then it depends on whether the framework retains the view explicitly, which it probably should since it's trying to use it but I have no idea whether it does or not.

    Not too hard to test, stick a breakpoint in the dealloc() method and see if it's called from within one of the mouseUp:/mouseDown: methods.

    If you are losing it, you can assign self to a strong reference at the start of the method which you nil out again after you've done your window shuffle, and that will hang onto it (as long as it doesn't get optimized out).
  • On 14/05/2013, at 1:14 PM, Roland King <rols...> wrote:

    > That depends on whether the framework which is calling mouseUp:/mouseDown: retains the object on which it's calling it. If the framework is using ARC and a normal strong reference, then it would be retained and that would stop it deallocing, if it's not using ARC then it depends on whether the framework retains the view explicitly, which it probably should since it's trying to use it but I have no idea whether it does or not.

    I'm pretty sure it doesn't, at least between the mouseDown and mouseDragged phases - it may for repeated calls to mouseDragged. I have run into this exact issue in the past.

    > Not too hard to test, stick a breakpoint in the dealloc() method and see if it's called from within one of the mouseUp:/mouseDown: methods.
    >
    > If you are losing it, you can assign self to a strong reference at the start of the method which you nil out again after you've done your window shuffle, and that will hang onto it (as long as it doesn't get optimized out).

    Or just do it manually. It's not incompatible with ARC which after all simply inserts the same calls. To my mind, it's clearer what's going on than relying on all those arcane magic pointer types.

    --Graham
  • On May 13, 2013, at 19:41 , Graham Cox <graham.cox...> wrote:

    > When you call -removeFromSuperview, the view is deleted, as there are no more references to it. The other methods are not called because the object ceases to exist.

    I believe your warning is apposite, but is not actually the cause of Tom's problem. After removing the view, his code adds it back again. If the view had already been deallocated, you'd expect a pretty big crash pretty soon (though not necessarily immediately).

    My guess is that the removed view is still retained by something, but that removing a view from its window causes the mouse-dragging state machine to be reset. If so, immediately re-adding the view isn't going to restore the dragging state.

    I think the trick is to avoid removing the view from its superview. I'm not sure of the *best* way of doing this, but here are the things I'd try:

    -- Invoke 'addSubview: self' without first removing self. The NSView machinery may be clever enough merely to move the view to the end of the subviews array, without actually removing and re-adding it.

    -- Create a mutable copy of 'self.superview.subviews', move 'self' to the end of the mutable copy, then use 'self.superview setSubviews:' to establish the new order. Note that using 'setSubviews:' this way is explicitly called out in the NSView documentation as a way of reordering views.

    -- Use 'sortSubviewsUsingFunction:context:' with a suitable comparison function to move 'self' to the end of the subviews.

    Of course, it's still possible that any or all of the above will reset the dragging state, but they're certainly worth a try.

    P.S. The OP's issue is one reason why using "many small, draggable views" probably isn't a good design solution to the problem anyway.
  • On 14 May, 2013, at 11:34, Graham Cox <graham.cox...> wrote:

    >
    > Or just do it manually. It's not incompatible with ARC which after all simply inserts the same calls. To my mind, it's clearer what's going on than relying on all those arcane magic pointer types.
    >
    > --Graham
    >
    >

    You'd have to disable arc on that file on that case. Explicit retain release in an arc enabled source file are syntax errors.
  • This works. Thanks for the tip.

    Tom Wetmore

    On May 13, 2013, at 11:38 PM, Quincey Morris <quinceymorris...> wrote:

    > -- Invoke 'addSubview: self' without first removing self. The NSView machinery may be clever enough merely to move the view to the end of the subviews array, without actually removing and re-adding it.
  • On May 13, 2013, at 11:38 PM, Quincey Morris <quinceymorris...> wrote:
    > On May 13, 2013, at 19:41 , Graham Cox <graham.cox...> wrote:
    >
    >> When you call -removeFromSuperview, the view is deleted, as there are no more references to it. The other methods are not called because the object ceases to exist.
    >
    > I believe your warning is apposite, but is not actually the cause of Tom's problem. After removing the view, his code adds it back again. If the view had already been deallocated, you'd expect a pretty big crash pretty soon (though not necessarily immediately).
    >
    > My guess is that the removed view is still retained by something,

    I believe ARC keeps it alive by virtue of self being a strong reference. I did a quick test and found that if I do

    - (void) mouseDown: (NSEvent*) event
    {
        NSView* superView = [self superview];
        [self removeFromSuperview];
    //    [superView addSubview: self];
    }

    ...then dealloc does in fact get called. But if I uncomment that one line, which references self, dealloc does not get called. This is good news -- I would want ARC to work regardless of whether it calls non-ARC code, and vice versa.

    > but that removing a view from its window causes the mouse-dragging state machine to be reset. If so, immediately re-adding the view isn't going to restore the dragging state.

    This sounds very plausible.

    >
    > I think the trick is to avoid removing the view from its superview. I'm not sure of the *best* way of doing this, but here are the things I'd try:
    >
    > -- Invoke 'addSubview: self' without first removing self.

    On May 13, 2013, at 11:56 PM, Thomas Wetmore <ttw4...> wrote:
    > This works. Thanks for the tip.

    Oh, good. I was going to suggest instead of removing and re-adding self, that you remove and re-add all the sibling views that are in front of self so that they are behind self, using addSubview:positioned:relativeTo:. That would have been very ugly.

    --Andy
  • Not only does this work, it also does not add duplicates to the subviews array.

    Tom

    On May 13, 2013, at 11:56 PM, Thomas Wetmore <ttw4...> wrote:

    > This works. Thanks for the tip.
    >
    > Tom Wetmore
    >
    > On May 13, 2013, at 11:38 PM, Quincey Morris <quinceymorris...> wrote:
    >
    >> -- Invoke 'addSubview: self' without first removing self. The NSView machinery may be clever enough merely to move the view to the end of the subviews array, without actually removing and re-adding it.
    >
  • On 14 May, 2013, at 12:30 PM, Andy Lee <aglee...> wrote:

    > On May 13, 2013, at 11:38 PM, Quincey Morris <quinceymorris...> wrote:
    >> On May 13, 2013, at 19:41 , Graham Cox <graham.cox...> wrote:
    >>
    >>> When you call -removeFromSuperview, the view is deleted, as there are no more references to it. The other methods are not called because the object ceases to exist.
    >>
    >> I believe your warning is apposite, but is not actually the cause of Tom's problem. After removing the view, his code adds it back again. If the view had already been deallocated, you'd expect a pretty big crash pretty soon (though not necessarily immediately).
    >>
    >> My guess is that the removed view is still retained by something,
    >
    > I believe ARC keeps it alive by virtue of self being a strong reference. I did a quick test and found that if I do
    >
    > - (void) mouseDown: (NSEvent*) event
    > {
    > NSView* superView = [self superview];
    > [self removeFromSuperview];
    > //    [superView addSubview: self];
    > }
    >
    > ...then dealloc does in fact get called. But if I uncomment that one line, which references self, dealloc does not get called. This is good news -- I would want ARC to work regardless of whether it calls non-ARC code, and vice versa.
    >

    well you have several lines in there which reference self, [ self superview ] and [ self removeFromSuperview ]. The only difference in the last one is that self is used as a parameter to a call to another object and that may possibly be the difference.

    And the LLVM documents talk about this, they say that self is not retained during a method call.

    http://clang.llvm.org/docs/AutomaticReferenceCounting.html#self

    and discuss exactly this risk.
  • On May 13, 2013, at 21:30 , Andy Lee <aglee...> wrote:

    > I believe ARC keeps it alive by virtue of self being a strong reference.

    It isn't, not exactly. According to section 7.3 of the Clang ARC spec:

    "The self parameter variable of an Objective-C method is never actually retained by the implementation. It is undefined behavior, or at least dangerous, to cause an object to be deallocated during a message send to that object."

    This is the case when the receiver of the method invocation is itself a strong reference. If it's actually a __weak reference, it *is* retained for the duration of the method execution, because of the rules for retention of __weak objects used in expressions.

    > I did a quick test and found that if I do
    >
    > - (void) mouseDown: (NSEvent*) event
    > {
    > NSView* superView = [self superview];
    > [self removeFromSuperview];
    > //    [superView addSubview: self];
    > }
    >
    > ...then dealloc does in fact get called. But if I uncomment that one line, which references self, dealloc does not get called.

    I suspect it works because the ARC implementation is "suboptimal", in the sense that it's causing self to be autoreleased as a result of being used in a later expression. If the implementation ever improved to avoid using autorelease, I'd expect it to start crashing in this scenario.
  • On May 14, 2013, at 12:53 AM, Roland King <rols...> wrote:
    [...]
    >> - (void) mouseDown: (NSEvent*) event
    >> {
    >> NSView* superView = [self superview];
    >> [self removeFromSuperview];
    >> //    [superView addSubview: self];
    >> }
    >>
    >> ...then dealloc does in fact get called. But if I uncomment that one line, which references self, dealloc does not get called. This is good news -- I would want ARC to work regardless of whether it calls non-ARC code, and vice versa.
    >>
    >
    > well you have several lines in there which reference self, [ self superview ] and [ self removeFromSuperview ].

    I was thinking that if calling [self removeFromSuperview] removed the last reference to self, dealloc would have been called in both cases. Naturally I wouldn't expect self to have been dealloc'ed before that statement.

    > The only difference in the last one is that self is used as a parameter to a call to another object and that may possibly be the difference.

    I fiddled a bit with it, adding calls that don't take arguments like [self description] and [self self]. There was no dealloc until just before the method exited, after those calls. It *seems* that merely referring to self, whether as receiver or argument, keeps it alive. However, I might just have been lucky.

    > And the LLVM documents talk about this, they say that self is not retained during a method call.
    >
    > http://clang.llvm.org/docs/AutomaticReferenceCounting.html#self
    >
    > and discuss exactly this risk.

    Thanks. Just goes to show it's dangerous to guess too much -- in my case, that self is a strong reference -- from too little evidence.

    --Andy
  • On May 14, 2013, at 1:16 AM, Andy Lee <aglee...> wrote:
    > I fiddled a bit with it, adding calls that don't take arguments like [self description] and [self self]. There was no dealloc until just before the method exited, after those calls. It *seems* that merely referring to self, whether as receiver or argument, keeps it alive. However, I might just have been lucky.

    Or, as was suggested, something else might well have been retaining self.

    --Andy
previous month may 2013 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