Мультисенсорные события

Обычно можно обработать почти все сенсорные события со стандартными средствами управления и устройствами распознавания жеста в UIKit. Устройства распознавания жеста позволяют Вам разделять распознавание касания от действия, которое производит касание. В некоторых случаях Вы хотите сделать что-то в своем приложении — таком как рисование от касания — где нет никакого преимущества к разъединению сенсорного распознавания от эффекта касания. Если содержание представления глубоко связано на ощупь самостоятельно, можно обработать сенсорные события непосредственно. Вы получаете сенсорные события, когда пользователь касается Вашего представления, интерпретируйте те события на основе их свойств и затем ответьте соответственно.

Создание подкласса UIResponder

Для Вашего приложения для реализации пользовательской сенсорной обработки событий сначала создайте подкласс класса респондента. Этот подкласс мог быть любым из следующего:

Подкласс

Почему Вы могли бы выбрать этот подкласс в качестве своего первого респондента

UIView

Подкласс UIView реализовать пользовательское представление получения.

UIViewController

Подкласс UIViewController если Вы также обрабатываете другие типы событий, такие как события движения встряски.

UIControl

Подкласс UIControl реализовать пользовательские элементы управления с сенсорным поведением.

UIApplication или UIWindow

Это было бы редко, потому что Вы обычно не разделяете на подклассы UIApplication или UIWindow.

Затем для экземпляров Вашего подкласса для получения мультисенсорных событий:

  1. Ваш подкласс должен реализовать UIResponder методы для сенсорной обработки событий, описанной в Реализации Методов Сенсорной Обработки событий в Вашем Подклассе.

  2. Касания получения представления должны иметь userInteractionEnabled набор свойств к YES. При разделении на подклассы контроллера представления представление, что он управляет, должно поддерживать взаимодействие с пользователем.

  3. Касания получения представления должны быть видимы; это не может быть прозрачным или скрытым.

Реализация методов Сенсорной Обработки событий в подклассе

iOS распознает касания как часть мультисенсорной последовательности. Во время мультисенсорной последовательности приложение отправляет ряд сообщений о событиях целевому респонденту. Чтобы получить и обработать эти сообщения, класс объекта респондента должен реализовать следующий UIResponder методы:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

Каждый из этих сенсорных методов соответствует сенсорной фазе: Начался, Перемещенный, Законченный, и Отмененный. Когда существуют новые или измененные касания для данной фазы, вызовы объектов приложения один из этих методов. Каждый метод берет два параметра: ряд касаний и события.

Набор касаний является набором (NSSet) UITouch объекты, представляя новые или измененные касания для той фазы. Например, когда сенсорные переходы от Начали фазу к Перемещенной фазе, приложение вызывает touchesMoved:withEvent: метод. Набор касаний передал в touchesMoved:withEvent: метод будет теперь включать это касание и все другие касания в Перемещенной фазе. Другой параметр является событием (UIEvent объект), который включает все сенсорные объекты для события. Это отличается от набора касаний, потому что некоторые сенсорные объекты в конечном счете могли не измениться начиная с предыдущего сообщения о событии.

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

Если Вы препятствуете объекту респондента получить касания для определенной фазы события, получающееся поведение может быть не определено и вероятно нежелательно.

Если респондент создает постоянные объекты при обработке событий, это должно реализовать touchesCancelled:withEvent: метод для избавления от тех объектов, если система отменяет последовательность. Когда внешнее событие — например, входящий телефонный вызов — разрушает обработку событий текущего приложения, отмена происходит. Обратите внимание на то, что объект респондента должен также избавиться от любых постоянных объектов, когда он получает последнее touchesEnded:withEvent: сообщение для мультисенсорной последовательности. Посмотрите Сенсорные События Передачи, чтобы узнать, как определить последнее UITouchPhaseEnded коснитесь объекта в мультисенсорной последовательности.

Отслеживание фазы и расположения сенсорного события

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

Сенсорные объектно-ориентированные памяти постепенно вводят информацию фаза свойство, и каждая фаза соответствует одному из сенсорных методов события. Сенсорное расположение объектно-ориентированных памятей тремя способами: окно, в котором касание произошло, представление в том окне и точное расположение касания в том представлении. Рисунок 3-1 показывает событие в качестве примера с двумя происходящими касаниями.

  Отношение рисунка 3-1 UIEvent возражает и его объекты UITouch
Relationship of a UIEvent object and its UITouch objects

