Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2025 SwiftSwiftUI & UI FrameworksDeveloper Tools

WWDC25 · 36 min · Swift / SwiftUI & UI Frameworks / Developer Tools

Optimize SwiftUI performance with Instruments

Discover the new SwiftUI instrument. We’ll cover how SwiftUI updates views, how changes in your app’s data affect those updates, and how the new instrument helps you visualize those causes and effects. To get the most out of this session, we recommend being familiar with writing apps in SwiftUI.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

  • 0:00 — Introduction & Agenda
  • 2:19 — Discover the SwiftUI instrument
  • 4:20 — Diagnose and fix long view body updates
  • 19:54 — Understand causes and effects of SwiftUI updates
  • 35:01 — Next steps

Code shown on screen · 8 snippets

LandmarkListItemView swift · at 8:47 ↗
import SwiftUI
import CoreLocation

/// A view that shows a single landmark in a list.
struct LandmarkListItemView: View {
    @Environment(ModelData.self) private var modelData

    let landmark: Landmark

    var body: some View {
        Image(landmark.thumbnailImageName)
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
            .overlay { ... }
            .clipped()
            .cornerRadius(Constants.cornerRadius)
            .overlay(alignment: .bottom) {
                VStack(spacing: 6) {
                    Text(landmark.name)
                        .font(.title3).fontWeight(.semibold)
                        .multilineTextAlignment(.center)
                        .foregroundColor(.white)

                    if let distance {
                        Text(distance)
                            .font(.callout)
                            .foregroundStyle(.white.opacity(0.9))
                            .padding(.bottom)
                    }
                }
            }
            .contextMenu { ... }
    }

    private var distance: String? {
        guard let currentLocation = modelData.locationFinder.currentLocation else { return nil }
        let distance = currentLocation.distance(from: landmark.clLocation)

        let numberFormatter = NumberFormatter()
        numberFormatter.numberStyle = .decimal
        numberFormatter.maximumFractionDigits = 0

        let formatter = MeasurementFormatter()
        formatter.locale = Locale.current
        formatter.unitStyle = .medium
        formatter.unitOptions = .naturalScale
        formatter.numberFormatter = numberFormatter
        return formatter.string(from: Measurement(value: distance, unit: UnitLength.meters))
    }
}
LocationFinder Class with Cached Distance Strings swift · at 12:13 ↗
import CoreLocation

/// A class the app uses to find the current location.
@Observable
class LocationFinder: NSObject {
    var currentLocation: CLLocation?
    private let currentLocationManager: CLLocationManager = CLLocationManager()

    private let formatter: MeasurementFormatter

    override init() {
        // Format the numeric distance
        let numberFormatter = NumberFormatter()
        numberFormatter.numberStyle = .decimal
        numberFormatter.maximumFractionDigits = 0

        // Format the measurement based on the current locale
        let formatter = MeasurementFormatter()
        formatter.locale = Locale.current
        formatter.unitStyle = .medium
        formatter.unitOptions = .naturalScale
        formatter.numberFormatter = numberFormatter
        self.formatter = formatter

        super.init()
        
        currentLocationManager.desiredAccuracy = kCLLocationAccuracyKilometer
        currentLocationManager.delegate = self
    }

    // MARK: - Landmark Distance

    var landmarks: [Landmark] = [] {
        didSet {
            updateDistances()
        }
    }

    private var distanceCache: [Landmark.ID: String] = [:]

    private func updateDistances() {
        guard let currentLocation else { return }

        // Populate the cache with each formatted distance string
        self.distanceCache = landmarks.reduce(into: [:]) { result, landmark in
            let distance = self.formatter.string(
                from: Measurement(
                    value: currentLocation.distance(from: landmark.clLocation),
                    unit: UnitLength.meters
                )
            )
            result[landmark.id] = distance
        }
    }

    // Call this function from the view to access the cached value
    func distance(from landmark: Landmark) -> String? {
        distanceCache[landmark.id]
    }
}

extension LocationFinder: CLLocationManagerDelegate {
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        switch currentLocationManager.authorizationStatus {
        case .authorizedWhenInUse, .authorizedAlways:
            currentLocationManager.requestLocation()
        case .notDetermined:
            currentLocationManager.requestWhenInUseAuthorization()
        default:
            currentLocationManager.stopUpdatingLocation()
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        print("Found a location.")
        currentLocation = locations.last
        // Update the distance strings when the location changes
        updateDistances() 
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) {
        print("Received an error while trying to find a location: \(error.localizedDescription).")
        currentLocationManager.stopUpdatingLocation()
    }
}
LandmarkListItemView with Favorite Button swift · at 16:51 ↗
import SwiftUI
import CoreLocation

/// A view that shows a single landmark in a list.
struct LandmarkListItemView: View {
    @Environment(ModelData.self) private var modelData

