Отбрасывать UIImageView назад при перетаскивании с экрана

Что мне нужно, так это то, что когда UIImageView перетаскивается с экрана, он возвращается, когда его отпускают. У меня это работает слева и сверху, вот что я делаю.

    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {          

        if (!CGRectContainsPoint(self.view.frame, imageView.frame.origin)){  

            CGFloat newX = 0.0f;
            CGFloat newY = 0.0f;

            // If off screen upper and left

            if (imageView.frame.origin.x < 0.0f){
                CGFloat negX = imageView.frame.origin.x * -1;
                newX = negX;
            }else{
                newX = imageView.frame.origin.x;
            }

            if (imageView.frame.origin.y < 0.0f){
                CGFloat negY = imageView.frame.origin.y * -1;
                newY = negY;
            }else{
                newY = imageView.frame.origin.y;
            }


            CGRect newPoint = CGRectMake(newX, newY, imageView.frame.size.width, imageView.frame.size.height);

            [UIView beginAnimations:@"BounceAnimations" context:nil];
            [UIView setAnimationDuration:.5];
            [letterOutOfBounds play];
            [imageView setFrame:newPoint];

            [UIView commitAnimations];

        }
    }
}

Поэтому я хотел бы добиться того же для правой и нижней сторон. Но я застрял на этом некоторое время. Есть идеи?


person Jonathan    schedule 17.11.2012    source источник


Ответы (2)


Как насчет следующего?

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    UIImageView *imageView = nil;

    BOOL moved = NO;
    CGRect newPoint = imageView.frame;

    // If off screen left

    if (newPoint.origin.x < 0.0f){
        newPoint.origin.x *= -1.0;
        moved = YES;
    }

    // if off screen up

    if (newPoint.origin.y < 0.0f){
        newPoint.origin.y *= -1.0;
        moved = YES;
    }

    // if off screen right

    CGFloat howFarOffRight = (newPoint.origin.x + newPoint.size.width) - imageView.superview.frame.size.width;
    if (howFarOffRight > 0.0)
    {
        newPoint.origin.x -= howFarOffRight * 2;
        moved = YES;
    }

    // if off screen bottom

    CGFloat howFarOffBottom = (newPoint.origin.y + newPoint.size.height) - imageView.superview.frame.size.height;
    if (howFarOffBottom > 0.0)
    {
        newPoint.origin.y -= howFarOffBottom * 2;
        moved = YES;
    }

    if (moved)
    {
        [UIView beginAnimations:@"BounceAnimations" context:nil];
        [UIView setAnimationDuration:.5];
        [letterOutOfBounds play];
        [imageView setFrame:newPoint];

        [UIView commitAnimations];
    }
}

Когда я прочитал ваш код, логика «если с левой стороны, переместите его обратно в вид на то же расстояние, на котором он находился за пределами экрана». Честно говоря, это не совсем понятно для меня (почему при отскоке назад координата зависит от того, насколько далеко она находилась от экрана), но я попытался учесть это в «правом за пределами экрана» и логика «нижняя часть экрана». Очевидно, моя логика использует superview из imageView для определения ширины содержащего представления, но если это не подходит, замените его тем, что есть.

Изменить:

Я лично делаю это с помощью распознавателей жестов, таких как:

UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
[self.imageView addGestureRecognizer:pan];
self.imageView.userInteractionEnabled = YES;

Таким образом, распознаватель жестов для анимации перемещения изображения назад будет выглядеть так:

- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
    static CGRect originalFrame; // you could make this an ivar if you want, but just for demonstration purposes

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        originalFrame = self.imageView.frame;
    }
    else if (gesture.state == UIGestureRecognizerStateChanged)
    {
        CGPoint translate = [gesture translationInView:gesture.view];

        CGRect newFrame = originalFrame;

        newFrame.origin.x += translate.x;
        newFrame.origin.y += translate.y;

        gesture.view.frame = newFrame;
    }
    else if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateCancelled)
    {
        CGRect newFrame = gesture.view.frame;

        newFrame.origin.x = fmaxf(newFrame.origin.x, 0.0);
        newFrame.origin.x = fminf(newFrame.origin.x, gesture.view.superview.bounds.size.width - newFrame.size.width);

        newFrame.origin.y = fmaxf(newFrame.origin.y, 0.0);
        newFrame.origin.y = fminf(newFrame.origin.y, gesture.view.superview.bounds.size.height - newFrame.size.height);

        // animate how ever you want ... I generally just do animateWithDuration

        [UIView animateWithDuration:0.5 animations:^{
            gesture.view.frame = newFrame;
        }];
    }
}

Или, если вам нужен распознаватель жестов, который просто предотвращает перетаскивание изображения с экрана, это будет:

- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
    static CGRect originalFrame;

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        originalFrame = self.imageView.frame;
    }
    else if (gesture.state == UIGestureRecognizerStateChanged)
    {
        CGPoint translate = [gesture translationInView:gesture.view];

        CGRect newFrame = originalFrame;

        newFrame.origin.x += translate.x;
        newFrame.origin.x = fmaxf(newFrame.origin.x, 0.0);
        newFrame.origin.x = fminf(newFrame.origin.x, gesture.view.superview.bounds.size.width - newFrame.size.width);

        newFrame.origin.y += translate.y;
        newFrame.origin.y = fmaxf(newFrame.origin.y, 0.0);
        newFrame.origin.y = fminf(newFrame.origin.y, gesture.view.superview.bounds.size.height - newFrame.size.height);

        gesture.view.frame = newFrame;
    }
}

Кстати, в iOS 7 вы можете сделать анимацию просмотра изображения обратно в исходное положение немного упругой, используя новый animationWithDuration с параметрами usingSpringWithDampening и initialSpringVelocity:

[UIView animateWithDuration:1.0
                      delay:0.0
     usingSpringWithDamping:0.3
      initialSpringVelocity:0.1
                    options:0
                 animations:^{
                     // set the new `frame` (or update the constraint constant values that
                     // will dictate the `frame` and call `layoutViewsIfNeeded`)
                 }
                 completion:nil];

Кроме того, в iOS7 вы также можете использовать UIKit Dynamics для добавления UISnapBehavior:

self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
self.animator.delegate = self;

UISnapBehavior *snap = [[UISnapBehavior alloc] initWithItem:self.viewToAnimate snapToPoint:CGPointMake(self.viewToAnimate.center.x, self.view.frame.size.height - 50)];

// optionally, you can control how much bouncing happens when it finishes, e.g., for a lot of bouncing:
//
// snap.damping = 0.2;

// you can also slow down the snap by adding some resistance
//
// UIDynamicItemBehavior *resistance = [[UIDynamicItemBehavior alloc] initWithItems:@[self.viewToAnimate]];
// resistance.resistance = 20.0;
// resistance.angularResistance = 200.0;
// [self.animator addBehavior:resistance];

[self.animator addBehavior:snap];
person Rob    schedule 17.11.2012
comment
Это прекрасно работает, единственная проблема, с которой я столкнулся, это то, что self.view дает портретные размеры, когда он находится в ландшафте. CGRectContainsRect(self.view.frame, imageView.frame) - person Jonathan; 17.11.2012
comment
Ротация всегда досадная проблема. Кстати, если мой ответ работает для вас, пожалуйста, примите его, спасибо. - person sunkehappy; 17.11.2012
comment
@Jonathan В моем коде не используется self.view.frame, поэтому я не уверен, что слежу за вами. Я использую imageView.superview.frame, но вы говорите, что он возвращает портретные размеры даже в альбомной ориентации? - person Rob; 17.11.2012
comment
@Rob Извините, я не понял, но да, он возвращает портретные размеры, когда он в альбомной ориентации. - person Jonathan; 17.11.2012
comment
@Rob Кажется, это похоже на сборку, есть ли у вас лучшие идеи, чтобы сохранить UIImageView в представлении, может быть, сделать так, чтобы они даже не могли его перетащить? - person Jonathan; 17.11.2012
comment
@ Джонатан, я не хотел усложнять. Я просто копировал уже имевшуюся у вас логику и добавлял ее для двух дополнительных измерений. Но, да, вероятно, лучше перестать перетаскивать различные измерения, когда вы достигаете конца экрана, а не позволять им перетаскивать их за пределы экрана и отбрасывать обратно. - person Rob; 17.11.2012
comment
@Rob Хорошо, вот что я сделаю. Как лучше всего это сделать? Спасибо - person Jonathan; 17.11.2012
comment
@Jonathan Я обновил свой ответ несколькими примерами. По общему признанию, они оба с распознавателями жестов (которые, я думаю, проще, чем функции touches, но это вопрос личного вкуса), но, надеюсь, вы видите, как вы можете адаптировать их для своих целей. А с точки зрения ограничения того, где изображение может быть размещено, вы можете либо настроить код, либо создать подпредставление вашего контроллера представления, которое определяет границы того, куда можно перетаскивать изображение, а затем сделать представление изображения его подпредставлением. - person Rob; 17.11.2012
comment
@Jonathan Что касается вашего вопроса о том, что self.view.frame (или image view.superview.frame) не отражает размеры, когда вы переходите в альбомную ориентацию, это странность (ошибка?) В iOS. Если он у вас есть внутри навигационного контроллера, self.view.frame обновляется правильно, но если у вас нет навигационного контроллера, это не так. Итак, если у вас нет навигационного контроллера, я считаю, что self.view.bounds обновляется правильно (и, учитывая, что нас интересует только ширина, в любом случае использование bounds для родительского представления нормально), поэтому вы можете заменить суперпредставление frame ссылается на bounds. - person Rob; 17.11.2012

Я думаю, что самый простой способ - проверить, не вышел ли ваш imageView из вашего self.view.

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {  
    if (!CGRectContainsRect(self.view.frame, imageView.frame)){
        // Your animation to bounce.
    }
}
person sunkehappy    schedule 17.11.2012
comment
Я предполагаю, что вопрос заключался не только в том, как определить, переместилось ли изображение в точку, где оно находится за пределами экрана, но и в том, как определить новое местоположение. - person Rob; 17.11.2012