From UIKit to SwiftUI: Navigating the Complexities of Infinite Scrolling
A heart rate graphing app that displays an unparalleled amount of data via an infinitely scrolling list helps users spot trends and patterns easily. Navigating the transition from UIKit’s table view to SwiftUI’s List was a complex hurdle, adding a layer of challenge to the creation process.
SwiftUI lacks much of the control of UIKit’s methods for optimizing table views like reusable cells, prefetching data, and fine-grained gesture recognition. We are seeing some of these shortcomings addressed in iOS 17 like with the much anticipated scrollPosition(id:anchor:).
For now, delivering a smooth, non-janky, infinite scrolling experience with SwiftUI means carefully refining operational efficiency without UIKit. Of course, the benefits of efficient code will stay in style even when SwiftUI is a distant memory.
I’m going to walk you through our process of fine-grained optimization of performance using Combine. We benefit from granular profiling using the amazing open-source TimeLane custom instrument by Marin Todorov.
If you are a project developer for HeartBond, open Instruments and TimeLane, installing it into Instruments if needed. Then choose Product > Profile in Xcode. You should then be able to record the app’s subscriptions and events. We are here to help if you have any questions.
We found an inefficiency related to the cancellation of chart data operations as list row views are first constructed. On rare occasions, it might cause the momentary display of a blank row. Our system has pathways for recovering from this condition, but preventing it from happening is a win for an improved user experience.
The process of profiling involves setting up an A/B test, toggling a single function between returning a Publisher with two different versions. We then analyze the results recorded by the TimeLane instrument. Our choosing Combine gave us the powerful capability to step into time like it is standing still.
We changed our updateMultiDeviceEntriesPublisher(day:)
to no longer have susceptibility to cancellation of its chart entry adding. Once the operations complete, we do not have to repeat processing for a day. By preventing repetition, we save a few hundred milliseconds and prevent users from missing a view.
This optimization only scratches the surface of what is possible. It shows how we designed our project to be maintainable, scalable for future growth, while making verifiable performance gains.
Our app is already blazingly fast on Macs. Further refinements will bring more of that speed to iOS devices.
How do we have code run to completion when a subscription is canceled?
We use the technique of wrapping a Future
in a Deferred
publisher. This allows us to run code to completion when an outer subscription is canceled.
From the original version susceptible to cancellation, shown below, we adjusted chart data processing to finish during such an event. We verified the results with TimeLane in Instruments.
Wrapping the code in a Deferred Future publisher enables it to complete if the outer subscription is canceled.
How did we reduce jankiness when scrolling?
We observed the limitations of SwiftUI List scrolling and designed our view presentation to have a stepwise progression of data loading. This allows us to process data in a series of intervals that are short enough, and interleaved with small reliefs for UI updates, to lower jankiness. Those milliseconds we saved before can really add up. Our autoscrolling feature illustrates how we coordinate the loading of data with the scrolling of a List.
Take care with DispatchQueue.async
One of the most important things to remember when using Combine is you lose determinism in an operation chain once you dispatch to main, or another queue, asynchronously. Therefore, keeping such calls to a minimum, or not at all as in our code examples, is a good practice. Wrapping code in a Future
can often fix problems related to dispatching.
Your feedback
We hope you enjoyed this article. Please let us know what you think in the comments below. Our apps are available for free on the App Stores. We would love to hear from you.