Search code examples
optimizationswiftuiforeachview

SwiftUI avoid ForEach rendering all items when only one changes


I'm using a ForEach to loop over an array of structures and display a View for each item of the array.
When I'm mutating a single item, the main view re-render every element of the array. Is there a way to avoid this when using structures? I'd like that only the view corresponding to my modified item to be re-rendered.

Is it achievable with structures or do I need to use classes with ObservedObjects ?

A simplified version would look like this (the real version uses the Defauts package with an array and the item view is way more expensive, but the idea is the same).

import SwiftUI
import PlaygroundSupport

struct Player {
    var name: String
    var score: Int
}

struct PlayerView: View {
    @Binding var player: Player
    
    var body: some View {
        let _ = Self._printChanges()
        return HStack(spacing: 12) {
            Text(player.name).font(.system(size: 20))
            Text("\(player.score)").font(.system(size: 20))
            Button("plus") { player.score += 1 }.font(.system(size: 20))
        }
    }
}

struct PlayersView: View {
    @State var players = [
        Player(name: "John", score: 12), 
        Player(name: "Jane", score: 15)
    ]
    
    var body: some View {
        VStack {
            ForEach($players, id: \.name) { player in
                PlayerView(player: player)
            }
            Button("inc", action: {players[0].score += 10})
        }
    }
}

PlaygroundPage.current.setLiveView(PlayersView())

Solution

  • Just make model Equatable so rendering engine could detect if dependent property is really changed.

    Tested with Xcode 13.4 / iOS 15.5

    struct Player: Equatable {    // << here !!
        var name: String
        var score: Int
    }