Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS (iPad) Drag & Drop in a UISplitViewController

I'm working on a D&D in a UISplitViewController (based on Xcode template project), from the MasterViewController to the DetailViewController.

Basically, what I'm doing is creating a UILongPressGestureRecognizer and placing it on the self.tableview property of the MasterViewController.

Below is my gesture recognizer.

- (void)gestureHandler:(UIGestureRecognizer*)gesture
{
    CGPoint location;
    NSIndexPath* indexPath = [self.tableView indexPathForRowAtPoint:[gesture locationInView:self.tableView]];
    if (gesture.state == UIGestureRecognizerStateBegan) {
        // Create draggable view
        NSString* imageCanvasName = [self imageNameByIndexPath:indexPath isDetail:YES];
        UIImage* imageCanvas = [UIImage imageNamed:imageCanvasName];
        _draggedView = [[UIImageView alloc] initWithImage:imageCanvas];

        // Create drag-feedback window, add the drag-view and make the drag-window visible
        CGRect windowFrame = self.view.window.frame;
        _dragFeedbackWindow = [[UIWindow alloc] initWithFrame:windowFrame];
        location = [gesture locationInView:gesture.view.window];
        [_draggedView setCenter:location];
        [_dragFeedbackWindow addSubview:_draggedView];
        [_dragFeedbackWindow setHidden:NO];     
    }
    else if (gesture.state == UIGestureRecognizerStateChanged) {
        // Update drag-view location
        location = [gesture locationInView:gesture.view.window];
        [_draggedView setCenter:location];
    }
    else if (gesture.state == UIGestureRecognizerStateEnded)
    {
        // Disconnect drag-view and hide drag-feedback window
        [_draggedView removeFromSuperview];     
        [_dragFeedbackWindow setHidden:YES];

        // If drop is in a valid location...
        if ([self.tableView pointInside:_draggedView.center withEvent:nil] == NO)
        {
            // Get final location in detailViewController coordinates
            location = [gesture locationInView:self.detailViewController.view];
            [_draggedView setCenter:location];
            [self.detailViewController.view addSubview:_draggedView];
        }
    }
    else {
        NSLog(@"%s unrecognized gesture %d", __FUNCTION__, gesture.state);
    }
}

All works very nicely when the iPad is in portrait mode - the drag, the drop - the works.

My problem starts if the iPad is rotated... In such a case, my _draggedView appears "counter-rotated" - it will "reflect" the iPad's rotation - until dropped.

It's like I must apply some rotation to _dragFeedbackWindow - but I tried a number of things, failing...

Any idea?

Thanks!

like image 581
Reuven Avatar asked Jan 04 '12 06:01

Reuven


People also ask

How do you drag and drop on iOS 15?

Update your iPhone to iOS 15 to use the drag and drop feature. Long-press on a text, URL, image, or document to select it from the source app. Drag and drop the selected content on the appropriate location on the destination app.


2 Answers

