При перемещении UILabel по вертикали с помощью UIPanGestureRecognizer, как мне помешать им зайти слишком далеко в любом направлении?

Я пытаюсь дать пользователю возможность перемещать UILabel вверх и вниз по представлению, присоединив UIPanGestureRecognizer к UILabel и впоследствии изменив константу ограничения с UILabel на верхнюю часть его представления. Таким образом, в основном, если распознаватель жестов обнаруживает, что они перемещаются вниз на 12 пунктов, переместите константу ограничения на 12 пунктов, чтобы переместить UILabel.

Однако я хочу, чтобы они не двигались дальше, когда они достигают определенной вертикальной точки (как слишком высокой, так и слишком низкой). Я мог бы просто проверить перевод жеста панорамирования, но мой UILabel может состоять из любого количества строк, поэтому, если это пять строк вместо одной, очевидно, что его нельзя панорамировать так далеко, поэтому я не могу полагаться на перевод жеста панорамирования, я должен учитывать размер метки.

Итак, я начал отслеживать его кадр, и он работает хорошо, но в моей реализации есть раздражающий результат: если они полностью панорамируются до нижнего предела, они должны панорамироваться очень далеко назад, прежде чем UILabel «догоняет» и идет с ним ( хотя такой проблемы не существует, когда они достигают верхней границы). По сути, они перемещаются вниз до нижнего предела, а когда они перемещаются обратно вверх (все это выполняется одним и тем же жестом), он на мгновение «залипает», пока они не перемещаются достаточно далеко вверх, а затем подпрыгивают пальцем вверх.

Вот код, который я использую для этого:

- (void)textLabelPanned:(UIPanGestureRecognizer *)panGestureRecognizer {
    if (panGestureRecognizer.state == UIGestureRecognizerStateBegan) {
        _textDistanceFromTopBeforeMove = self.textToReadLabelPositionFromTopConstraint.constant;
    }
    else if (panGestureRecognizer.state == UIGestureRecognizerStateEnded) {
        NSNumber *textDistanceFromTop = @(self.textToReadLabelPositionFromTopConstraint.constant);
        [[NSUserDefaults standardUserDefaults] setObject:textDistanceFromTop forKey:@"TextDistanceFromTop"];
    }
    else {
        if (CGRectGetMinY(self.textToReadLabel.frame) >= [UIScreen mainScreen].bounds.origin.y + CLOSEST_TEXT_DISTANCE_TO_TOP && CGRectGetMaxY(self.textToReadLabel.frame) <= [UIScreen mainScreen].bounds.size.height - CLOSEST_TEXT_DISTANCE_TO_BOTTOM) {
                self.textToReadLabelPositionFromTopConstraint.constant = _textDistanceFromTopBeforeMove + [panGestureRecognizer translationInView:self.mainView].y;
        }
        else if ([panGestureRecognizer translationInView:self.mainView].y > 0) {
            if (CGRectGetMaxY(self.textToReadLabel.frame) + _textDistanceFromTopBeforeMove + [panGestureRecognizer translationInView:self.mainView].y < [UIScreen mainScreen].bounds.size.height - CLOSEST_TEXT_DISTANCE_TO_BOTTOM) {
                self.textToReadLabelPositionFromTopConstraint.constant = _textDistanceFromTopBeforeMove + [panGestureRecognizer translationInView:self.mainView].y;
            }
        }
        else if ([panGestureRecognizer translationInView:self.mainView].y < 0) {
            if (CGRectGetMinY(self.textToReadLabel.frame) + _textDistanceFromTopBeforeMove + [panGestureRecognizer translationInView:self.mainView].y > [UIScreen mainScreen].bounds.origin.y + CLOSEST_TEXT_DISTANCE_TO_TOP) {
                self.textToReadLabelPositionFromTopConstraint.constant = _textDistanceFromTopBeforeMove + [panGestureRecognizer translationInView:self.mainView].y;
            }
        }


        // If one of the options views are present and the user pans really low, hide the options as to allow the user to see where they're panning
        if (_inSpeedChangingMode) {
            if (CGRectGetMaxY(self.textToReadLabel.frame) > CGRectGetMinY(self.progressBar.frame) - 10) {
                [self showWordOptions:nil];
            }
        }
        else if (_inTextChangingMode) {
            if (CGRectGetMaxY(self.textToReadLabel.frame) > CGRectGetMinY(self.progressBar.frame) - 10) {
                [self showTextOptions:nil];
            }
        }
    }
}