Когда палец касается экрана, то касание связано и с базовым окном и с представлением для времени жизни события, даже если событие позже передается другому представлению для обработки. Используйте информацию о расположении касания, чтобы определить, как ответить на ощупь. Например, если два касания происходят в быстрой преемственности, они обрабатываются как двойное касание, только если они оба произошли в том же представлении. Сенсорные объектно-ориентированные памяти и его текущее расположение и его предыдущее расположение, если существует тот.

Получение и запросы сенсорных объектов

В методе обработки событий Вы получаете информацию о событии путем получения сенсорных объектов от:

multipleTouchEnabled свойство установлено в NO по умолчанию, что означает, что представление получает только первое касание в мультисенсорной последовательности. Когда это свойство отключено, можно получить сенсорный объект путем вызова anyObject метод на наборе возражает, потому что существует только один объект в наборе.

Если Вы хотите знать расположение касания, используйте locationInView: метод. Путем передачи параметра self к этому методу Вы получаете расположение касания в системе координат представления получения. Точно так же previousLocationInView: метод говорит Вам предыдущее расположение касания. Можно также определить, сколько касаний касание имеет (tapCount), когда касание создавалось или в последний раз видоизменилось (timestamp), и в чем находится фаза касание (phase).

Если Вы интересуетесь касаниями, не изменившимися начиная с последней фазы или которые находятся в различной фазе, чем касания в переданном - в наборе, можно найти, что те в конечном счете возражают. Рисунок 3-2 изображает объект-событие, содержащий сенсорные объекты. Для получения всех этих сенсорных объектов вызовите allTouches метод на объекте-событии.

Рисунок 3-2  Все касания для данного сенсорного события

Если Вы интересуетесь только касаниями, связанными с определенным окном, вызовите touchesForWindow: метод UIEvent объект. Рисунок 3-3 показывает все касания для окна A.

Рисунок 3-3  Все касания, принадлежащие определенному окну

Если Вы хотите связать касания с определенным представлением, вызовите touchesForView: метод объекта-события. Рисунок 3-4 показывает все касания для представления A.

Рисунок 3-4  Все касания, принадлежащие определенному представлению

Обработка жестов касания

Помимо способности распознать жест касания в Вашем приложении, Вы, вероятно, захотите отличить единственное касание, двойное касание, или даже тройное касание. Используйте касание tapCount свойство для определения числа раз, пользователь коснулся представления.

Лучшее место для нахождения этого значения touchesEnded:withEvent: метод, потому что это соответствует, когда пользователь шевелит пальцем от касания. Путем поиска касания рассчитывают в фазе подкраски — когда последовательность закончилась — Вы гарантируете, что палец действительно касается и не, например, приземляясь и перетаскивая. Перечисление 3-1 показывает пример того, как определить, произошло ли двойное касание в одном из Ваших представлений.

Перечисление 3-1  , Обнаруживающее двойной жест касания

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
}
 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    for (UITouch *aTouch in touches) {
        if (aTouch.tapCount >= 2) {
             // The view responds to the tap
             [self respondToDoubleTapGesture:aTouch];
        }
    }
}
 
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
}

Обработка сильно ударяет и перетаскивает жесты

Горизонталь и вертикальный сильно ударяет, простой тип жеста, который можно отследить. Для обнаружения сильно ударить жеста отследите перемещение пальца пользователя вдоль желаемой оси движения. Затем решите, является ли перемещение сильно ударением путем исследования следующих вопросов на каждый жест:

Для ответа на эти вопросы сохраните начальное расположение касания и сравните его расположение, когда перемещается касание.

Перечисление 3-2 показывает некоторые основные методы отслеживания, которые Вы могли использовать для обнаружения горизонтали, сильно ударяет в представлении. В этом примере представление имеет a startTouchPosition свойство, которое это использует для хранения начального расположения касания. В touchesEnded: метод, это сравнивает конечную сенсорную позицию со стартовым расположением, чтобы определить, является ли это сильно ударение. Если касание перемещается слишком далеко вертикально или не перемещается достаточно далеко, это не считается сильно ударением. Этот пример не показывает реализацию для myProcessRightSwipe: или myProcessLeftSwipe: методы, но пользовательское представление обработали бы сильно ударить жест там.

Перечисление 3-2  , Отслеживающее сильно ударить жест в представлении