OK - I figured out the "WHY" this happens, and the "HOW" to fix (and will attach the handler code to do this correctly, all below.

Here's the code... "Just" add it to your MAsterViewController (if - like me - you want to drag from the master to the detail...)

// A simple UIView extension to rotate it to a given orientation
@interface UIView(oriented)
- (void)rotateToOrientation:(UIInterfaceOrientation)orientation;
@end

@implementation UIView(oriented)
- (void)rotateToOrientation:(UIInterfaceOrientation)orientation {
    CGFloat angle = 0.0;    
    switch (orientation) { 
        case UIInterfaceOrientationPortraitUpsideDown:
            angle = M_PI; 
            break;
        case UIInterfaceOrientationLandscapeLeft:
            angle = - M_PI / 2.0f;
            break;
        case UIInterfaceOrientationLandscapeRight:
            angle = M_PI / 2.0f;
            break;
        default: // as UIInterfaceOrientationPortrait
            angle = 0.0;
            break;
    } 

    self.transform = CGAffineTransformMakeRotation(angle);
}

Now, the LongPress gesture handler...

- (void)gestureHandler:(UIGestureRecognizer*)gesture
{
    CGPoint location;
    UIWindow* dragFeedback = [UIApplication sharedApplication].delegate.window;
    if (gesture.state == UIGestureRecognizerStateBegan) {
        // Create draggable view
        _draggedView = [[UIImageView alloc] initWithImage:@"someImage.png"];

        // Required to adapt orientation... WORKS, BUT WHY NEEDED???
        [_draggedView rotateToOrientation:[[UIApplication sharedApplication] statusBarOrientation]];

        // Create drag-feedback window, add the drag-view and make the drag-window visible
        location = [gesture locationInView:dragFeedback];
        [_draggedView setCenter:location];
        [dragFeedback addSubview:_draggedView];
    }
    else if (gesture.state == UIGestureRecognizerStateChanged) {
        // Update drag-view location
        location = [gesture locationInView:dragFeedback];
        [_draggedView setCenter:location];
    }
    else if (gesture.state == UIGestureRecognizerStateEnded)
    {
        // Disconnect drag-view and hide drag-feedback window
        [_draggedView removeFromSuperview];     

        // Get final location in detailViewController coordinates
        location = [gesture locationInView:self.detailViewController.view];
        [_draggedView setCenter:location];
        // "Noramlize" orientation... WORKS, BUT WHY NEEDED???
        [_draggedView rotateToOrientation:UIInterfaceOrientationPortrait];
        [self.detailViewController.view addSubview:_draggedView];
    }
    else {
        NSLog(@"%s unrecognized gesture %d", __FUNCTION__, gesture.state);
    }
}

This works like a charm - let me know how it works for you (if you need a sample project, let me know...)

like image 73
Reuven Avatar answered Oct 01 '22 04:10

Reuven


Thanks for your solution! You got me 90% of the way there. In payment, I will give you the last 10% :)

The issue seems to be that the UIWindow never gets rotated, only its subviews do! So the solution is to use a subview.

I've also added some code that shows how to see which cell is selected (assuming your master view is a UITableViewController).

- (IBAction)handleGesture:(UIGestureRecognizer *)gesture
    {
        CGPoint location;
        UIWindow *window = [UIApplication sharedApplication].delegate.window;

        //The window doesn't seem to rotate, so we'll get the subview, which does!
        UIView *dragFeedback = [window.subviews objectAtIndex:0];
        if (gesture.state == UIGestureRecognizerStateBegan) {
            location = [gesture locationInView:dragFeedback];

            //which cell are we performing the long hold over?
            NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:[gesture locationInView:self.tableView]];
            UITableViewCell *selecteCell = [self.tableView cellForRowAtIndexPath:indexPath];
            NSLog(@"Cell %@", selecteCell);

            // Create draggable view
            _draggedView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"someImage.png"]];

            // Create drag-feedback window, add the drag-view and make the drag-window visible
            [_draggedView setCenter:location];
            [dragFeedback addSubview:_draggedView];
        }
        else if (gesture.state == UIGestureRecognizerStateChanged) {
            // Update drag-view location
            location = [gesture locationInView:dragFeedback];
            [_draggedView setCenter:location];
        }
        else if (gesture.state == UIGestureRecognizerStateEnded)
        {
            // Disconnect drag-view and hide drag-feedback window
            [_draggedView removeFromSuperview];    

            // Get final location in detailViewController coordinates
            location = [gesture locationInView:self.detailViewController.view];
            [_draggedView setCenter:location];
            [self.detailViewController.view addSubview:_draggedView];
        }
        else {
            NSLog(@"%s unrecognized gesture %d", __FUNCTION__, gesture.state);
        }
    }
like image 37
RefuX Avatar answered Oct 01 '22 04:10

RefuX