SwiftUI Performance Tuning: Tips and Tricks

Things you should know to boost your app’s performance.
Dec 20 2022 · 6 min read

Background

SwiftUI is a powerful new framework for building user interfaces on Apple platforms.

It’s a declarative framework, which means you can define the desired state of your user interface and the framework takes care of updating the views to match the state.

This can make it easier to write and maintain your code, but it also means that you need to be mindful of how you use the framework to avoid performance issues.

SwiftUI has been around for almost 3 years now, and during this period, I have noticed a few things that can impact its performance, the mistakes every developer should avoid.

In this post, I’ve tried to cover a few important tips that we can take care of for SwiftUI app performance.

SwiftUI Performance Tips

Here are some tips to help you optimize the performance of your SwiftUI project —

Use structs instead of classes for your data model

SwiftUI works best with value types (such as structs) because they are easier to work with and can result in better performance. When possible, try to use structs instead of classes to store your data and pass it around your app.

This can be more efficient than using classes, which are reference types and require more memory management.

Avoid heavy work on the main thread

Performing intensive tasks, such as network requests, image processing, or complex computations on the main thread, can cause UI unresponsiveness, leading to a sluggish user experience.

Offloading heavy tasks to background threads using techniques like Grand Central Dispatch (GCD) or SwiftUI’s async/await can significantly improve app responsiveness and overall performance.

Avoid excessive usage of AnyView

Using AnyView can impact SwiftUI's performance to some extent. When used excessively, it might lead to performance issues due to the loss of SwiftUI's view hierarchy optimizations.

In some cases, especially in lists with a lot of data, this can have a performance impact. The reason is that SwiftUI relies on the type of views to compute the diffing. If it’s AnyView, it will not know how to compute the diffing, and it will just redraw the whole view, which is not efficient.

Use lazy stacks and grids when appropriate

Utilizing lazy stacks and grids efficiently manages large view lists by creating only the views that are currently visible on the screen and with that, it reduces unnecessary view creation.

This practice can significantly enhance app performance, particularly when handling extensive and resource-intensive view lists.

Use @State and @Binding instead of @ObservedObject or @EnvironmentObject when possible

Using @State and @Binding boosts the speed because they refresh the view only when their values change.

Meanwhile, @ObservedObject and @EnvironmentObject refresh the view for any property alteration. That’s why even if a single property change within these objects occurs, the entire view associated with them will refresh.

This broader scope of updates can impact performance compared to the more targeted updates provided by @State and @Binding.

 

Use ForEach with an explicit id parameter

Using ForEach with an explicit ID parameter is beneficial in SwiftUI. It helps SwiftUI efficiently manage lists of views by allowing each element to be uniquely identified.

This allows SwiftUI to determine which specific view needs to be created, updated, or removed when the list changes, which can improve performance.

If you have any array of the object which has a unique field then you can set that field as the value of the id param.

This same rule applies to the list view, it also has an id param for the same purpose.

Use .preference(key: value:) to pass data up the view hierarchy

This approach doesn't rely on conventional mechanisms like @State, @Binding, or @EnvironmentObject. Instead, it permits the transfer of data among various views without incurring the overhead of invalidating or rebuilding views.

This technique can notably enhance performance by optimizing data communication between views.

Use Text instead of Label when possible

Text is a lightweight view designed specifically for displaying small amounts of text, while Label is comparatively heavier and more suitable for larger text or mixed content.

By using Text appropriately, especially for displaying limited text content, you can potentially enhance the performance of your SwiftUI app.

Avoid using .onAppear and .onDisappear if possible

These modifiers can cause unnecessary view body recomputations multiple times, especially when used excessively or inappropriately which can negatively impact the performance.

However, sometimes they’re necessary for specific functionalities. So, try to use them only when necessary, or consider using .onChange to update your views more selectively.

Avoid using complex view hierarchies

Complex hierarchies can increase the workload on SwiftUI to keep it up to date, leading to performance issues.