#define HORIZ_SWIPE_DRAG_MIN  12
#define VERT_SWIPE_DRAG_MAX    4
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *aTouch = [touches anyObject];
    // startTouchPosition is a property
    self.startTouchPosition = [aTouch locationInView:self];
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
}
 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *aTouch = [touches anyObject];
    CGPoint currentTouchPosition = [aTouch locationInView:self];
 
    //  Check if direction of touch is horizontal and long enough
    if (fabsf(self.startTouchPosition.x - currentTouchPosition.x) >= HORIZ_SWIPE_DRAG_MIN &&
        fabsf(self.startTouchPosition.y - currentTouchPosition.y) <= VERT_SWIPE_DRAG_MAX)
    {
        // If touch appears to be a swipe
        if (self.startTouchPosition.x < currentTouchPosition.x) {
            [self myProcessRightSwipe:touches withEvent:event];
        } else {
            [self myProcessLeftSwipe:touches withEvent:event];
    }
    self.startTouchPosition = CGPointZero;
}
 
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    self.startTouchPosition = CGPointZero;
}

Заметьте, что этот код не проверяет расположение касания посреди жеста, что означает, что жест мог пойти на всем протяжении экрана, но все еще считаться сильно ударением, если его запуск и конечные точки в гармонии. Более сложное сильно ударяет, устройство распознавания жеста должно также регистрировать средние расположения touchesMoved:withEvent: метод. Для обнаружения сильно ударяют жесты в вертикальном направлении, Вы использовали бы подобный код, но подкачаете компоненты x и y.

Перечисление 3-3 показывает еще более простую реализацию отслеживания единственного касания, на сей раз пользователь перетаскивает представление вокруг экрана. Здесь, пользовательский класс представления полностью реализует только touchesMoved:withEvent: метод. Этот метод вычисляет значение дельты между текущими и предыдущими расположениями касания в представлении. Это тогда использует это значение дельты для сброса источника кадра представления.

Перечисление 3-3  , Перетаскивающее представление с помощью единственного касания

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *aTouch = [touches anyObject];
    CGPoint loc = [aTouch locationInView:self];
    CGPoint prevloc = [aTouch previousLocationInView:self];
 
    CGRect myFrame = self.frame;
    float deltaX = loc.x - prevloc.x;
    float deltaY = loc.y - prevloc.y;
    myFrame.origin.x += deltaX;
    myFrame.origin.y += deltaY;
    [self setFrame:myFrame];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
}
 
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
}

Обработка сложной мультисенсорной последовательности

Касания, перетаскивает и сильно ударяет, обычно включают только одно касание и просты отследить. Обработка сенсорного события, состоящего из двух или больше касаний, более сложна. Вам, вероятно, придется отследить все касания через все фазы, записав сенсорные атрибуты, изменившиеся и изменяющий внутреннее состояние соответственно. Чтобы отследить и обработать многократные касания, Вы должны:

При обработке события с многократными касаниями Вы часто храните информацию о состоянии касания так, чтобы можно было сравнить касания позже. Как пример, скажите, что Вы хотите сравнить заключительное расположение каждого касания с его исходным расположением. В touchesBegan:withEvent: метод, Вы получаете исходное расположение каждого касания от locationInView: метод и хранилище те в a CFDictionaryRef объект с помощью адресов UITouch объекты как ключи. Затем в touchesEnded:withEvent: метод, можно использовать адрес каждого, передал - в сенсорном объекте получить исходное расположение объекта и сравнить его с его текущим расположением.

Перечисление 3-4 иллюстрирует, как сохранить стартовые расположения UITouch объекты в Базовом словаре Основы. cacheBeginPointForTouches: метод хранит расположение каждого касания в координатах суперпредставления так, чтобы это имело общую систему координат для сравнения расположения всех касаний.

Перечисление 3-4  , Хранящее начинающиеся расположения многократных касаний

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
     [self cacheBeginPointForTouches:touches];
}
 
- (void)cacheBeginPointForTouches:(NSSet *)touches {
    if ([touches count] > 0) {
        for (UITouch *touch in touches) {
            CGPoint *point = (CGPoint *)CFDictionaryGetValue(touchBeginPoints, touch);
            if (point == NULL) {
                point = (CGPoint *)malloc(sizeof(CGPoint));
                CFDictionarySetValue(touchBeginPoints, touch, point);
            }
            *point = [touch locationInView:view.superview];
        }
    }
}

Перечисление 3-5 основывается на предыдущем примере. Это иллюстрирует, как получить начальные расположения из словаря. Затем это получает текущие расположения тех же касаний так, чтобы можно было использовать эти значения для вычислений аффинного преобразования (не показанный).

