10 SwiftUI Performance Tips: My experience using SwiftUI

Have you ever wondered why that SwiftUI view isn't performing as expected? Despite our iOS dev expertise, SwiftUI sometimes throws unexpected hangs and hitches with its rendering, why is that? Is there some compact research to understand its lifecycle, data management, and best practices? In this article, I'm going to share the challenges I faced when building SwiftUI views in my Pomodoro watchOS app, and how I resolved them with the knowledge I learned from WWDC videos.


Learn more about SwiftUI Data management in my other article here:


Problem-Solution Breakdown:

1. Problem: Misunderstanding of how SwiftUI draws Views.

- Solution: Views in SwiftUI are a direct reflection of the state and not a sequence of events. They get re-computed or re-rendered whenever there's a data change. Remember that these views are lightweight and SwiftUI does the heavy lifting by efficiently diffing the view changes, thereby eliminating the need to manage render states.

2. Problem: Inefficient View Life Cycle Management.

- Solution: The lifespan of a SwiftUI view is closely linked to its identity. When a view is about to appear, its lifetime commences, and it concludes when the view disappears. To maintain continuity, ensure views use stable IDs, and avoid unnecessary branches using ifs’ inside the body.

3. Problem: Using AnyViews in body leads to bad performance.

- Solution: It's better to leverage `@ViewBuilder` and avoid type-erasure with AnyViews.

4. Problem: Unclear about necessary data for view existence.

- Solution: When creating views, ascertain the source of truth, understand what the view needs to exist, and pinpoint what data the view must manipulate.

5. Problem: Mismanaging Data Flow.

- Solution: Adhere to the following when dealing with data in SwiftUI:

- Use constants for unchanging views.

- `@State` is for transient view-owned data.

- `@Binding` is to manipulate data owned by other views or from any `ObservableObject`.

- Use `ObservableObject` to separate UI data and manage data lifecycle efficiently.

- Never set default values for properties wrapped with `@ObservedObject` to avoid performance degradation. Instead, opt for `@StateObject` easy to create objects.

6. Problem: Incorrectly setting up @Environment properties.

- Solution: Misconfiguring `@Environment` properties might cause crashes. Ensure it's set up correctly at the root, and utilize the wrapper effectively for reading and writing.

7. Problem: Encapsulating another SwiftUI view within view properties.

- Solution: Avoid holding any SwiftUI view inside another view's properties. Always declare them within the body function.

8. Problem: Expensive view initialization and logic-laden body functions.

- Solution: Keep view initializations lightweight and ensure body functions remain pure, free from any logic.

9. Problem: Inefficient dependencies and unnecessary redrawing.

- Solution: Use `@Observable` to restrict dependencies. Extract views to prevent them from being redrawn when there's a data change. Remember, the goal is to make the view depend only on the data it needs.

10. Problem: Slow updates causing hangs and hitches.

- Solution: Use performance debugging steps like measuring, identifying causes, and optimizing. Be cautious about the common culprits: expensive dynamic properties, expensive state objects, slow body computations, and inefficient identification.

Dive deep into its life cycle, data management practices, and leverage tools at your disposal, such as the `Self._printchanges` to monitor requests. With practice and persistence, you'll find yourself creating seamless and high-performant SwiftUI UIs in no time.


Learn more about SwiftUI Data management in my other article here:

Thumbnail of this article was a Photo taken by Solen Feyissa on Unsplash

Previous
Previous

#1 - TDD, Pair Programming, and Katas in iOS/Swift

Next
Next

Mastering SwiftUI: Optimize UI Performance & Streamline Data Flow