Я работаю над алгоритмом прокрутки, который принимает входные данные из UIPanGestureRecognizer для перемещения нарисованного объекта в пользовательском UIView.Я хочу, чтобы прокрутка была похожа на UIScrollView, вместо этого она выглядит неуклюжей.У меня проблема с реализацией случая, когда палец поднимается после жеста панорамирования, и частица замедляется, пока не остановится.
Замедление, вызванное алгоритмом, кажется, время от времени различается.Иногда кажется, что он на самом деле немного ускоряется, прежде чем замедляется.
Мне интересно, есть ли у вас какие-нибудь предложения, как это исправить?
Алгоритм работает следующим образом:
Когда состояние распознавателя равно«Начала» текущая позиция частицы сохраняется в переменной startPosition .
Пока состояние распознавателя «изменено», текущее положение частиц рассчитывается относительно движения.Текущая скорость сохраняется, и представление обновляется, чтобы отобразить частицу в новом месте.
Когда состояние распознавателя «закончено», мы входим в глобальный поток, используя GCD API для вычисленияоставшееся движение (чтобы предотвратить зависание интерфейса) .Цикл запускается и устанавливается на 60 кадров в секунду.Положение частиц обновляется, и скорость уменьшается на каждой итерации.Мы входим в основной поток, чтобы обновить представление для отображения частицы в ее новой позиции.
func scroll(recognizer: UIPanGestureRecognizer, view: UIView) {
switch recognizer.state {
case .began:
startPosition = currentPosition
case .changed:
currentPosition = startPosition + recognizer.translation(in: view)
velocity = recognizer.velocity(in: view)
view.setNeedsDisplay()
case .ended:
DispatchQueue.global().async {
let fps: Double = 60
let delayTime: Double = 1 / fps
var frameStart = CACurrentMediaTime()
let friction: CGFloat = 0.9
let tinyValue: CGFloat = 0.001
while (self.velocity > tinyValue) {
self.currentPosition += (self.velocity / CGFloat(fps))
DispatchQueue.main.sync {
view.setNeedsDisplay()
}
self.velocity *= friction
let frameTime = CACurrentMediaTime() - frameStart
if (frameTime < delayTime) {
// calculate time to sleep in μs
usleep(UInt32((delayTime - frameTime) * 1E6))
}
frameStart = CACurrentMediaTime()
}
}
default:
return
}
}
Проблема может заключаться в том, что цикл не работает с очень постоянной частотой кадров.Но частота кадров выглядит достаточно стабильной для меня (я могу ошибаться) .Вот пример расчетной частоты кадров:
"
Frame rate: 59.447833329705766
Frame rate: 57.68833849362473
Frame rate: 57.43794057083063
Frame rate: 53.11410092668673
Frame rate: 51.76492245230155
Frame rate: 52.71845062546561
Frame rate: 50.211233616282904
Frame rate: 59.86028817338459
Frame rate: 55.7360938798143
Frame rate: 47.55385819651489
Frame rate: 50.13437540167264
Frame rate: 48.93274027995551
Frame rate: 50.76905714109756
Frame rate: 57.06095686426517
Frame rate: 49.852101165412876
Frame rate: 51.49043459888154
Frame rate: 55.96442240956844
Frame rate: 53.66651780498373
Frame rate: 55.336349953967726
Frame rate: 51.4698476880566
"
Рассчитывается путем добавления следующей строки в конце цикла, непосредственно перед обновлением переменной frameStart : print("Frame rate: \(1 / (CACurrentMediaTime() - frameStart))")
Какие-нибудь предложения о том, как сделать частоту кадров еще более устойчивой?
Условия гонки могут быть проблемой, но currentPosition (которыйиспользуется для позиционирования частицы) переменная защищена семафором.И я не могу (из-за моего недостатка знаний) se других критических областей в коде.
var currentPosition: CGPoint {
get {
semaphore.wait()
let pos = _currentPosition
semaphore.signal()
return pos
}
set {
semaphore.wait()
_currentPosition = newValue
semaphore.signal()
}
}
Я рад услышать любые предложения.Спасибо!