Перечисление 3-5  , Получающее начальные расположения сенсорных объектов

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
     CGAffineTransform newTransform = [self incrementalTransformWithTouches:touches];
}
 
- (CGAffineTransform)incrementalTransformWithTouches:(NSSet *)touches {
     NSArray *sortedTouches = [[touches allObjects] sortedArrayUsingSelector:@selector(compareAddress:)];
 
     // Other code here
     CGAffineTransform transform = CGAffineTransformIdentity;
 
     UITouch *touch1 = [sortedTouches objectAtIndex:0];
     UITouch *touch2 = [sortedTouches objectAtIndex:1];
 
     CGPoint beginPoint1 = *(CGPoint *)CFDictionaryGetValue(touchBeginPoints, touch1);
     CGPoint currentPoint1 = [touch1 locationInView:view.superview];
     CGPoint beginPoint2 = *(CGPoint *)CFDictionaryGetValue(touchBeginPoints, touch2);
     CGPoint currentPoint2 = [touch2 locationInView:view.superview];
 
 
     // Compute the affine transform
     return transform;
}

Следующий пример, Перечисление 3-6, не использует словарь для отслеживания сенсорных мутаций; однако, это обрабатывает многократные касания во время события. Это показывает пользовательское UIView касания ответа объекта путем анимации перемещения «Желанного» плаката как палец перемещают его вокруг экрана. Когда пользователь удваивает касания, это также изменяет язык плаката. Этот пример прибывает из проекта примера кода MoveMe, который можно исследовать для получения лучшего понимания контекста обработки событий.

Перечисление 3-6  , Обрабатывающее сложную мультисенсорную последовательность

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
 
     // App supports only single touches, so anyObject retrieves just
     // that touch from touches
     UITouch *touch = [touches anyObject];
 
     // Move the placard view only if the touch was in the placard view
     if ([touch view] != placardView) {
          // In case of a double tap outside the placard view, update
          // the placard's display string
          if ([touch tapCount] == 2) {
               [placardView setupNextDisplayString];
          }
          return;
     }
 
     // Animate the first touch
     CGPoint touchPoint = [touch locationInView:self];
     [self animateFirstTouchAtPoint:touchPoint];
}
 
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
 
     UITouch *touch = [touches anyObject];
 
     // If the touch was in the placardView, move the placardView to its location
     if ([touch view] == placardView) {
          CGPoint location = [touch locationInView:self];
          placardView.center = location;
     }
}
 
 
 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
 
    UITouch *touch = [touches anyObject];
 
    // If the touch was in the placardView, bounce it back to the center
    if ([touch view] == placardView) {
        // Disable user interaction so subsequent touches
        // don't interfere with animation
        self.userInteractionEnabled = NO;
        [self animatePlacardViewToCenter];
    }
}
 
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
 
     // To impose as little impact on the device as possible, simply set
     // the placard view's center and transformation to the original values
     placardView.center = self.center;
     placardView.transform = CGAffineTransformIdentity;
}
 

Для обнаружения, когда последним пальцем в мультисенсорной последовательности шевелят от представления посмотрите, сколько сенсорных объектов находится в переданном - в наборе и сколько находится в переданном - в UIEvent объект. Если число является тем же, то мультисенсорная последовательность завершила. Перечисление 3-7 иллюстрирует, как сделать это в коде.

Перечисление 3-7  , Определяющее, когда закончилось последнее касание в мультисенсорной последовательности

- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
    if ([touches count] == [[event touchesForView:self] count]) {
        // Last finger has lifted
    }
}

Помните, что переданный - в наборе содержит все сенсорные объекты, связанные с представлением, которые являются новыми или изменяются для данной фазы, тогда как сенсорные объекты возвратились из touchesForView: метод включает все объекты, связанные с указанным представлением.

Указание пользовательского сенсорного поведения события

Настройте путь Ваши касания дескрипторов приложения путем изменения поведения определенного жеста, определенного представления или всех сенсорных событий в приложении. Можно изменить поток сенсорных событий следующими способами:

Можно также выключить поставку сенсорного события полностью, или только сроком на время:

Прерывание касаний переопределяющим тестированием хита

Если у Вас есть пользовательское представление с подпредставлениями, необходимо определить, хотите ли Вы обработать, заходит в уровень подпредставления или уровень суперпредставления. Если Вы приняли решение обработать, заходит в уровень суперпредставления — подразумевать, что Ваши подпредставления не реализуют touchesBegan:withEvent:, touchesEnded:withEvent:, или touchesMoved:withEvent: методы — тогда суперпредставление должны переопределить hitTest:withEvent: возвратить себя, а не любое из его подпредставлений.