Simplifying views and keeping the hierarchy as flat and straightforward as possible can help improve rendering speed and responsiveness.

Same as above, try to divide the views into multiple parts so that it will be easy to maintain.

Use @ViewBuilder to create views conditionally

The @ViewBuilder attribute allows us to create views conditionally, which can help to improve our app's performance by avoiding unnecessary view creation and layout work.

However, its direct impact on performance may vary depending on the context and the complexity of the conditionals. Overall, it’s a recommended practice for improving SwiftUI code readability and maintainability.

Use AnimatableData and animation() to animate changes to your views

SwiftUI provides a built-in mechanism for animating changes to your views using AnimatableData and animation() modifier. This can help improve the performance of your animations by taking advantage of hardware acceleration.

Use canvas to combine multiple draw calls into a single draw call

The canvas takes GraphicsContext and size as parameters to allow immediate mode drawing within the containing frame.

This can help reduce the number of draw calls that need to be performed, which can improve performance.

Use .matchedGeometryEffect to animate changes to your layout

It enables smooth transitions between views by animating changes such as size, position, or shape of views while leveraging hardware acceleration, thereby enhancing the performance of your app’s animations.

This feature is quite fast, making your animations look good and work smoothly without slowing down your app.

Use .simultaneousGesture() to handle multiple gestures on a single view

The .simultaneousGesture() modifier allows you to manage multiple gestures on a single view efficiently, contributing to performance improvements by minimizing the need for additional view creation. This optimization can positively impact overall performance.

Avoid using .transition & .animation(_:) with large numbers of views

The .transition & .animation(_:) modifiers allow you to animate changes to your views, but they can be expensive to use with large numbers of views.

If you need to animate changes to many views at once, consider using a lower-level animation technique, such as withAnimation(), to improve performance.

Avoid using .padding and .frame together

Using .padding and .frame together may lead to redundant layout calculations in SwiftUI, potentially impacting performance.

However, its effect on performance is generally minimal compared to other optimization strategies. It's more about efficient usage rather than a major performance booster.

Use .fixedSize() to specify the size of views that don't change

By specifying the .fixedSize(), SwiftUI can optimize layout calculations and avoid unnecessary recalculations for these unchanging views, which can improve performance.

Use GeometryReader occasionally

The GeometryReader view allows you to access the size and position of its parent view, but it can be expensive to use, as it requires the layout of all its child views to be recomputed whenever its parent view changes.

Consider using it sparingly and only when necessary to avoid unnecessary layout recalculations.

Avoid using complex expressions in your view declarations

SwiftUI uses a different algorithm to determine which views need to be updated when your data changes.

If your view declarations contain complex expressions, it can be more difficult for the diffing algorithm to determine which views need to be updated, which can lead to performance issues.

Simplifying expressions can help the algorithm to efficiently identify the relevant updates, which can potentially enhance the performance.

Use the performance tools in Xcode

Utilizing the performance tools in Xcode can significantly help in identifying, diagnosing, and resolving performance issues within our SwiftUI app.

These tools, such as the SwiftUI Profiler and View Debugger, offer insights into view hierarchy, rendering times, and potential bottlenecks, facilitating the optimization process for improving performance.

And here we are done with the list, but still, it’s not enough.

Thanks for reading! 🍻

Conclusion

I’ve added this list based on my experience it can vary according to a person's need, and that will be updated if new patterns start appearing.

Of course, SwiftUI is not perfect and still has some things that need to be improved, but it’s an amazing and powerful way of building user interfaces on iOS.

The best way to get started is by trying things out. You can even make a performance issue on purpose, and try to find it with instruments. It will help you to get started using it.

Related Popular Article


amisha-i image
Amisha Italiya
iOS developer @canopas | Sharing knowledge of iOS development


amisha-i image
Amisha Italiya
iOS developer @canopas | Sharing knowledge of iOS development

contact-footer
Say Hello!
footer
Subscribe Here!
Follow us on
2024 Canopas Software LLP. All rights reserved.