Reputation: 121
I'm using UIPanGestureRecognizer
in my iOS app programmed with Swift to let the user control a color component of an image. Although the update only takes a very small fraction of a second, the overall look is laggy because UIPanGestureRecognizer
is called dozens of times a second. What is the best way to ensure that this does not happen? Right now I'm just making it so that it only calls the function every third update, but this seems unideal.
Upvotes: 1
Views: 651
Reputation: 437482
I believe that your solution (only considering every third update) is predicated on a false assumption that if you can't respond to gestures quickly enough, that they get backlogged. But that's not how it works. If the main thread cannot keep up with gestures, it drops gestures, not getting backlogged. In fact, your solution (considering every third update) only introduces lagginess rather than solving it.
So, the goal is to make touches more responsive. There are two possible issues here:
Pan gestures require a little movement before it's successfully identified as a pan, thus there can be a slight lag before the gesture is first recognized.
While there are tricks around that prior issue with gesture recognizers, there is a second feature possible when you don't use gesture recognizers and implement touchesBegan
, touchesMoved
, etc., instead. Not only do you avoid that lag to first recognize the gesture, but you can also participate in predictive touches.
So, for highly performant touches on the device, you could do something like:
var start: CGPoint?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
start = touches.first?.location(in: view)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let point: CGPoint
if let predictedTouch = event?.predictedTouches(for: touch)?.last {
point = predictedTouch.location(in: view)
} else {
point = touch.location(in: view)
}
updateColor(for: point)
}
// make sure when you're done to update in `touchesEnded`, too, in case
// the last `touchesMoved` generated a predictive touch, but when we're
// done, we really want to use the final real `location`
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
updateColor(for: touch.location(in: view))
}
private func updateColor(for point: CGPoint) {
// do something with `start` and `point` to adjust the color
}
Note, in my experience you won't always experience predictive touches in the simulator, so make sure to test this on a physical device.
It's worth recognizing that there is a third potential issue. Specifically, if your "change color component" takes too long to render a consistent 60 fps, then you'd want to do some time profiling in instruments to identify the source of that problem. But we cannot comment without seeing a reproducible example of your problem (i.e. a MCVE).
Also, make sure that you do "release" builds (depending upon your Swift code, it can make a fairly significant performance change) and test it on a physical device because the graphics performance is very different in a device than it is in the simulator.
But if you use predictive touches and test a release build on a physical device, you should generally get decent performance. But it's hard to say without seeing some code.
Upvotes: 1