Даже если ваше приложение работает плавно со скоростью 60 кадров в секунду, высокопроизводительные устройства iOS имеют скорость сканирования сенсора 120 герц и стандартную стандартную обработку касаний, вы можете легко пропустить половину движений касания вашего пользователя.
К счастью, с iOS 9 Apple представила некоторые новые функции в UIEvent, чтобы уменьшить задержку касания и улучшить временное разрешение. Есть демонстрационное приложение, которое вы можете найти в моем репозитории GitHub здесь .
Touch Coalescing
Первая особенность — сенсорная коалесценция. Если ваше приложение выполняет сэмплирование касаний с помощью переопределенного метода touchesMoved (), то наибольшее количество сэмплов, которое вы получите, составляет 60 в секунду, и, если ваше приложение заботится о себе с помощью некоторых необычных вычислений в главном потоке, оно может быть меньше тот.
Слияние касаний позволяет получить доступ ко всем промежуточным касаниям, которые могли произойти между вызовами touchesMoved (). Это позволяет, например, вашему приложению нарисовать плавную кривую, состоящую из полдюжины точек, когда вы получили только один UIEvent.
Синтаксис очень прост. В моем демонстрационном приложении у меня есть несколько экземпляров CAShapeLayer, на которые я опираюсь. Первый слой (mainDrawLayer, который содержит синие линии с кругами в каждой вершине) рисуется с использованием информации из основного UIEvent от touchesMoved ():
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
{
super.touchesMoved(touches, withEvent: event)
guard let touch = touches.first, event = event else
{
return
}
let locationInView = touch.locationInView(view)
mainDrawPath.addLineToPoint(locationInView)
mainDrawPath.appendPath(UIBezierPath.createCircleAtPoint(locationInView, radius: 4))
mainDrawPath.moveToPoint(locationInView)
mainDrawLayer.path = mainDrawPath.CGPath
[...]
Если вам интересно, откуда взялся метод createCircleAtPoint (), я написал небольшое расширение для UIBezierPath, которое возвращает круговой путь в заданной точке:
extension UIBezierPath
{
static func createCircleAtPoint(origin: CGPoint, radius: CGFloat) -> UIBezierPath
{
let boundingRect = CGRect(x: origin.x - radius,
y: origin.y - radius,
width: radius * 2,
height: radius * 2)
let circle = UIBezierPath(ovalInRect: boundingRect)
return circle
}
}
Если пользователь проведет пальцем по экрану достаточно быстро, он получит зубчатую линию, что, вероятно, не то, что он хочет. Для доступа к промежуточным касаниям я использую метод события coalescedTouchesForTouch, который возвращает массив UITouch:
[...]
if let coalescedTouches = event.coalescedTouchesForTouch(touch)
{
print("coalescedTouches:", coalescedTouches.count)
for coalescedTouch in coalescedTouches
{
let locationInView = coalescedTouch.locationInView(view)
coalescedDrawPath.addLineToPoint(locationInView)
coalescedDrawPath.appendPath(UIBezierPath.createCircleAtPoint(locationInView, radius: 2))
coalescedDrawPath.moveToPoint(locationInView)
}
coalescedDrawLayer.path = coalescedDrawPath.CGPath
}
[...]
Здесь я зацикливаю эти касания (отслеживаю количество касаний консоли для информации) и создаю желтую линию, рисуя oncoalescedDrawLayer в качестве наложения. Я добавил ужасный цикл в метод touchesMoved, чтобы немного замедлить процесс:
var foo = Double(1)
for bar in 0 ... 1_000_000
{
foo += sqrt(Double(bar))
}
Если вы запустите это приложение на своем устройстве iOS 9 и начертите его, вы увидите два результата: зубчатую синюю линию и красивую гладкую желтую линию, основанную на всех тех промежуточных событиях касания, которые могли быть пропущены. В каждом месте прикосновения я добавляю маленький кружок, который четко иллюстрирует увеличенное разрешение желтой кривой на основе объединенных данных.
Прогнозирующее прикосновение
Что-то, возможно, даже более умное — это сенсорное предсказание, которое позволяет вам заранее определить, где палец пользователя (или Apple Pencil) может оказаться в ближайшем будущем. Интеллектуальное прикосновение использует некоторые тщательно настроенные алгоритмы и постоянно обновляется в соответствии с тем, где iOS ожидает, что прикосновения пользователей будут примерно в кадре в будущем. Это означает, что вы можете начать подготовку компонентов пользовательского интерфейса (например, начать постепенное изменение или создание экземпляров некоторых объектов) до того, как они фактически потребуются для уменьшения задержки.
В моем демонстрационном приложении я отображаю предсказанное касание в виде маленьких белых пятен с «хвостами», которые возникают из-за их предсказывающего касания. Синтаксис не отличается от синтаксиса коалесцированного прикосновения: у события есть новый методgnatedTouchesForTouch (), который возвращает массив UITouch:
[...]
if let predictedTouches = event.predictedTouchesForTouch(touch)
{
print("predictedTouches:", predictedTouches.count)
for predictedTouch in predictedTouches
{
let locationInView = predictedTouch.locationInView(view)
predictedDrawPath.moveToPoint(touch.locationInView(view))
predictedDrawPath.addLineToPoint(locationInView)
predictedDrawPath.appendPath(UIBezierPath.createCircleAtPoint(locationInView, radius: 1))
}
predictedDrawLayer.path = predictedDrawPath.CGPath
}
[...]
Когда вы запускаете приложение, когда вы перемещаете палец по экрану, маленькие предсказанные точки касания дают представление о том, где iOS думает о вашем следующем касании. С неровным движением, когда предсказание не работает так хорошо, вы можете видеть эти точки как маленькие головастики после импульса вашего прикосновения.
Вы можете видеть несколько серых кружков на снимке экрана выше, которые показывают два прикосновения, предсказанные iOS, которые я бы сделал, чтобы завершить спираль — чего я никогда не делал! Пугающий!
Вывод
Поскольку пользователи требуют меньших задержек и более высокого разрешения касания, объединение касаний и прогнозирование позволяют нашим приложениям поддерживать это. Независимо от частоты кадров наших приложений, мы можем реагировать на все жесты пользователя и даже выгружать некоторые из них!
Исходный код этого демонстрационного приложения доступен в моем репозитории GitHub здесь .