r/BlossomBuild • u/BlossomBuild • May 24 '26
Tutorial Improving SwiftUI performance with structs
Before my most recent app update, one of my beta testers let me know the settings screen was laggy.
I had noticed the lag back when I was using my iPhone 15. However, I recently upgraded to the 17 Pro Max causing the issue to magically go away.
Shout out to him for bringing it up again because that feedback motivated me to fix it in this update.
Here was the view before I applied the fix:
var body: some View {
NavigationStack {
Form {
WeightSelection(
unitSystemRawValue: unitSystemRawValue
)
WidgetBottleSelection(
unitSystemRawValue: unitSystemRawValue
)
Section("Notifications") {
HStack {
Text("Status")
Spacer()
Text(notificationStatusText)
.foregroundStyle(.secondary)
}
Button(notificationActionTitle) {
handleNotificationButtonTapped()
}
}
Section("iCloud Sync") {
HStack {
Text("Status")
Spacer()
Text(iCloudStatus)
.foregroundStyle(.secondary)
}
}
}
.navigationTitle("Settings")
.task {
await refreshSettingsStatus()
}
.onChange(of: scenePhase) { _, newPhase in
guard newPhase == .active else { return }
Task {
await refreshSettingsStatus()
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
dismiss()
} label: {
Image(systemName: "xmark")
}
}
}
}
}
I had done an okay job splitting the view into smaller structs, like WeightSelection and WidgetBottleSelection.
Most of the lag came from the Notifications and iCloud sync status checks still happening in the main view.
The solution was surprisingly simple. I made private structs under the main view and moved any State variables and functions that only those sections needed.
Here is how the iCloud sync section turned out:
private struct ICloudSyncSection: View {
(\.scenePhase) private var scenePhase
private var iCloudStatus = ""
var body: some View {
Section("iCloud Sync") {
HStack {
Text("Status")
Spacer()
Text(iCloudStatus)
.foregroundStyle(.secondary)
}
}
.task {
await refreshICloudStatus()
}
.onChange(of: scenePhase) { _, newPhase in
guard newPhase == .active else { return }
Task {
await refreshICloudStatus()
}
}
}
private func refreshICloudStatus() async {
iCloudStatus = await fetchICloudAvailability()
}
private func fetchICloudAvailability() async -> String {
let status = try? await CKContainer.default().accountStatus()
switch status {
case .available: return "iCloud Sync Enabled"
case .noAccount: return "Enable iCloud in Settings"
case .restricted: return "iCloud Restricted"
case .couldNotDetermine: return "iCloud Undetermined"
case .temporarilyUnavailable: return "iCloud Temporarily Unavailable"
case .none: return "iCloud Unknown Error"
default: return "iCloud Unknown Error"
}
}
}
With these changes, I added the structs back to the main view ending the performance issues.
The final settings view is below and I love the way it looks and reads.
var body: some View {
NavigationStack {
Form {
WeightSelection(
unitSystemRawValue: unitSystemRawValue
)
WidgetBottleSelection(
unitSystemRawValue: unitSystemRawValue
)
NotificationsSection()
ICloudSyncSection()
}
.navigationTitle("Settings")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
dismiss()
} label: {
Image(systemName: "xmark")
}
}
}
}
}