    let landmark: Landmark

    var body: some View {
        Image(landmark.thumbnailImageName)
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
            .overlay { ... }
            .clipped()
            .cornerRadius(Constants.cornerRadius)
            .overlay(alignment: .bottom) { ... }
            .contextMenu { ... }
            .overlay(alignment: .topTrailing) {
                let isFavorite = modelData.isFavorite(landmark)
                Button {
                    modelData.toggleFavorite(landmark)
                } label: {
                    Label {
                        Text(isFavorite ? "Remove Favorite" : "Add Favorite")
                    } icon: {
                        Image(systemName: "heart")
                            .symbolVariant(isFavorite ? .fill : .none)
                            .contentTransition(.symbolEffect)
                            .font(.title)
                            .foregroundStyle(.background)
                            .shadow(color: .primary.opacity(0.25), radius: 2, x: 0, y: 0)
                    }
                }
                .labelStyle(.iconOnly)
                .padding()
            }
    }
}
ModelData Class swift · at 17:20 ↗
/// A structure that defines a collection of landmarks.
@Observable
class LandmarkCollection: Identifiable {
    // ...
    var landmarks: [Landmark] = []
    // ...
}

/// A class the app uses to store and manage model data.
@Observable @MainActor
class ModelData {
    // ...
    var favoritesCollection: LandmarkCollection!
    // ...

    func isFavorite(_ landmark: Landmark) -> Bool {
        var isFavorite: Bool = false
        
        if favoritesCollection.landmarks.firstIndex(of: landmark) != nil {
            isFavorite = true
        }
        
        return isFavorite
    }

    func toggleFavorite(_ landmark: Landmark) {
        if isFavorite(landmark) {
            removeFavorite(landmark)
        } else {
            addFavorite(landmark)
        }
    }

    func addFavorite(_ landmark: Landmark) {
        favoritesCollection.landmarks.append(landmark)
    }

    func removeFavorite(_ landmark: Landmark) {
        if let landmarkIndex = favoritesCollection.landmarks.firstIndex(of: landmark) {
            favoritesCollection.landmarks.remove(at: landmarkIndex)
        }
    }
    // ...
}
OnOffView swift · at 20:50 ↗
struct OnOffView: View {
    @State private var isOn = true
    var body: some View {
        Text(isOn ? "On" : "Off")
    }
}
Favorites View Model Class swift · at 29:21 ↗
@Observable class ViewModel {
    var isFavorite: Bool
    
    init(isFavorite: Bool = false) {
        self.isFavorite = isFavorite
    }
}
ModelData Class with New ViewModel swift · at 29:21 ↗
@Observable @MainActor
class ModelData {
    // ...
    var favoritesCollection: LandmarkCollection!
    // ...

    @Observable class ViewModel {
        var isFavorite: Bool
        init(isFavorite: Bool = false) {
            self.isFavorite = isFavorite
        }
    }

    // Don't observe this property because we only need to react to changes
    // to each view model individually, rather than the whole dictionary
    @ObservationIgnored private var viewModels: [Landmark.ID: ViewModel] = [:]

    private func viewModel(for landmark: Landmark) -> ViewModel {
        // Create a new view model for a landmark on first access
        if viewModels[landmark.id] == nil {
            viewModels[landmark.id] = ViewModel()
        }
        return viewModels[landmark.id]!
    }

    func isFavorite(_ landmark: Landmark) -> Bool {
        // When a SwiftUI view, such as LandmarkListItemView, calls
        // `isFavorite` from its body, accessing `isFavorite` on the 
        // view model here establishes a direct dependency between
        // the view and the view model
        viewModel(for: landmark).isFavorite
    }

    func toggleFavorite(_ landmark: Landmark) {
        if isFavorite(landmark) {
            removeFavorite(landmark)
        } else {
            addFavorite(landmark)
        }
    }

    func addFavorite(_ landmark: Landmark) {
        favoritesCollection.landmarks.append(landmark)
        viewModel(for: landmark).isFavorite = true
    }

    func removeFavorite(_ landmark: Landmark) {
        if let landmarkIndex = favoritesCollection.landmarks.firstIndex(of: landmark) {
            favoritesCollection.landmarks.remove(at: landmarkIndex)
        }
        viewModel(for: landmark).isFavorite = false
    }
    // ...
}
Cause and effect: EnvironmentValues swift · at 31:34 ↗
struct View1: View {
    @Environment(\.colorScheme)
    private var colorScheme

    var body: some View {
        Text(colorScheme == .dark
                ? "Dark Mode"
                : "Light Mode")
    }
}

struct View2: View {
    @Environment(\.counter) private var counter

    var body: some View {
        Text("\(counter)")
    }
}

Resources