Is it possible to reload only modified item in SwiftUI List/ForEach that uses @Binding

Viewed 32

Simple sample code with toggle button (slightly modified from hackingwithswift: This code(hackingwithswift original and my version) IS redrawing every list cell whenever any toggle happens. I modified code to better debug view drawing.

import SwiftUI

struct User: Identifiable {
    let id = UUID()
    var name: String
    var isContacted = false
}

struct ProfileView: View {
    @State private var users = [
        User(name: "Taylor"),
        User(name: "Justin"),
        User(name: "Adele")
    ]

    var body: some View {
        let _ = Self._printChanges()
        List($users) { $user in
            ProfileCell(user: $user)
        }
    }
}

struct ProfileCell: View{
    @Binding var user: User
    var body: some View{
        let _ = Self._printChanges()
        Text(user.name)
        Spacer()
        Toggle("User has been contacted", isOn: $user.isContacted)
            .labelsHidden()
    }
}

Running app and toggling will print following in console for every toggle:

ProfileView: _users changed.
ProfileCell: @self, _user changed.
ProfileCell: @self, _user changed.
ProfileCell: @self, _user changed.

Hackingwithswift tutorial states "Using a binding in this way is the most efficient way of modifying the list, because it won’t cause the entire view to reload when only a single item changes.", however that does not seem to be true. Is it possible to redraw only item that was changed?

1 Answers

Theoretically it should be working, but it seems they changed something since first introduction, because now on state change they recreate(!) bindings (all of them), so automatic view changes handler interpret that as view update (binding is a property after all).

A possible workaround for this is to help rendering engine and check view equitability manually.

Tested with Xcode 13.4 / iOS 15.5

demo

Main parts:

// 1
List($users) { $user in
    EquatableView(content: ProfileCell(user: $user)) // << here !!
}

// 2
struct ProfileCell: View, Equatable {
  static func == (lhs: ProfileCell, rhs: ProfileCell) -> Bool {
    lhs.user == rhs.user
  }

  // ...

// 3
struct User: Identifiable, Equatable {

Test module is here

Related