Переопределяющее тестирование хита гарантирует, что суперпредставление получает все касания, потому что путем установки себя как представление теста хита суперпредставление прерывает и получает касания, обычно передающиеся подпредставлению сначала. Если суперпредставление не переопределяет hitTest:withEvent:, сенсорные события связаны с подпредставлениями, где они сначала произошли и никогда не отправляются в суперпредставление.

Вспомните, что существует два метода тестирования хита: hitTest:withEvent: метод представлений и hitTest: метод уровней, как описано в Тестирующих хит Возвратах Представление, Где Произошло Касание. Редко необходимо вызывать эти методы сами. Более вероятно, что Вы переопределите их для прерывания сенсорных событий от подпредставлений. Однако иногда респонденты выполняют тестирование хита до передачи события (см. Сенсорные События Передачи).

Передача сенсорных событий

Для передачи сенсорных событий к другому объекту респондента отправьте надлежащие сообщения сенсорной обработки событий в тот объект. Используйте этот метод с осторожностью, потому что классы UIKit не разработаны для получения касаний, не связывающихся с ними. Поскольку респондент возражает для обработки касания, касание view свойство должно содержать ссылку на респондента. Если Вы хотите условно передать касания другим респондентам в Вашем приложении, все респонденты должны быть собственными экземплярами UIView подкласс.

Например, скажем, приложение имеет три пользовательских представления: A, B, и C. Когда пользователь касается представления A, окно приложения решает, что это - представление теста хита и отправляет в него начальное сенсорное событие. В зависимости от определенных условий просмотрите форвардов событие или к представлению B или к представлению C. В этом случае представления A, B, и C должны знать об этой передаче и просматривают B, и C должен быть в состоянии иметь дело с касаниями, не связывающимися с ними.

Событие, передающее часто, требует, чтобы касание анализа возразило для определения, где они должны быть переданы. Существует несколько подходов, которые можно проявить для этого анализа:

Переопределение sendEvent: метод позволяет Вам следить за развитием событий, которые получает Ваше приложение. Оба UIApplication возразите и каждый UIWindow возразите событиям отгрузки в sendEvent: метод, таким образом, этот метод служит точкой трубы для событий, входящих к приложению. Это - что-то, что очень немного приложений должны сделать и, если Вы действительно переопределяете sendEvent:, обязательно вызовите реализацию суперкласса —[super sendEvent:theEvent]. Никогда не вмешивайтесь в распределение событий.

Перечисление 3-8 иллюстрирует этот метод в подклассе UIWindow. В этом примере события передаются пользовательскому респонденту помощника, выполняющему аффинные преобразования на представлении, что он связан с.

Перечисление 3-8  , Передающее сенсорные события к объектам респондента помощника

- (void)sendEvent:(UIEvent *)event {
    for (TransformGesture *gesture in transformGestures) {
        // Collect all the touches you care about from the event
        NSSet *touches = [gesture observedTouchesForEvent:event];
        NSMutableSet *began = nil;
        NSMutableSet *moved = nil;
        NSMutableSet *ended = nil;
        NSMutableSet *canceled = nil;
 
        // Sort touches by phase to handle—-similar to normal event dispatch
        for (UITouch *touch in touches) {
            switch ([touch phase]) {
                case UITouchPhaseBegan:
                    if (!began) began = [NSMutableSet set];
                    [began addObject:touch];
                    break;
                case UITouchPhaseMoved:
                    if (!moved) moved = [NSMutableSet set];
                    [moved addObject:touch];
                    break;
                case UITouchPhaseEnded:
                    if (!ended) ended = [NSMutableSet set];
                    [ended addObject:touch];
                    break;
                case UITouchPhaseCancelled:
                    if (!canceled) canceled = [NSMutableSet set];
                    [canceled addObject:touch];
                    break;
                default:
                    break;
            }
        }
        // Call methods to handle the touches
        if (began)     [gesture touchesBegan:began withEvent:event];
        if (moved)     [gesture touchesMoved:moved withEvent:event];
        if (ended)     [gesture touchesEnded:ended withEvent:event];
        if (canceled) [gesture touchesCancelled:canceled withEvent:event];
    }
    [super sendEvent:event];
}

Заметьте, что переопределяющий подкласс вызывает реализацию суперкласса sendEvent: метод. Это важно для целостности сенсорного потока событий.

Методы наиболее успешной практики для обработки мультисенсорных событий

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