Что именно я делаю неправильно, из-за чего он «залипает»? И, возможно, есть лучший способ сделать это?


person Doug Smith    schedule 29.10.2013    source источник


Ответы (2)


Вы можете выполнить это полностью с ограничениями, определенными либо в Интерфейсном Разработчике, либо в коде. Хитрость заключается в том, чтобы определить ограничения, препятствующие перемещению метки за границы, имеющие более высокий приоритет, чем ограничения, устанавливающие желаемое положение.

В моем тестовом проекте я полностью настроил иерархию представлений в раскадровке, имеющей 1) представление контроллера представления 2) «представление контейнера», которое определяет границы 3) многострочный UILabel. На этикетку из ее контейнера действуют 6 ограничений:

  • 4 ограничения «пробел до» (начало, конец, верх, низ) предотвращают размещение метки за пределами родительского контейнера. Приоритет для них установлен на значение по умолчанию '1000' в Интерфейсном Разработчике. Отношение для этих ограничений — «>=», а постоянное значение — «0».

  • 2 ограничения «отступ до» (сначала, сверху) управляют фактической позицией метки. Приоритет для них установлен ниже; Я выбрал «500». Эти ограничения имеют выходы в контроллере представления, поэтому их можно настроить в коде. Отношение для этих ограничений — «=», а начальное значение — это то, что вы хотите расположить метку.

Сама метка имеет ограничение по ширине, чтобы заставить ее отображаться с несколькими строками.

Вот как это выглядит в IB:

Выбранное ограничение имеет более низкий приоритет и используется для управления положением метки по оси X. Это ограничение привязано к ivar в контроллере представления, поэтому его можно настроить во время выполнения. показ позиционирования  выбрано ограничение

Выбранное ограничение имеет более высокий приоритет и используется для закрепления метки в родительском представлении.   отображение выбранного ограничения кораллирования

А вот код в контроллере представления:

@interface TSViewController ()
@end

@implementation TSViewController
{
    IBOutlet NSLayoutConstraint* _xLayoutConstraint;

    IBOutlet NSLayoutConstraint* _yLayoutConstraint;
}

- (IBAction) pan: (UIGestureRecognizer*) pgr
{
    CGPoint p = [pgr locationInView: self.view];

    p.x -= pgr.view.frame.size.width / 2.0;
    p.y -= pgr.view.frame.size.height / 2.0;

    _xLayoutConstraint.constant = p.x;
    _yLayoutConstraint.constant = p.y;
}

@end

UIPanGestureRecognizer связан с UILabel, и его обратный вызов установлен на метод pan: в контроллере представления.

person TomSwift    schedule 31.10.2013
comment
Не могли бы вы прикрепить свой тестовый проект? - person Doug Smith; 02.11.2013
comment
А, понял. Красивое решение. - person Doug Smith; 04.11.2013

Если ваше приложение имеет минимальный SDK iOS7, вы можете использовать UIKit Dynamics вместо этих UIGestureRecognizers. Ваша проблема может быть легко решена с помощью UICollisionBehavoir в сочетании с UIAttachmentBehavior.

Возможно, вы захотите изучить это. Вот пример проекта Apple в UIKit Dynamics: https://developer.apple.com/library/IOS/samplecode/DynamicsCatalog/Introduction/Intro.html

Поэкспериментируйте с ним, и вы будете поражены тем, что можно сделать с таким небольшим количеством кода.

Сессии WWDC 2013: - Начало работы с UIKit Dynamics - Расширенные методы работы с UIKit Dynamics

person Thomas Keuleers    schedule 31.10.2013