Mastering in SwiftUI

Abhi Makadiya
Simform Engineering
5 min readDec 7, 2023

Insider tips and techniques to optimize app performance

SwiftUI is progressively gaining power and is becoming the preferred choice for building applications. In this post, we will cover important points to boost your app development and performance.

Which architecture is better: Redux or MVVM?

Redux provides a centralized state management approach. A single Store variable is created as an observable object at the root level that stores the whole application’s state. We pass this Store variable to every view as an Environment object. That means, the SwiftUI view will refresh when the Store variable changes.

As a result, a single variable change can refresh whole application views, which, in turn, decreases app performance when the modules are getting bigger.

MVVM, on the contrary, can create @ObservableObject for each view separately, simplifying SwiftUI projects. This setup ensures that changes in the observableObject only update the specific associated view, making the refresh more efficient and precise.

Use @StateObject and @ObservableObject

@StateObject is particularly useful for top-level views that are short-lived. On the other side, @ObservedObject is typically used when you want to share the same instance of an object across multiple views.

In the below example, the TapViewModel initializes every time on every refresh of RandomNumberView. Converting @ObservableObject into @StateObject simply solves the issue.

Create @Published variable with care

Excessive use of @Published properties can lead to unnecessary view updates, which might impact the app's performance. Overusing it could cause views to refresh more frequently than necessary, consuming unnecessary resources.

Alternatives of the @Published property:

  • Create a variable without using the @Published property in the ObservableObject class and use objectWillChange.send() whenever you want to refresh the UI.
  • Create a @State variable in a view that is responsible for updating the view only when needed like below:

Breaking down the content of the body into smaller to minimize the compilation time

The SwiftUI adds a limitation to adding only 10 views in each stack. There is a reason behind that. Directly adding multiple views in the stack can increase the compilation time. Better to create a separate variable/function for the subviews and call them in a body.

Prefer @State for the animations instead of @Published

@State manages and updates the local state within a view. SwiftUI is optimized to work with @State variables efficiently. When the variable changes, SwiftUI automatically recalculates the affected parts of the view hierarchy. This leads to better performance and smoother animations.

On the other hand, @Published is typically used in the context of the Observable Object pattern, which is more suitable for sharing data across different views or managing data in a model-like structure. While you can use @Published with animations, it might not provide the same level of animation performance and predictability as @State.

Declare Observable objects at a specific place to avoid unnecessary UI update

Updating the UI more frequently than necessary can lead to performance issues. By declaring observable objects strategically, you can minimize the number of updates and ensure that only relevant changes trigger UI refreshes.

In the below example, it used an observable object to get the tick count. Instead of declaring it in the ContentView, we declared it in the TickView. Consequently, the sole responsibility for refreshing every second lies with the TickView, sparing other views from UI updates. This strategic adjustment contributes to heightened app performance and overall fluidity.

Find which data change is causing a SwiftUI view to update

If you are adding the .onChange and .onReceive to check which variable is responsible for the UI update, don’t! SwiftUI provides a special, debug-only method to identify what change caused a view to reload itself.

Self._printChanges() should be called inside the body property and you can see the change logs in the console.

Same as above, we can write print statements within the body to check the current value of variables.

Use LazyHStack and LazyVStack if you have a long list

LazyHStack and LazyVStack views offer optimized memory management and rendering, which results in improved performance and responsiveness for your app.

Is there anything similar to viewDidLoad()?

You can replace .onAppear with the .onLoad modifier if your logic needs to call only once.

Use UIKit components directly in SwiftUI

If you don’t have much data flow between the SwiftUI view and the UIKit component, this modifier will help to place the UIKit component directly in the SwiftUI view.

Conditional modifier

Adding conditions in the modifier is not easy. You can use the turnery operator but you have to confirm if/else both statements. What if you only need an if statement? The below modifier will help you with it:

Avoid using .padding and .frame together

Combining the .padding and .frame modifiers on a single view can lead to redundant layout computations, causing performance drawbacks.

Use GeometryReader wisely

The GeometryReader view provides access to the dimensions and placement of its parent view, yet its usage can incur performance costs. This is due to the necessity of recalculating the layout for all child views whenever adjustments occur in the parent view. Therefore, exercise caution and employ it judiciously, reserving its use for situations where it is truly essential.

Use AnyView occasionally

In SwiftUI, you can use the AnyView type to show different types of views as needed. But if you use AnyView too much, it might make your code less safe and slow down performance improvements.

Must use ID with Foreach and List

When you use ForEach to make a list of views, make sure to include an “id” parameter. This helps SwiftUI know which item is which, so it can figure out when to add, update, or remove views as the list changes. Doing this can make your app work faster and smoother.

Prefer using Text over Label

Text is a lightweight view that is optimized for displaying small amounts of text, while Label is a more heavy-weight view suited for displaying larger amounts of text or mixed content. It can help improve the performance of your app, especially when displaying large amounts of text.

Use Group to return multiple view instances

If the same property will be applied for multiple views, the best way is to add them into a Group and apply common property only once.

Use .fixedSize() to specify that the frame doesn’t change

When you have views with a consistent size, you can use the .fixedSize() modifier to set their dimensions. This helps SwiftUI skip unnecessary layout calculations, leading to better performance.

Parting words

These insights are purely based on our experience. Though it’s not an extensive list for SwiftUI, it will help you start your journey to creating seamless applications.

For more updates on the latest development trends, follow the Simform Engineering blog.

Follow Us: Twitter | LinkedIn

--

--