В этой статье пойдет речь о том, как быть, если приложение «крэшится» когда QA с ехидным оскалом барабанит пальцами по экрану девайса. Будем рассматривать в основном бизнес-приложения(ибо из них вынесен этот опыт). Здесь налицо ситуация, когда на некоторые кнопки нажимать одновременно нельзя или, скажем, когда приложение анимирует(некрасивый термин, но что поделать) от одного экрана к другому, а пользователь уже успел нажать кнопку еще на старом экране. Или, скажем, у QA(а потенциально и у пользователя) большой (или маленький) палец — тогда он или не может попасть по кнопке или не может не попасть по кнопке. В таких случаях может потребоваться расширить рабочую область кнопки или сузить ее. Т.е. можно выделить 3 проблемы:
1. «secret combos». Есть UI элементы(кнопки, поля ввода и другие контролы) нажатие на которые вообще не совместимо. Соответственно нужно блокировать их .
2. «Intermediate states». Есть промежуточные, нестабильные состояния приложения, в которые с приложением не стоит взаимодействовать изза «переходных процессов» внутри. В этом случае нужно обождать до перехода в стабильное положение и тогда разрешать взаимодействие.
3. «custom hit area». Т.е. можно сделать чувствительную область кнопки или любого другого контрола круглой, квадратной, звездочкой — на что хватит воображения.
Для начала рассмотрим способы регуляции доставки «тачей» (UITouch) приложению. В доках выделили следующие способы:
- Отключить доставку тачей для элемента при помощи
userInteractionEnabled
property. В этом случае тачи этому контролу не будут доставляться вообще. Это свойство находится в UIView. - Отключить доставку тачей глобально для всего приложения при помощи
beginIgnoringInteractionEvents
иendIgnoringInteractionEvents
. Это свойство находится в классе UIApplication. - Отключить мультитач для компонента (несколько пальцев на компоненте одновременно) при помощи
multipleTouchEnabled
свойства UIView. - Метод «эксклюзивного тача» — если компонент нажат и не отпущен — тачи в другие компоненты игнорируются. Это делается при помощи
exclusiveTouch
свойства UIView. - Hit-Testing. Этот метод позволяет вручную решать, попадает ли этот тач в этот компонент или нет. полезен для расширениясужения чувствительных областей компонентов.
Теперь рассмотрим, как можно решить те 3 проблемы, обозначенные вначале с помощью «методов тачерегуляции».
Secret Combos
В UI-ном неигровом приложении чаще всего не нужно чтобы можно было нажимать на несколько кнопок одновременно. Нажал на кнопку — чтото отработалось. Нажал на вторую — еще чтонибудь сделалось. Но нажимать на вторую, удерживая первую — не типичный случай. Так вот, запретить нажатия на все остальные кнопки, когда уже чтонибудь нажато можно с помощью эксклюзивного тача (exclusiveTouch
). Устанавливаем его всем кнопкам — и наслаждаемся. Теперь, если мы нажали на один компонент и удерживаем палец на нем — все остальные нажатия на другие компоненты игнорируются.
Intermediate states
В переходных состояниях(смена экранов, анимирование переориентации, чтото анимированно появляется или исчезает, показываем модальный контроллер) лучше всего вообще отключать доставку тачей с помощью beginIgnoringInteractionEvents
и endIgnoringInteractionEvents
.
Сustom Hit Area
Система, чтобы определить, по чему нажал пользователь вызывает у самого главного UIView приложения метод hitTest:withEvent:. Этот метод полчает на вход CGPoint в своих координатах(bounds)
Система использует метод hitTest:withEvent: у UIView для определения контрола, которому принадлежит эта точка, чтобы доставить ему это событие. Если hitTest возвращает какойто свой subview — то остальные ветки иерархии subviews отбрасываются и hitTest вызывается у этого subview — и так до тех пор пока hitTest у uiview не вернет свою же uiview. Для определения, принадлежит ли этот CGPoint этому uiview — hitTest вызывает pointInside:(CGPoint)point у каждого из своих subview. Если метод возвращает YES — значит принадлежит.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
Пример
Теперь рассмотрим тестовый проект в котором есть реализации этих случаев. UI выглядит так:
PushMe и PushMeToo кнопки и рядом переключатели которые вклчают exclusive touch для каждой из них раздельно.
Ниже — кнопка у которой расширена чувствительная область вправо и влево на 30 px и вверхвниз на 20 px. Это можно наблюдать нажимая рядом с ней, а не на ней(меняется подпись). Это можно попробовать даже в симуляторе с зажатыми alt а потом shift.
Еще ниже есть кнопка Start animation — по нажатию на нее кнопка i am flying! начинает двигаться. Двигаться она будет 5 секунд. И в то время, когда она движется — отключена доставка тачей при помощи beginIgnoringInteractionEvents
и endIgnoringInteractionEvents
. Так же можно по свойству UIApplication
- (BOOL)isIgnoringInteractionEvents
определить, игнорируются тачи или нет в данный момент. Но с этим вариантом нужно быть аккуратным, так как, если по каким-либо причинам мы не смогли включить обратно endIgnoringInteractionEvents — приложение перестанет реагировать на пользователя вообще.
Теперь посмотрим исходный код.
С exclusiveTouch все и так понятно — установил в YESNO — и все дела. А вот для кнопки I am big! нужно наследоваться от UIButton и перегрузить pointsInside следующим образом:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
CGRect bounds = self.bounds;
//Calculate offsets from buttons bounds
bounds = CGRectMake(bounds.origin.x - 30.0,
bounds.origin.y - 20.0,
bounds.size.width + 60.0,
bounds.size.height + 40.0);
return CGRectContainsPoint(bounds, point);
}
Этот метод вызывается каждый раз, когда UIView, в котором находится данный компонент хочет узнать, попадает ли данный тач в данную кнопку, или нет.
Отключение интерфеса и анимация сделаны следующим образом:
-(IBAction)startAnimation:(id)sender{
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[UIView animateWithDuration:5.0 animations:^{
_flyingButton.center = CGPointMake(248.0, 408.0);
} completion:^(BOOL finished) {
_flyingButton.center = CGPointMake(76.0, 408.0);
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}];
}
Работающий проект можно взять тут
Проект создавался в XCode 4.2. Для пользования блоков при анимации нужно iOS 4.0 +. Тестирование multitouch в симуляторе можно сделать при помощи мышки, alt и shift.
Материал предоставлен компанией Softeq Development, FLLC