7

The moment I receive touchesBegan, I want to removeFromSuperview the view that was touched and addSuperview to a new parent view, and then continue to receive touches. However I am finding that sometimes this does not work. Specifically, touchesMoved and touchesEnded are never called.

Is there a trick for making this work correctly? This is for implementing a drag and drop behavior, where the view is initially inside a scroll view.

Thanks.

Kev
  • 118,037
  • 53
  • 300
  • 385

2 Answers2

7

Instead of:

[transferView removeFromSuperView];
[newParentView addSubview:transferView];

Use only:

[newParentView addSubview:transferView];

The documentation states: "Views can have only one superview. If view already has a superview and that view is not the receiver, this method removes the previous superview before making the receiver its new superview."

Therefore there is no need to use removeFromSuperView because it is handled by addSubview. I have noticed that removeFromSuperView ends any current touches without calling touchesEnded. If you use only addSubview, touches are not interrupted.

Josh Bernfeld
  • 4,246
  • 2
  • 32
  • 35
  • 1
    This should be the accepted answer as it is not necessary to manage dragging in the superview to achieve the OP's goal. Simply removing **removeFromSuperView** does the trick. Thanks **walapu**. – walkingbrad Mar 04 '13 at 23:18
  • Great answer, this was very helpful! – Max Jul 24 '15 at 16:24
0

You need to process your touches in the superview instead of in the view that you want switched out. This will allow you to switch out the view without loosing your touch events. When you do this though, you'll have to test yourself whether the touch is occurring in the specific subview you want switched out. This can be done many ways, but here are some methods to get you started:

Converting Rects/Point to another view:

[view convertRect:rect toView:subview];
[view convertPoint:point toView:subview];

Here are some methods to test if the point is located in the view:

[subView hitTest:point withEvent:nil];
CGRectContainsPoint(subview.frame, point); //No point conversion needed
[subView pointInside:point withEvent:nil];

In general, it's better to use UIGestureRecognizers. For example, if you were using a UIPanGestureRecognizer, you would create a method that the gesture recognizer can call and in that method you do your work. For example:

- (void) viewPanned:(UIPanGestureRecognizer *)pan{
    if (pan.state == UIGestureRecognizerStateBegan){
        CGRect rect = subView.frame;
        newView = [[UIView alloc] initWithFrame:rect];
        [subView removeFromSuperview];
        [self addSubview:newView];
    } else if (pan.state == UIGestureRecognizerStateChanged){
        CGPoint point = [pan locationInView:self];
        newView.center = point;
    } else {
        //Do cleanup or final view placement
    }
}

Then you init the recognizer, assign it to the target (usually self) and add it:

[self addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(viewPanned:)]];

Now self (which would be the superview managing it's subviews) will respond to pan motions.

Aaron Hayman
  • 8,492
  • 2
  • 36
  • 63
  • Not sure why that would be. Can you post code? BTW, what's keeping you from using a UIGestureRecognizer? In almost every case I've considered using Touches, I've found that either an existing UIGestureRecognizer or subclassing UIGestureRecognizer works better (and I can reuse it). – Aaron Hayman Apr 05 '12 at 16:03
  • If you're using the stock UIPanGestureRecognizer, `touchesBegan` shouldn't be in your code. You should setup some target method like this: `- (void) viewPanned:(UIPanGestureRecognizer)pan`. When you init the recognizer you set its target: `[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(viewPanned:)]`. That method will get called repeatedly and you can check the state and perform the appropriate actions: `If (pan.state == UIGestureRecognizerStateBegan){ .......}` or `If (pan.state == UIGestureRecognizerStateChanged{ ..... }` etc... – Aaron Hayman Apr 05 '12 at 18:32
  • I expanded my answer to reflect this. – Aaron Hayman Apr 05 '12 at 18:44
  • I notice that you're removing the view and creating another to add to the target. I am definitely finding that when I remove the original from the superview, it ceases to receive touches, even if I re-add it to another view immediately afterwards. –  Apr 05 '12 at 19:17